目的:
建立客户端与服务端之间的双向传输通道,确保双方都能确认对方的接收和发送能力,为后续的数据传输奠定可靠基础。
流程:
客户端发送 SYN
客户端发送 SYN 报文,请求建立连接,并包含初始序列号(SEQ),此时客户端进入 SYN_SENT 状态。
服务端回应 SYN-ACK
服务端收到 SYN 后,回应 SYN-ACK,其中 ACK 为客户端 SYN 的确认,ACK 号为客户端 SEQ+1。服务端也发送自己的 SYN,包含自己的初始序列号,此时服务端进入 SYN_RCVD 状态。
客户端发送 ACK
客户端收到 SYN-ACK 后,发送 ACK 报文,确认号为服务端的 SEQ+1,双方进入 ESTABLISHED 状态,连接建立成功。
关键状态:
listen()
后进入该状态,等待客户端连接。目的:
安全拆除双向传输通道,确保双方都能完成数据的发送和接收,避免数据丢失。
流程:
(假设主动关闭方为 A,被动方为 B)
A 发送 FIN
A 发送 FIN,表示没有数据要发送,进入 FIN_WAIT_1 状态。
B 回复 ACK
B 回复 ACK,确认收到 A 的关闭请求,进入 CLOSE_WAIT 状态。
B 发送 FIN
B 完成数据发送后,发送 FIN,请求关闭 B 到 A 的通道,进入 LAST_ACK 状态。
A 回复 ACK
A 回复 ACK,确认 B 的关闭请求,进入 TIME_WAIT 状态,等待一段时间后才能真正关闭连接。
关键状态:
listen()
后进入监听状态,等待客户端的连接请求。命令:
netstat -na | grep <端口号>
该命令列出特定端口的连接状态。例如,查看端口 8080 的连接状态:
netstat -na | grep 8080
关键列说明:
bind()
指定端口(如 HTTP 协议使用 80 端口)。backlog(已连接队列大小):
指定 listen()
的第二个参数,影响全连接队列的大小,默认值通常为 128,但可以根据实际需要增大。
示例:
listen(sockfd, 1024); // 设置队列长度为 1024
问题:
主动关闭方在 TIME_WAIT
状态期间无法重用端口,可能导致高并发场景中的端口资源浪费。
解决方案:
设置 SO_REUSEADDR
属性来允许端口复用:
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
FIN
。攻击方式:
攻击者伪造大量源 IP 地址,发送大量 SYN
报文,消耗服务端资源。
防御方法:
启用 SYN Cookie 机制来防御该攻击。
FIN
。#include
#include
#include
#include
#include
#include
#define PORT 8080
#define BACKLOG 1024
int main() {
int sockfd, new_sockfd;
struct sockaddr_in addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int opt = 1;
// 创建 socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置端口复用
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
perror("setsockopt failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 绑定端口
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(sockfd, BACKLOG) == -1) {
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
// 接受客户端连接
new_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (new_sockfd == -1) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Client connected.\n");
// 处理客户端请求
close(new_sockfd);
close(sockfd);
return 0;
}
使用 ab 工具测试并发连接:
ab -n 1000 -c 100 http://localhost:8080/
理解三次握手和四次挥手是掌握 TCP 协议的基础,通过结合编程实践与网络状态分析工具(如 netstat
),可以有效排查连接问题并优化高并发场景下的服务端性能。对常见网络攻击(如 SYN 泛洪攻击)和连接状态(如 TIME_WAIT
、CLOSE_WAIT
)的理解和处理是网络编程和系统运维中不可或缺的技能。