【JAVASE | 第十七篇】Java 网络通信
2026/6/10 9:41:18 网站建设 项目流程

学 Java 网络通信时,不要一上来就背一堆类名。先抓住一个问题:一段数据怎样从一台机器上的程序,发送到另一台机器上的程序?

一、网络通信先看三要素

网络通信离不开三个信息:IP、端口、协议。

IP 用来定位网络中的一台主机;端口用来定位这台主机上的某个应用程序;协议决定双方按什么规则传输数据。

常见架构可以先分成两类:

架构说明常见例子
C/S客户端直接连接服务器桌面聊天软件、游戏客户端
B/S浏览器访问服务器网页、后台管理系统

不管是 C/S 还是 B/S,底层都绕不开“地址 + 端口 + 协议”。代码里用本机测试时,目的地址一般写成本机地址,服务端监听一个固定端口,客户端向这个端口发送数据。

二、先用 InetAddress 认识主机地址

day05-net 的 demo1inetaddress 示例先演示了 InetAddress。它不负责发送消息,而是负责表示和解析主机地址。

InetAddressip1=InetAddress.getLocalHost();System.out.println(ip1);System.out.println(ip1.getHostAddress());System.out.println(ip1.getHostName());InetAddressip2=InetAddress.getByName("www.baidu.com");System.out.println(ip2.getHostAddress());System.out.println(ip2.getHostName());System.out.println(ip2.isReachable(5000));

这段代码可以拆成三件事:

  1. getLocalHost 获取本机地址对象。
  2. getByName 根据域名或 IP 字符串获取目标主机地址对象。
  3. isReachable 尝试判断指定时间内目标主机是否可达。

学习网络通信时,InetAddress 的价值在于让“主机”变成代码里的对象。后面 UDP 和 TCP 示例中,客户端都需要指定目标主机和端口,本机测试时就使用 getLocalHost。

三、UDP:把数据封成包发出去

UDP 的特点是无连接、速度快,但不保证可靠到达。

它适合对实时性要求高、允许少量丢失的场景,比如直播、语音、广播。它的代码模型也很直观:发送端把字节数组封成 DatagramPacket,再通过 DatagramSocket 发出去;接收端绑定端口,准备缓冲区,等待数据包进来。

1. UDP 发送端

demo2udp 的客户端代码完成了一次发送:

DatagramSocketsocket=newDatagramSocket();byte[]bytes="你好,帅哥".getBytes();DatagramPacketpacket=newDatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),8080);socket.send(packet);socket.close();

这里的关键点是 DatagramPacket 构造方法中的四个参数:

参数作用
bytes要发送的数据
bytes.length本次发送的有效字节长度
InetAddress.getLocalHost()目标主机地址
8080目标程序监听的端口

发送端本身不需要和服务端提前建立连接。只要知道目标 IP 和端口,就可以把这个数据包发出去。

2. UDP 接收端

服务端要先绑定端口,否则客户端发到 8080 的数据没有程序接收。

DatagramSocketsocket=newDatagramSocket(8080);byte[]buf=newbyte[1024*64];DatagramPacketpacket=newDatagramPacket(buf,buf.length);socket.receive(packet);intlen=packet.getLength();Stringmessage=newString(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 时退出:

Scannersc=newScanner(System.in);while(true){System.out.println("请输入:");Stringtext=sc.nextLine();if(text.equals("exit")){break;}byte[]bytes=text.getBytes();DatagramPacketpacket=newDatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),8080);socket.send(packet);}

服务端也用 while 循环持续接收:

