Java 面向对象 · 交互课程

网络编程(进阶)

网络编程(进阶)

目标:理解两台机器之间如何通信;掌握 Socket / ServerSocket 与输入/输出流收发数据的基本模型;会用 Java 写出最简的 client / server,理解 TCP 三次握手与「序列化对象」在网络里的落地。

本模块是进阶补充(原 32 课次无独立网络课),覆盖复习提纲「六、网络编程基础:socket / client / server」。贯穿示例用网络版 ERASE(素材/Erase-JAVA-NET/)——单机版俄罗斯方块的对战版,客户端与服务端各自起线程并发收发。

一、为什么要学网络编程:两台机器如何通信

一句话:服务端「守株待兔」,客户端「主动上门」,握手成功后双向传数据。

二、TCP/IP 与 Socket 基础

传输层两大协议:

Socket 编程四件套(本模块的核心 API):

角色关键类 / 方法作用
服务端·监听new ServerSocket(port)绑定端口并进入监听,等待客户端接入
服务端·接客serverSocket.accept()阻塞等待;每来一个客户端返回一个专属 Socket
客户端·连接new Socket(host, port)向服务端发起 TCP 连接(背后是三次握手)
双方·收发getInputStream() / getOutputStream()输入流收数据、输出流发数据(配合序列化)

三、Socket 握手与数据帧

一次完整的 Socket 通信遵循固定五步:① 监听 → ② 连接 → ③ 通道 → ④ 收发 → ⑤ 关闭。下面按时序逐步高亮每条消息,看 TCP 三次握手如何建立连接、序列化数据帧如何在两端之间双向流动:

交互实验:Socket 客户端 / 服务端通信(SocketClientServerSim)

网络 · TCP

两台机器要通信,靠的是 Socket:服务端 new ServerSocket(port) 监听端口、accept() 接客;客户端 new Socket(host, port) 发起连接(背后是 TCP 三次握手);双方再用输入/输出流收发序列化对象。下面按 「监听 → 连接 → 通道 → 收发 → 关闭」逐步高亮每条消息:

① 监听② 连接③ 通道④ 收发⑤ 关闭
第 1 / 9 步 · ① 监听
new ServerSocket(9001)—— 绑定端口 9001 并进入监听;accept() 阻塞,等待有客户端接入。
客户端 Clientnew Socket(host, port)服务端 Servernew ServerSocket(port)new ServerSocket(9001)new SocketserverSocket.accept()writeObjectis.readObject()writeObjectis.readObject()cs.close()cs.close()
TCP 三次握手(连接建立)
1. SYN客户端 → 服务端
seq = x

客户端 → 服务端:请求建立连接,携带随机初始序号 x。

2. SYN-ACK服务端 → 客户端
seq = y, ack = x+1

服务端 → 客户端:同意建连,给出自己的序号 y,并确认收到 x(回 x+1)。

3. ACK客户端 → 服务端
ack = y+1

客户端 → 服务端:确认收到 y(回 y+1)。三次握手完成,连接正式建立。

一帧数据 = 一个序列化对象(PlayInfo)

客户端每帧把 PlayInfo(玩家名 + 分数/等级/速度 + 棋盘状态)用 writeObject() 序列化成字节发出去,对端 readObject() 再还原——这就是模块6 学的序列化在网络里的落地。

字段值(示例)
namezm23
score1280
level5
speed400
board[41×22]棋盘状态…

最小可运行 client / server(改编自网络版 ERASE)

Server.java(服务端)
// 服务端:监听端口,为每个客户端起一个线程收发数据
ServerSocket ss = new ServerSocket(9001);   // 绑定端口并进入监听
while (true) {
    Socket cs = ss.accept();                // 阻塞等待客户端接入
    new Thread(() -> {                      // 每客户端一个线程(模块7)
        ObjectInputStream is = new ObjectInputStream(cs.getInputStream());
        ObjectOutputStream os = new ObjectOutputStream(cs.getOutputStream());
        while (true) {
            PlayInfo info = (PlayInfo) is.readObject();   // 读数据帧
            os.writeObject(handle(info));                 // 回发数据帧
            os.flush();
        }
    }).start();
}
Client.java(客户端)
// 客户端:连接服务端,收发序列化数据
Socket cs = new Socket("localhost", 9001);   // TCP 三次握手建立连接
ObjectOutputStream os = new ObjectOutputStream(cs.getOutputStream());
ObjectInputStream is = new ObjectInputStream(cs.getInputStream());

