【Java 网络编程全解】Socket 套接字与 TCP/UDP 通信实战全解

【Java 网络编程全解】Socket 套接字与 TCP/UDP 通信实战全解

文章目录

一、 网络编程基础

  • 什么是网络编程

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。

只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。

特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。

1.1 网络编程中的基本概念

1.1.1 发送端和接收端

在一次网络数据传输时:

  • 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
  • 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
  • 收发端:发送端和接收端两端,也简称为收发端。

注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。

1.1.2 请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输:

  1. 第一次:请求数据的发送
  2. 第二次:响应数据的发送
在这里插入图片描述
1.1.3 客户端和服务端
  • 服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
  • 客户端:获取服务的一方进程,称为客户端。

对于服务来说,一般是提供:

  1. 客户端获取服务资源
    例如:客户端主机的客户端进程发送“获取服务资源”的请求到服务端主机的服务端进程,服务端进程发送“返回服务资源”的响应到客户端进程,如:传输视频资源、图片资源、文本资源。
  2. 客户端保存资源在服务端
    例如:客户端主机的客户端进程发送“保存用户资源”的请求到服务端主机的服务端进程,服务端进程保存资源后发送“返回处理结果”的响应到客户端进程。

二、Socket套接字

概念: Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。

分类: Socket套接字主要针对传输层协议划分为如下三类:

  1. 流套接字:使用传输层TCP协议

TCP,即(传输控制协议),传输层协议。

以下为TCP的特点:

  • 有连接
  • 可靠传输
  • 面向字节流
  • 有接收缓冲区,也有发送缓冲区
  • 大小不限

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

  1. 数据报套接字:使用传输层UDP协议

UDP,即(用户数据报协议),传输层协议。

以下为UDP的特点:

  • 无连接
  • 不可靠传输
  • 面向数据报
  • 有接收缓冲区,无发送缓冲区
  • 大小受限:一次最多传输64k

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据(假如100个字节),必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

  1. 原始套接字

原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。

Socket编程注意事项客户端和服务端:开发时,经常是基于一个主机开启两个进程作为客户端和服务端,但真实的场景,一般都是不同主机。注意目的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程。Socket编程是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议,也需要考虑。关于端口被占用的问题如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这种情况也叫端口被占用。对于java进程来说,端口被占用的常见报错信息如下:
在这里插入图片描述

三、UDP数据报套接字编程

API 介绍

  1. DatagramSocket :DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
  • DatagramSocket 构造方法:
方法签名说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
  • DatagramSocket 方法:
方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字
  1. DatagramPacket :DatagramPacket 是UDP Socket发送和接收的数据报。
  • DatagramPacket 构造方法:
方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个 DatagramPacket 以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个 DatagramPacket 以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号
  • DatagramPacket 方法:
方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据
  1. 构造UDP发送的数据报时,需要传入 SocketAddress,该对象可以使用 InetSocketAddress 来创建。
  • InetSocketAddress:InetSocketAddressSocketAddress 的子类)构造方法:
方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

代码示例:

  • UDP Echo Server
//服务器publicclassUdpEchoServer{//首先定义socket对象privateDatagramSocket socket =null;/** * * @param port 端口号:区分一个主机上的不同的应用程序 * @throws SocketException */publicUdpEchoServer(int port)throwsSocketException{ socket=newDatagramSocket(port);}/** * 启动服务器 */publicvoidstart()throwsIOException{System.out.println("server start!");//死循环:编写服务器的一种常见做法,不断处理客服端发来的请求while(true){//1. 读取请求并解析DatagramPacket requestPacket =newDatagramPacket(newbyte[1024],1024); socket.receive(requestPacket);//二进制转字符串String request =newString(requestPacket.getData(),0,requestPacket.getLength());//2. 根据请求计算响应。(注:通常是一个复杂的过程。此处只做回显数据)String response =process(request);//3. 将响应返回给客户端DatagramPacket responsePacket =newDatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress()); socket.send(responsePacket);//打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(), requestPacket.getPort(),request,response);}}//根据请求计算响应publicStringprocess(String request){return request;}publicstaticvoidmain(String[] args)throwsIOException{UdpEchoServer server =newUdpEchoServer(9090); server.start();}}
  • UDP Echo Client