while(true){socket.receive(packet);intlen=packet.getLength();Stringmessage=newString(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 端口,然后通过输出流发送数据:

Socketsocket=newSocket(InetAddress.getLocalHost(),9999);DataOutputStreamdos=newDataOutputStream(socket.getOutputStream());dos.writeInt(1);dos.writeUTF("你好");socket.close();

服务端先启动 ServerSocket,再调用 accept 等客户端连接:

ServerSocketss=newServerSocket(9999);Socketsocket=ss.accept();DataInputStreamdis=newDataInputStream(socket.getInputStream());intid=dis.readInt();Stringmessage=dis.readUTF();System.out.println("id:"+id+" 内容:"+message);System.out.println("客户端的ip"+socket.getInetAddress());System.out.println("客户端的端口"+socket.getPort());

这一组代码最值得注意的是读写顺序。客户端先 writeInt,再 writeUTF;服务端也必须先 readInt,再 readUTF。TCP 传的是连续字节流,不会自动理解业务含义,双方必须约定好数据格式和读取顺序。

2. TCP 多发多收

demo5tcp2 把客户端发送放进循环里:

while(true){System.out.println("请说:");Stringtext=sc.nextLine();if("exit".equals(text)){System.out.println("退出");dos.close();break;}dos.writeUTF(text);dos.flush();}

服务端对应地循环读取:

while(true){Stringmessage=dis.readUTF();System.out.println("内容:"+message);System.out.println("客户端的ip"+socket.getInetAddress());System.out.println("客户端的端口"+socket.getPort());}

这里的 flush 很关键。输出流可能有缓冲,如果写完后迟迟不刷新,接收端可能一直读不到数据。多发多收时,写一次消息后及时刷新,能让服务端更快拿到本次输入。

3. TCP 支持多个客户端

demo5tcp2 的服务端只能处理一个客户端。因为 accept 接到一个连接后,当前线程就进入读取循环,无法回到 accept 接下一个连接。

demo6tcp3 用多线程解决这个问题:

while(true){Socketsocket=ss.accept();System.out.println("一个客户端上线了"+socket.getInetAddress().getHostAddress());newServerReader(socket).start();}

每接入一个客户端,就创建一个 ServerReader 线程专门负责读取这个客户端的消息:

publicclassServerReaderextendsThread{privateSocketsocket;publicServerReader(Socketsocket){this.socket=socket;}@Overridepublicvoidrun(){try{DataInputStreamdis=newDataInputStream(socket.getInputStream());while(true){Stringmessage=dis.readUTF();System.out.println("收到的客户端message:"+message);System.out.println("客户端的IP"+socket.getInetAddress().getHostAddress());System.out.println("客户端的端口"+socket.getPort());}}catch(Exceptione){e.printStackTrace();}}}

主线程只负责不断接收新连接;子线程负责和某一个客户端持续通信。这就是一个很基础的 TCP 多客户端模型。

五、UDP 和 TCP 怎么选

UDP 和 TCP 的区别可以从“有没有连接”和“可靠性”两条线来记。

对比点UDPTCP
是否连接不需要连接必须先建立连接
数据形式数据包字节流
可靠性不保证可靠到达可靠传输
接收方式接收一个个数据包从连接流中持续读取
常见场景直播、语音、广播文件传输、登录、支付

选择时可以问自己三个问题:

  1. 这条数据丢了能不能接受?
  2. 双方是否需要维持一个稳定连接?
  3. 数据顺序和完整性是不是必须保证?

如果答案偏向“必须可靠、必须按顺序、不能丢”,优先考虑 TCP。如果答案偏向“实时性更重要,偶尔丢一点可以接受”,UDP 更合适。

六、运行验证和常见排查

这些示例都可以按“先服务端,后客户端”的顺序验证。

UDP 验证:

  1. 先运行 demo2udp 或 demo3udp2 中的服务端,让它绑定 8080。
  2. 再运行对应客户端。
  3. 如果服务端打印消息、发送方 IP 和端口,说明数据包已经收到。

TCP 验证:

  1. 先运行服务端,让它监听 9999。
  2. 再运行客户端发起连接。
  3. 如果服务端打印客户端消息、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 API:java.net 包
    https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/package-summary.html
  • Oracle Java SE 26 API:DatagramSocket
    https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/DatagramSocket.html
  • Oracle Java SE 26 API:DatagramPacket
    https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/DatagramPacket.html
  • Oracle Java SE 26 API:Socket
    https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/Socket.html
  • Oracle Java SE 26 API:ServerSocket
    https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/net/ServerSocket.html

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询