PlayInfo me = buildMyInfo();      // 组装自己的操作/状态
os.writeObject(me);               // 发数据帧(序列化)
os.flush();
PlayInfo fellow = (PlayInfo) is.readObject();  // 读回发来的数据帧
cs.close();

Socket / 网络要点

  • Socket 通信三件套:Server 端 `new ServerSocket(port)` 监听 + `accept()` 拿专属 Socket;Client 端 `new Socket(host, port)` 发起连接;双方用 `getInputStream()` / `getOutputStream()` 收发字节。
  • TCP 是面向连接的可靠传输:`new Socket(...)` 背后是三次握手(SYN → SYN-ACK → ACK),建连后数据按序、不丢、可重传——适合聊天/对战这类要保序保真的场景(UDP 则相反,见 C2 对比)。
  • Java 里收发的是「序列化对象」:`writeObject()` 把对象转成字节流发出,`readObject()` 再还原成对象(Erase-NET 每帧发的就是 `PlayInfo`)——模块6 的序列化在网络里的落地。
  • 每个客户端对应服务端一个线程(`ServThreadPerClient`):`accept()` 返回后立刻起线程处理该客户端的收发——多线程 + 网络结合,正是模块7 的知识落到真实项目。

TCP 三次握手的每一步。 上面 new Socket(host, port) 背后正是 TCP 三次握手:客户端发 SYN、服务端回 SYN-ACK、客户端再发 ACK——三步走完,两端才同时进入 ESTABLISHED;若目标端口没有服务在监听,服务端回 RST(复位),连接被拒绝。下面逐步走查三个报文如何在 client/server 间往返、两端状态机如何同步迁移(含 RST 异常分支):

交互实验:TCP 三次握手动画(ThreeWayHandshake)

new Socket(host, port) 背后是 TCP 三次握手:客户端发 SYN、服务端回 SYN-ACK、 客户端再发 ACK,三步走完两端才进入 ESTABLISHED;若服务端拒绝则回 RST 复位。点「下一步」看报文往返与状态机同步迁移。

第 1/5 步

客户端(client)

IDLE↓
SYN_SENT↓
ESTABLISHED↓
CLOSED
(无在途报文)

服务端(server)

LISTEN↓
SYN_RCVD↓
ESTABLISHED↓
CLOSED

初始

客户端尚未发起连接;服务端已 bind + listen,处于监听态等待接入。

四、TCP vs UDP:可靠有序 vs 快而无序

上面实验全程用的是 TCP。但传输层其实有两位选手——TCP 和 UDP,它们不是「谁更好」的关系,而是「各管一类场景」:

七维度并排对比 + 场景选型练习(点场景看推荐协议)+ 两套 Java API 代码对照:

交互实验:TCP vs UDP 并排对比(TCPvsUDPCompare)

同一条传输层,两种性格——点击任意维度行,看两者的具体表现与一句话点评:

← 点击任意维度行查看点评

选型练习:这个场景该用哪个协议?

← 点一个场景,看推荐协议与理由

TCP:ServerSocket / Socket + 流
// TCP:先建连,可靠有序
ServerSocket server = new ServerSocket(9999);
Socket socket = server.accept();      // 三次握手完成
OutputStream out = socket.getOutputStream();
out.write("Hello".getBytes());        // 按序到达、保证送达
UDP:DatagramSocket + 数据报
// UDP:不建连,直接发
DatagramSocket socket = new DatagramSocket(9999);
byte[] buf = "Hi".getBytes();
socket.send(new DatagramPacket(buf, buf.length,
    InetAddress.getByName("localhost"), 8888)); // 发了就不管
