一文理清TCP协议的通讯流程
目录
一、TCP通信全流程:
二、三次握手:连接建立的协同过程
客户端视角
服务端视角
为什么需要三次?从双方角度理解
三、数据传输阶段:双向数据流
客户端的数据传输
服务端的数据传输
双向通信的特点
四、四次挥手:连接释放的协同过程
主动关闭方(以客户端为例)
被动关闭方(服务端)
为什么需要四次挥手?双向释放的必要性
五、异常情况处理
连接建立异常
数据传输异常
连接释放异常
六、实际通信代码样例:
多客户端支持
使用IO多路复用
TCP通信是一个双向的过程,需要客户端和服务端的协同配合才能完成可靠的连接建立、数据传输和连接释放。本文将从通信双方的视角,完整解析TCP通信的全生命周期,深入探讨三次握手和四次挥手的必要性。
一、TCP通信全流程:
二、三次握手:连接建立的协同过程
客户端视角
-
主动打开:应用程序调用
connect()
函数 -
发送SYN:向服务器发送SYN包,序列号为x,进入
SYN_SENT
状态 -
等待响应:阻塞等待服务器的SYN-ACK响应
-
确认连接:收到SYN-ACK后发送ACK,进入
ESTABLISHED
状态
服务端视角
-
被动打开:应用程序调用
listen()
后调用accept()
阻塞等待 -
接收SYN:收到客户端的SYN包,进入
SYN_RCVD
状态 -
发送SYN-ACK:回复SYN-ACK包,序列号为y,确认号为x+1
-
等待确认:等待客户端的ACK确认,收到后进入
ESTABLISHED
状态
为什么需要三次?从双方角度理解
客户端需要确认的:
-
服务器确实存在且正在监听
-
服务器能够正常接收和发送数据
-
自己的发送能力正常(通过收到响应确认)
服务器需要确认的:
-
客户端确实想要建立连接
-
客户端能够正常接收和发送数据
-
客户端的网络地址有效且可达
如果只有两次握手的问题:
-
旧的重复SYN包可能导致服务器错误建立连接
-
服务器无法确认客户端的接收能力是否正常
-
可能造成服务器资源浪费(为无效连接分配资源)
三、数据传输阶段:双向数据流
客户端的数据传输
// 客户端发送数据
write(sockfd, request_data, request_size);// 客户端接收响应
read(sockfd, response_buffer, buffer_size);
服务端的数据传输
// 服务端接收请求
read(connfd, request_buffer, buffer_size);// 处理请求后发送响应
write(connfd, response_data, response_size);
双向通信的特点
-
全双工通信:双方可以同时发送和接收数据
-
流量控制:通过滑动窗口机制协调发送速率
-
拥塞控制:根据网络状况动态调整发送速率
-
可靠传输:通过序列号、确认和重传机制保证可靠性
四、四次挥手:连接释放的协同过程
主动关闭方(以客户端为例)
-
发送FIN:应用程序调用
close()
,发送FIN包,进入FIN_WAIT_1
状态 -
等待ACK:收到服务器的ACK后,进入
FIN_WAIT_2
状态 -
等待FIN:等待服务器的FIN包
-
发送最终ACK:收到FIN后发送ACK,进入
TIME_WAIT
状态 -
等待2MSL:等待2倍最大段生存时间后进入
CLOSED
状态
被动关闭方(服务端)
-
接收FIN:收到客户端的FIN包,进入
CLOSE_WAIT
状态 -
发送ACK:立即发送ACK确认
-
应用层处理:完成剩余数据的发送(如果有)
-
发送FIN:应用程序调用
close()
,发送FIN包,进入LAST_ACK
状态 -
等待ACK:等待客户端的ACK确认,收到后进入
CLOSED
状态
为什么需要四次挥手?双向释放的必要性
从全双工特性理解:
-
TCP连接是全双工的,有两个独立的数据通道
-
每个方向都需要单独关闭
-
一方发送FIN只表示"我不会再发送数据了",但还可以接收数据
客户端需要:
-
确保服务器收到自己的关闭请求
-
确认服务器也没有数据要发送了
-
处理可能延迟到达的数据包
服务器需要:
-
确认客户端的关闭意图
-
完成可能尚未发送完的数据
-
确保客户端知道服务器也要关闭了
TIME_WAIT状态的意义:
-
确保最后一个ACK被正确接收(如果丢失,服务器会重传FIN)
-
让旧的重复数据包在网络中消失,避免影响新连接
-
标准的2MSL等待时间(通常是2分钟)
五、异常情况处理
连接建立异常
-
SYN超时:客户端SYN未收到响应,会重试多次
-
SYN Flood攻击:恶意发送大量SYN包消耗服务器资源
数据传输异常
-
数据包丢失:通过超时重传机制解决
-
网络拥塞:通过拥塞控制算法调整发送速率
连接释放异常
-
FIN丢失:通过重传机制确保FIN被正确接收
-
机器崩溃:通过keepalive机制检测连接状态
六、实际通信代码样例:
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8080
#define BUFFER_SIZE 1024
#define BACKLOG 5void handle_client(int connfd, struct sockaddr_in client_addr) {char buffer[BUFFER_SIZE];ssize_t n;printf("客户端 %s:%d 已连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 接收和处理数据while ((n = read(connfd, buffer, sizeof(buffer) - 1)) > 0) {buffer[n] = '\0';printf("收到来自 %s:%d 的消息: %s\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buffer);// 处理请求(这里简单回显)char response[BUFFER_SIZE];snprintf(response, sizeof(response), "服务器已收到: %s", buffer);// 发送响应if (write(connfd, response, strlen(response)) < 0) {perror("write error");break;}printf("已发送响应给客户端\n");}if (n == 0) {printf("客户端 %s:%d 关闭了连接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));} else if (n < 0) {perror("read error");}close(connfd);printf("连接已关闭\n\n");
}//char buffer[] = "Hello World";
//char response[100];//snprintf(response, sizeof(response), "服务器已收到: %s", buffer);
//response的内容变为: "服务器已收到: Hello World"int main() {int listenfd, connfd;struct sockaddr_in serv_addr, client_addr;socklen_t client_len = sizeof(client_addr);// 1. 创建socketif ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}printf("Socket创建成功\n");// 设置SO_REUSEADDR选项,避免地址占用错误int opt = 1;if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {perror("setsockopt failed");close(listenfd);exit(EXIT_FAILURE);}// 2. 绑定地址和端口memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;serv_addr.sin_port = htons(PORT);if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("bind failed");close(listenfd);exit(EXIT_FAILURE);}printf("绑定端口 %d 成功\n", PORT);// 3. 开始监听if (listen(listenfd, BACKLOG) < 0) {perror("listen failed");close(listenfd);exit(EXIT_FAILURE);}printf("开始监听,等待客户端连接...\n\n");// 4. 接受和处理客户端连接while (1) {if ((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len)) < 0) {perror("accept failed");continue;}// 处理客户端连接(这里使用阻塞方式,实际应用中可用多线程或IO多路复用)handle_client(connfd, client_addr);}close(listenfd);return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024void communicate_with_server(int sockfd) {char buffer[BUFFER_SIZE];ssize_t n;printf("已连接到服务器,开始通信(输入'quit'退出)\n");while (1) {// 获取用户输入printf("请输入消息: ");fgets(buffer, sizeof(buffer), stdin);// 去除换行符buffer[strcspn(buffer, "\n")] = 0;// 检查退出条件if (strcmp(buffer, "quit") == 0) {printf("正在关闭连接...\n");break;}// 发送数据到服务器if (write(sockfd, buffer, strlen(buffer)) < 0) {perror("write failed");break;}printf("已发送: %s\n", buffer);// 接收服务器响应n = read(sockfd, buffer, sizeof(buffer) - 1);if (n < 0) {perror("read failed");break;} else if (n == 0) {printf("服务器关闭了连接\n");break;} else {buffer[n] = '\0';printf("服务器响应: %s\n", buffer);}}
}int main() {int sockfd;struct sockaddr_in serv_addr;// 1. 创建socketif ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}printf("Socket创建成功\n");// 2. 设置服务器地址memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("invalid address");close(sockfd);exit(EXIT_FAILURE);}// 3. 连接到服务器(发起三次握手)printf("正在连接到服务器 %s:%d...\n", SERVER_IP, PORT);if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("connection failed");close(sockfd);exit(EXIT_FAILURE);}printf("连接成功!\n");// 4. 与服务器通信communicate_with_server(sockfd);// 5. 关闭连接(发起四次挥手)printf("关闭socket...\n");close(sockfd);return 0;
}
运行效果:
多客户端支持
当前服务端是阻塞的,可以改为:
// 使用多线程
#include <pthread.h>
void* client_thread(void* arg) {int connfd = *(int*)arg;// 处理客户端close(connfd);free(arg);return NULL;
}// 在accept后
int* client_sock = malloc(sizeof(int));
*client_sock = connfd;
pthread_t thread;
pthread_create(&thread, NULL, client_thread, client_sock);
pthread_detach(thread);
使用IO多路复用
// 使用select/poll/epoll处理多个客户端
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(listenfd, &readfds);// 添加监控和事件处理循环
以上便是TCP通信的全过程,包括连接建立、数据传输和连接释放的所有阶段。