【JAVASE | 第十七篇】Java 网络通信
学 Java 网络通信时不要一上来就背一堆类名。先抓住一个问题一段数据怎样从一台机器上的程序发送到另一台机器上的程序一、网络通信先看三要素网络通信离不开三个信息IP、端口、协议。IP 用来定位网络中的一台主机端口用来定位这台主机上的某个应用程序协议决定双方按什么规则传输数据。常见架构可以先分成两类架构说明常见例子C/S客户端直接连接服务器桌面聊天软件、游戏客户端B/S浏览器访问服务器网页、后台管理系统不管是 C/S 还是 B/S底层都绕不开“地址 端口 协议”。代码里用本机测试时目的地址一般写成本机地址服务端监听一个固定端口客户端向这个端口发送数据。二、先用 InetAddress 认识主机地址day05-net 的 demo1inetaddress 示例先演示了 InetAddress。它不负责发送消息而是负责表示和解析主机地址。InetAddressip1InetAddress.getLocalHost();System.out.println(ip1);System.out.println(ip1.getHostAddress());System.out.println(ip1.getHostName());InetAddressip2InetAddress.getByName(www.baidu.com);System.out.println(ip2.getHostAddress());System.out.println(ip2.getHostName());System.out.println(ip2.isReachable(5000));这段代码可以拆成三件事getLocalHost 获取本机地址对象。getByName 根据域名或 IP 字符串获取目标主机地址对象。isReachable 尝试判断指定时间内目标主机是否可达。学习网络通信时InetAddress 的价值在于让“主机”变成代码里的对象。后面 UDP 和 TCP 示例中客户端都需要指定目标主机和端口本机测试时就使用 getLocalHost。三、UDP把数据封成包发出去UDP 的特点是无连接、速度快但不保证可靠到达。它适合对实时性要求高、允许少量丢失的场景比如直播、语音、广播。它的代码模型也很直观发送端把字节数组封成 DatagramPacket再通过 DatagramSocket 发出去接收端绑定端口准备缓冲区等待数据包进来。1. UDP 发送端demo2udp 的客户端代码完成了一次发送DatagramSocketsocketnewDatagramSocket();byte[]bytes你好,帅哥.getBytes();DatagramPacketpacketnewDatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),8080);socket.send(packet);socket.close();这里的关键点是 DatagramPacket 构造方法中的四个参数参数作用bytes要发送的数据bytes.length本次发送的有效字节长度InetAddress.getLocalHost()目标主机地址8080目标程序监听的端口发送端本身不需要和服务端提前建立连接。只要知道目标 IP 和端口就可以把这个数据包发出去。2. UDP 接收端服务端要先绑定端口否则客户端发到 8080 的数据没有程序接收。DatagramSocketsocketnewDatagramSocket(8080);byte[]bufnewbyte[1024*64];DatagramPacketpacketnewDatagramPacket(buf,buf.length);socket.receive(packet);intlenpacket.getLength();StringmessagenewString(buf,0,len);System.out.println(服务端收到: message);System.out.println(对方IP:packet.getAddress().getHostAddress());System.out.println(对方端口:packet.getPort());接收端的缓冲区用来装收到的数据。真正转成字符串时不应该直接把整个 buf 转掉而是要根据 packet.getLength 取本次收到的有效长度。否则缓冲区后面没被写入的空内容也会被带进去。packet 里还能拿到发送方的地址和端口这一点很重要。UDP 没有连接对象如果接收端想知道是谁发来的就要从数据包本身获取。3. UDP 多发多收demo3udp2 在一发一收的基础上加了 while 循环。客户端循环读控制台输入输入 exit 时退出ScannerscnewScanner(System.in);while(true){System.out.println(请输入:);Stringtextsc.nextLine();if(text.equals(exit)){break;}byte[]bytestext.getBytes();DatagramPacketpacketnewDatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),8080);socket.send(packet);}服务端也用 while 循环持续接收while(true){socket.receive(packet);intlenpacket.getLength();StringmessagenewString(buf,0,len);System.out.println(服务端收到: message);System.out.println(对方IP:packet.getAddress().getHostAddress());System.out.println(对方端口:packet.getPort());}这里能看出 UDP 的一个特点客户端退出只是客户端不再发送服务端不会自动结束它仍然会阻塞在 receive继续等待下一个数据包。四、TCP先建立连接再通过流传输TCP 的特点是面向连接、可靠传输。和 UDP 不同TCP 通信前必须先建立连接。在 Java 代码里客户端用 Socket 请求连接服务端用 ServerSocket 监听端口并通过 accept 等待客户端接入。连接建立后双方通过输入流和输出流收发数据。1. TCP 一发一收demo4tcp1 的客户端连接本机 9999 端口然后通过输出流发送数据SocketsocketnewSocket(InetAddress.getLocalHost(),9999);DataOutputStreamdosnewDataOutputStream(socket.getOutputStream());dos.writeInt(1);dos.writeUTF(你好);socket.close();服务端先启动 ServerSocket再调用 accept 等客户端连接ServerSocketssnewServerSocket(9999);Socketsocketss.accept();DataInputStreamdisnewDataInputStream(socket.getInputStream());intiddis.readInt();Stringmessagedis.readUTF();System.out.println(id:id 内容:message);System.out.println(客户端的ipsocket.getInetAddress());System.out.println(客户端的端口socket.getPort());这一组代码最值得注意的是读写顺序。客户端先 writeInt再 writeUTF服务端也必须先 readInt再 readUTF。TCP 传的是连续字节流不会自动理解业务含义双方必须约定好数据格式和读取顺序。2. TCP 多发多收demo5tcp2 把客户端发送放进循环里while(true){System.out.println(请说:);Stringtextsc.nextLine();if(exit.equals(text)){System.out.println(退出);dos.close();break;}dos.writeUTF(text);dos.flush();}服务端对应地循环读取while(true){Stringmessagedis.readUTF();System.out.println(内容:message);System.out.println(客户端的ipsocket.getInetAddress());System.out.println(客户端的端口socket.getPort());}这里的 flush 很关键。输出流可能有缓冲如果写完后迟迟不刷新接收端可能一直读不到数据。多发多收时写一次消息后及时刷新能让服务端更快拿到本次输入。3. TCP 支持多个客户端demo5tcp2 的服务端只能处理一个客户端。因为 accept 接到一个连接后当前线程就进入读取循环无法回到 accept 接下一个连接。demo6tcp3 用多线程解决这个问题while(true){Socketsocketss.accept();System.out.println(一个客户端上线了socket.getInetAddress().getHostAddress());newServerReader(socket).start();}每接入一个客户端就创建一个 ServerReader 线程专门负责读取这个客户端的消息publicclassServerReaderextendsThread{privateSocketsocket;publicServerReader(Socketsocket){this.socketsocket;}Overridepublicvoidrun(){try{DataInputStreamdisnewDataInputStream(socket.getInputStream());while(true){Stringmessagedis.readUTF();System.out.println(收到的客户端message:message);System.out.println(客户端的IPsocket.getInetAddress().getHostAddress());System.out.println(客户端的端口socket.getPort());}}catch(Exceptione){e.printStackTrace();}}}主线程只负责不断接收新连接子线程负责和某一个客户端持续通信。这就是一个很基础的 TCP 多客户端模型。五、UDP 和 TCP 怎么选UDP 和 TCP 的区别可以从“有没有连接”和“可靠性”两条线来记。对比点UDPTCP是否连接不需要连接必须先建立连接数据形式数据包字节流可靠性不保证可靠到达可靠传输接收方式接收一个个数据包从连接流中持续读取常见场景直播、语音、广播文件传输、登录、支付选择时可以问自己三个问题这条数据丢了能不能接受双方是否需要维持一个稳定连接数据顺序和完整性是不是必须保证如果答案偏向“必须可靠、必须按顺序、不能丢”优先考虑 TCP。如果答案偏向“实时性更重要偶尔丢一点可以接受”UDP 更合适。六、运行验证和常见排查这些示例都可以按“先服务端后客户端”的顺序验证。UDP 验证先运行 demo2udp 或 demo3udp2 中的服务端让它绑定 8080。再运行对应客户端。如果服务端打印消息、发送方 IP 和端口说明数据包已经收到。TCP 验证先运行服务端让它监听 9999。再运行客户端发起连接。如果服务端打印客户端消息、IP 和端口说明连接和读取都正常。常见问题可以按下面顺序排查现象优先检查客户端连接失败服务端是否先启动端口是否一致服务端没有收到 UDP 消息接收端端口是否绑定为 8080发送目标地址是否正确TCP 服务端一直卡住accept 或读取方法本来就是阻塞等待先确认客户端是否真的连接或发送读取内容错乱DataOutputStream 和 DataInputStream 的写入、读取顺序是否一致多客户端只能连一个服务端是否为每个 Socket 单独开线程命令行运行客户端失败检查 main 方法是否声明为 public static void main这里最后一点来自当前代码细节部分 ClientDemo1 写成了 static void main。如果用命令行 java 直接运行标准入口方法需要 public static void main。学习时不一定要急着改代码但排查运行失败时要先看这个位置。七、总结网络通信入门可以按一条链路理解IP 找主机端口找程序协议决定传输方式。UDP 是把字节封进数据包直接发写法简单但不保证可靠TCP 是先建立连接再通过输入输出流稳定传输可靠性更强也需要处理连接、阻塞、读写顺序和多客户端问题。以后再遇到网络通信代码可以先判断它在解决哪一层问题是在找地址、绑定端口、封装数据包还是建立连接、读写字节流。把这条线分清InetAddress、DatagramSocket、DatagramPacket、Socket、ServerSocket 这些类就不会乱成一团。参考资料Oracle Java SE 26 APIjava.net 包https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/package-summary.htmlOracle Java SE 26 APIDatagramSockethttps://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/DatagramSocket.htmlOracle Java SE 26 APIDatagramPackethttps://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/DatagramPacket.htmlOracle Java SE 26 APISockethttps://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/Socket.htmlOracle Java SE 26 APIServerSockethttps://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/ServerSocket.html