byte[] rbuf = new byte[1024];
DatagramPacket rp = new DatagramPacket(rbuf, rbuf.length);
socket.receive(rp);   // 一次 receive() = 一个完整数据报
七维度汇总:TCP 占优 2 项(可靠性、有序性)UDP 占优 4 项(免握手、速度、组播、消息边界)平手 1 项(典型场景:各有所长)
  • •TCP = 面向连接、可靠、有序、字节流;UDP = 无连接、不保证、无序、数据报。
  • •选型原则:保序保真(网页/聊天/对战)→ TCP;速度实时、可容忍丢包(音视频/直播/DNS)→ UDP。
  • •Java API 两套完全分开:TCP 用 ServerSocket / Socket + 输入输出流;UDP 用 DatagramSocket + DatagramPacket 的 send() / receive()。
  • •真实项目里两者可以并存:例如对战用 TCP 同步状态(可靠),同时用 UDP 同步低延迟的位置信息。

一句话选型:要保序保真(网页、聊天、对战)→ TCP;要速度实时、丢一帧没关系(音视频、直播、DNS)→ UDP。

五、把两个程序跑起来(贯穿示例:网络版 ERASE)

最简的 client / server 只要几十行——上面实验里已给出可直接运行的 Server.java / Client.java。真实项目网络版 ERASE(素材/Erase-JAVA-NET/src/com/zmofun/neterase/)把它们放大成完整对战:

思政落点:网络编程让「个人借助平台提升能力」——每个客户端是独立个体,服务端是共享平台;遵守协议(TCP/序列化格式)才能协作无间。

六、URL 与 WebService 概览

七、动手实验:在浏览器里编译运行 Java(CodePlayground)

前面都是「看」网络,这一节反过来——直接在本页写一段 Java,让后端真实地 javac 编译 + java 运行,再把输出送回你的浏览器。这正是第一节 client / server 模型的落地版:

交互实验:浏览器里编译运行 Java(CodePlayground)

未连接

左边写 Java,点「运行」——源码经 WebSocket+STOMP 发给后端(端口 8080),真实javac编译 + java 运行,输出推回你的浏览器。先选一个预置程序试试:

Main.java(可编辑)6 行 · 0.2 KiB / 64 KiB
结果

← 点「运行」看编译/运行结果

💡 最小可运行程序:UTF-8 中文输出 + 表达式求值。

幕后流程:浏览器 →(WebSocket ws://<host>:8080/ws)→ 后端 STOMP /app/run → 沙箱编译运行 → 结果推回你的会话专属队列 /queue/result-<你的id>。每个浏览器标签页只收自己的结果,互不串台。

沙箱安全说明(C4):每次运行都放在独立临时目录(用完即删)、子进程内存上限 256 MB、编译/运行各自 10 秒超时后强杀整棵进程树——所以「死循环」预置不会把服务端拖死,只会收到 timeout。

八、总结

思考题

  1. 为什么说 TCP 是「面向连接」的?三次握手的三步分别由谁发起、目的是什么?
  2. 服务端为什么通常要为每个客户端起一个线程?如果只开一个线程串行处理所有客户端,会出现什么问题?
  3. Java 通过 Socket 发送一个对象时为什么能直接 writeObject(obj) 而不是先手动转成字符串?(提示:序列化)

学习小测验

学习小测验

Q1

在 Java Socket 编程中,负责监听端口、等待客户端接入的是哪个类?

Q2

服务端调用 serverSocket.accept() 的作用是( )

Q3

TCP 建立连接时的三次握手顺序是( )

Q4

Java 里通过 Socket 发送一个对象时,应使用( )

Q5

网络版 ERASE 里,服务端为每个接入的客户端各起一个线程(ServThreadPerClient)处理收发,这主要体现了哪个模块的知识?

Q6

客户端要向服务端发送数据,应使用( )

Q7

开发实时视频通话应用时,传输层协议更适合选( )

Q8

TCP 能保证数据按发送顺序到达接收方,靠的是( )

Q9

CodePlayground 前端把 Java 源码提交给后端编译运行、并接收回传的结果,采用的传输方式是( )

Q10

在 CodePlayground 里提交一段含 for(;;){} 死循环的程序,沙箱最可能返回的状态是( )

Q11

TCP 三次握手中,第三步由客户端发送的报文是( )

Q12

客户端向一个没有服务在监听的端口发起连接,服务端最可能回( )