Skip to content

4.说说 TCP 为什么需要三次握⼿和四次挥⼿?

1.建立连接

TCP 三次握手是在建立 TCP 连接时需要进行的过程,用于确认双方的接收能力和发送能力是否正常。目的:确保连接是双向的。

conenct

  • 第一次握手:客户端发送一个 SYN 包,指定自己的初始序列号 seq=0,此时客户端处于 SYN_SENT 状态。
  • 第二次握手:服务端收到客户端的 SYN 包后,以自己的 SYN 包作为应答,确认客户端的 SYN,并将客户端的 seq+1 作为 ACK 的值,此时服务端处于 SYN_RCVD 状态。
  • 第三次握手:客户端收到服务端的 SYN 包后,发送一个 ACK 包,值为服务端的 seq+1。此时客户端处于 ESTABLISHED 状态,服务端也处于 ESTABLISHED 状态,双方连接建立完成。

通过三次握手,双方确认了彼此的接收和发送能力正常,可以进行正常的数据传输。

2.数据传输

push

3.断开连接

服务端在收到客户端断开连接 FIN 包后,并不会立即关闭连接,而是先发送一个 ACK 包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送 FIN 包断开连接,因此需要四次挥手。

closed

  • 第一次挥手:客户端发送一个 FIN 包,指定一个序列号。此时客户端处于 FIN_WAIT1 状态,停止发送数据,等待服务端的确认。
  • 第二次挥手:服务端收到 FIN 包后,发送一个 ACK 包,且将客户端的序列号值+1 作为 ACK 包的序列号值,表明已经收到客户端的 FIN 包。此时服务端处于 CLOSE_WAIT 状态。
  • 第三次挥手:如果服务端也想断开连接,服务端发送一个 FIN 包,指定一个序列号。此时服务端处于 LAST_ACK 状态。
  • 第四次挥手:客户端收到服务端的 FIN 包后,发送一个 ACK 包作为应答,且将服务端的序列号值+1 作为 ACK 包的序列号值,此时客户端处于 TIME_WAIT 状态。需要等待一段时间以确保服务端收到自己的 ACK 包之后才会进入 CLOSED 状态,服务端收到 ACK 包后,就处于关闭连接了,处于 CLOSED 状态。

为了防止最终的ACK丢失,发送ACK后需要等待一段时间,因为如果丢包服务端需要重新发送FIN包,如果客户端已经closed,那么服务端会将结果解析成错误。 通常需要等待 2 倍的 MSL(Maximum Segment Lifetime),才可以关闭。因此在高并发非长连接的场景下会有大量端口被占用。

4.测试代码

client.js

js
const net = require("net");
const socket = new net.Socket();
// 连接8080端口
socket.connect(8080, "localhost");
// 连接成功后给服务端发送消息
socket.on("connect", function (data) {
  socket.write("hello"); // 浏览器和客户端说 hello
  socket.end();
});
socket.on("data", function (data) {
  console.log(data.toString());
});
socket.on("error", function (error) {
  console.log(error);
});
const net = require("net");
const socket = new net.Socket();
// 连接8080端口
socket.connect(8080, "localhost");
// 连接成功后给服务端发送消息
socket.on("connect", function (data) {
  socket.write("hello"); // 浏览器和客户端说 hello
  socket.end();
});
socket.on("data", function (data) {
  console.log(data.toString());
});
socket.on("error", function (error) {
  console.log(error);
});

server.js

js
const net = require("net");
const server = net.createServer(function (socket) {
  socket.on("data", function (data) {
    // 客户端和服务端
    socket.write("hi"); // 服务端和客户端说 hi
  });
  socket.on("end", function () {
    console.log("客户端关闭");
  });
});
server.on("error", function (err) {
  console.log(err);
});
server.listen(8080); // 监听8080端口
const net = require("net");
const server = net.createServer(function (socket) {
  socket.on("data", function (data) {
    // 客户端和服务端
    socket.write("hi"); // 服务端和客户端说 hi
  });
  socket.on("end", function () {
    console.log("客户端关闭");
  });
});
server.on("error", function (err) {
  console.log(err);
});
server.listen(8080); // 监听8080端口

Released under the MIT License.