//客户端publicclassUdpEchoClient{privateDatagramSocket socket =null;privateString serverIp;privateint serverPort;/** * 此处和服务器不一样,不用指定端口号 * 意味着操作系统会自动分配一个空闲的端口号 * @param serverIp : 服务器Ip * @param serverPort : 服务器端口号 * @throws SocketException */publicUdpEchoClient(String serverIp,int serverPort)throwsSocketException{ socket =newDatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}publicvoidstart()throwsIOException{System.out.println("client start!");Scanner scanner =newScanner(System.in);//输入字符串发给服务器,从服务器读取响应while(true){// 1. 从控制台读取用户输入System.out.print("->");String request = scanner.next();if(request.equals("exit")){break;}// 2. 将用户输入的字符串构造出UDP数据报进行发送DatagramPacket requestPacket =newDatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(this.serverIp),this.serverPort); socket.send(requestPacket);// 3. 从服务器读取响应DatagramPacket responsePacket =newDatagramPacket(newbyte[1024],1024); socket.receive(responsePacket);String response =newString(responsePacket.getData(),0,responsePacket.getLength());// 4. 显示响应System.out.println(response);}}publicstaticvoidmain(String[] args)throwsIOException{UdpEchoClient client =newUdpEchoClient("127.0.0.1",9090); client.start();}}
  • UDP Dict Server

编写一个英译汉的服务器,只需要重写 process 方法:

/** * 英译汉服务器 */publicclassUdpDictServerextendsUdpEchoServer{privateMap<String,String> dict =newHashMap<String,String>();/** * @param port 端口号:区分一个主机上的不同的应用程序 * @throws SocketException */publicUdpDictServer(int port)throwsSocketException{super(port); dict.put("hello","你好"); dict.put("world","世界"); dict.put("cat","小猫"); dict.put("dog","小狗"); dict.put("pig","小猪");}@OverridepublicStringprocess(String request){return dict.getOrDefault(request,"没有找到该单词");}publicstaticvoidmain(String[] args)throwsIOException{UdpDictServer server =newUdpDictServer(9090);; server.start();}}

四、TCP流套接字编程

和刚才UDP类似,实现一个简单的英译汉的功能。

API 介绍

  1. ServerSocket:ServerSocket 是创建TCP服务端Socket的API。
  • ServerSocket 构造方法:
方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口
  • ServerSocket 方法:
方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字
  1. Socket:Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept 方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

  • Socket 构造方法:
方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
  • Socket 方法:
方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

代码示例

  • TCP Echo Server
publicclassTcpEchoServer{ServerSocket serverSocket =null;publicTcpEchoServer(int port)throwsIOException{ serverSocket =newServerSocket(port);}publicvoidstart()throwsIOException{System.out.println("server start");while(true){// 首先接收客户端的连接,然后才能进行通信// 如果有客户端和服务器建立好了连接,accept 才能返回// 否则 accept 会阻塞Socket socket = serverSocket.accept();// 通过这个方法处理这个客服端整个的连接过程processConnection(socket);}}privatevoidprocessConnection(Socket socket){System.out.printf("[%s:%d] 客户端上线!\n",socket.getInetAddress(),socket.getPort());try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();){Scanner scanner =newScanner(inputStream);PrintWriter writer =newPrintWriter(outputStream);while(true){// 处理多次请求/响应的操作//1. 读取请求并解析if(!scanner.hasNext()){System.out.printf("[%s:%d] 客户端下线!\n",socket.getInetAddress(),socket.getPort());break;}String request = scanner.next();//2. 根据请求计算响应String response =process(request);//3. 把响应写回客户端 writer.println(response); writer.flush();//4. 打印日志System.out.printf("[%s:%d] res: %s,resp: %s\n",socket.getInetAddress(),socket.getPort(), request,response);}}catch(IOException e){ e.printStackTrace();}finally{try{ socket.close();}catch(IOException e){thrownewRuntimeException(e);}}}publicStringprocess(String request){return request;}publicstaticvoidmain(String[] args)throwsIOException{TcpEchoServer server =newTcpEchoServer(9090); server.start();}}
  • TCP Echo Client
publicclassTcpEchoClient{privateSocket socket =null;publicTcpEchoClient(String serverIp,int serverPort)throwsIOException{ socket =newSocket(serverIp,serverPort);}publicvoidstart(){System.out.println("client start");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();){Scanner scanner =newScanner(System.in);Scanner scannerNetwork =newScanner(inputStream);PrintWriter writer =newPrintWriter(outputStream);while(true){//1. 从控制台读取输入System.out.print("->");String request = scanner.next();//2. 把请发送给服务器 writer.println(request); writer.flush();//3. 从服务器获取响应if(!scannerNetwork.hasNext()){break;}String reponse = scannerNetwork.next();// 4. 把响应显示到控制台上System.out.println(reponse);}}catch(IOException e){ e.printStackTrace();}}publicstaticvoidmain(String[] args)throwsIOException{TcpEchoClient client =newTcpEchoClient("127.0.0.1",9090); client.start();}}
4.1 服务器引入多线程

如果只是单个线程,无法同时响应多个客户端。此处给每个客户端都分配一个线程:

// 启动服务器publicvoidstart()throwsIOException{System.out.println("服务器启动!");while(true){Socket clientSocket = serverSocket.accept();Thread t =newThread(()->{processConnection(clientSocket);}); t.start();}}
4.2 服务器引入线程池

为了避免频繁创建销毁线程,也可以引入线程池:

// 启动服务器publicvoidstart()throwsIOException{System.out.println("服务器启动!");ExecutorService service =Executors.newCachedThreadPool();while(true){Socket clientSocket = serverSocket.accept();// 使用线程池,来解决上述问题 service.submit(newRunnable(){@Overridepublicvoidrun(){processConnection(clientSocket);}});}}

长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

对比以上长短连接,两者区别如下:

  1. 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  2. 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。

两者的使用场景有不同:

  • 短连接适用于客户端请求频率不高的场景,如浏览网页等。
  • 长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

扩展了解:
基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。

由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。

一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求。

实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。


总结: Java 网络编程的核心是基于 Socket 套接字实现跨进程数据传输,核心概念包含发送 / 接收端、请求 / 响应、客户端 / 服务端。Socket 套接字主要分为 TCP 流套接字和 UDP 数据报套接字:TCP 具备有连接、可靠传输的特点,UDP 则无连接、传输高效但单次传输最大 64k。实操层面,UDP 通过 DatagramSocket 和 DatagramPacket 完成数据收发,TCP 依托 ServerSocket 和 Socket 实现通信,还可通过多线程、线程池优化多客户端并发处理。TCP 的长短连接适配不同场景,短连接适合低频请求,长连接适配高频通信,开发时需结合协议特性规避端口占用等问题,保障网络应用的稳定性与效率。

Read more

AI入门系列:AI入门者的困惑:常见术语解释与误区澄清

AI入门系列:AI入门者的困惑:常见术语解释与误区澄清

引言 人工智能领域充满了令人困惑的专业术语和概念误区。对于刚接触AI的新手而言,机器学习、深度学习、神经网络这些名词常常让人一头雾水。很多初学者会将AI简单地等同于机器人,或者误以为AI已经具备人类水平的思维能力。实际上,AI是一个包含多个子领域的广阔学科,每个术语都有其特定的含义和应用范围。理解这些基础概念的区别,避免常见的认知误区,是踏入AI世界的第一步。本文将系统梳理AI领域的核心术语,澄清普遍存在的误解,帮助初学者建立正确的认知框架,为后续的深入学习打下坚实基础。 AI到底是什么?从科幻到现实的转变 很多人一听到AI,就想到《终结者》里的天网或者《黑客帝国》里的矩阵。但实际上,AI远比这些科幻场景要"接地气"得多。 想象一下,当你对手机说"嘿,Siri,明天天气怎么样?",手机能够理解你的话,查找天气信息,并用语音回答你。这就是AI在工作,它包含了语音识别、自然语言处理、信息检索等多个技术。 AI的本质是让机器完成那些过去只有人类才能完成的任务。但这并不意味着机器要变得像人一样思考,而是让机器在特定任务上表现得像人一样聪明。 误区澄清:

OpenClaw:让AI直接操控你的电脑

有安全风险;可接入本地大模型 1. OpenClaw 到底是什么? 你可以把它理解成:一个能直接控制你电脑的 AI 助手。 普通 AI(ChatGPT、豆包、文心一言): * 只能跟你聊天 * 只能告诉你怎么做 * 不能碰你电脑里的任何东西 OpenClaw: * 是能动手操作你电脑的 AI * 能自己点开文件、写代码、运行程序、点鼠标、改设置 * 就像雇了一个会用电脑的人,坐在你电脑前帮你干活 一句话:普通 AI 是 “嘴强王者”,OpenClaw 是 “真能干活”。 2. 它能帮你做什么?(超直白举例) 你直接用自然说话,它就能自己干: ✅ 写代码 / 改项目 * 你说:“帮我写一个登录页面” * 它自己新建文件、写代码、保存、运行 * 你不用动手敲一行 ✅ 操作电脑文件

LLaMA-Factory自定义评估指标完整实现指南

LLaMA-Factory自定义评估指标完整实现指南 在大型语言模型(LLM)微调过程中,准确评估模型性能是至关重要的环节。LLaMA-Factory作为一款功能强大的LLM微调框架,提供了灵活的评估机制,支持用户根据具体需求快速实现自定义评估指标。本文将详细介绍如何在该框架中构建完整的自定义评估流程。 评估框架核心架构解析 LLaMA-Factory的评估系统基于模块化设计,主要组件包括评估器、模板处理器和指标计算器。评估器位于src/llamafactory/eval/evaluator.py,负责整个评估流程的协调执行。模板系统定义在src/llamafactory/eval/template.py中,负责数据格式的统一处理。 现有评估机制深度分析 当前框架默认支持分类任务的准确率评估,通过比较模型预测结果与真实标签来计算性能指标。评估过程包括数据加载、模型推理、结果比较和指标计算四个主要阶段。在Evaluator类的eval方法中,可以看到核心的评估逻辑实现: # 现有准确率计算逻辑 correct_predictions = np.array(predict

6.llamafactory项目介绍与安装部署

6.llamafactory项目介绍与安装部署

一、学术资源加速 * 服务说明:AutoDL提供学术资源加速服务,主要解决GitHub和HuggingFace访问速度慢的问题,但仅限学术用途且不承诺稳定性 * 加速地址:包含github.com、githubusercontent.com、githubassets.com、huggingface.co等域名 * 终端配置: * 注意事项: * 建议不需要时关闭加速,可能影响正常网络 * 关闭命令: 二、主流微调框架介绍 1. Transformer * 生态地位:Hugging Face核心库,NLP领域最广泛使用的基础框架 * 技术特点: * 支持全参数微调 * 兼容PEFT库扩展 * 优势: * 生态系统最完善,社区活跃 * 与PyTorch/TensorFlow无缝集成 * 模型和教程资源丰富 * 适用场景:中小规模模型实验、研究和开发,微调入门首选 2. PEFT * 技术定位:参数高效微调标准库 * 核心方法: * LoRA * Prefix-tuning * AdaLoRA