当前位置: 首页 > news >正文

一文理清TCP协议的通讯流程

       

目录

一、TCP通信全流程:

二、三次握手:连接建立的协同过程

客户端视角

服务端视角

为什么需要三次?从双方角度理解

三、数据传输阶段:双向数据流

客户端的数据传输

服务端的数据传输

双向通信的特点

四、四次挥手:连接释放的协同过程

主动关闭方(以客户端为例)

被动关闭方(服务端)

为什么需要四次挥手?双向释放的必要性

五、异常情况处理

连接建立异常

数据传输异常

连接释放异常

六、实际通信代码样例:

多客户端支持

使用IO多路复用


        TCP通信是一个双向的过程,需要客户端和服务端的协同配合才能完成可靠的连接建立、数据传输和连接释放。本文将从通信双方的视角,完整解析TCP通信的全生命周期,深入探讨三次握手和四次挥手的必要性。

一、TCP通信全流程:

二、三次握手:连接建立的协同过程

客户端视角

  1. 主动打开:应用程序调用connect()函数

  2. 发送SYN:向服务器发送SYN包,序列号为x,进入SYN_SENT状态

  3. 等待响应:阻塞等待服务器的SYN-ACK响应

  4. 确认连接:收到SYN-ACK后发送ACK,进入ESTABLISHED状态

服务端视角

  1. 被动打开:应用程序调用listen()后调用accept()阻塞等待

  2. 接收SYN:收到客户端的SYN包,进入SYN_RCVD状态

  3. 发送SYN-ACK:回复SYN-ACK包,序列号为y,确认号为x+1

  4. 等待确认:等待客户端的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);

双向通信的特点

  1. 全双工通信:双方可以同时发送和接收数据

  2. 流量控制:通过滑动窗口机制协调发送速率

  3. 拥塞控制:根据网络状况动态调整发送速率

  4. 可靠传输:通过序列号、确认和重传机制保证可靠性

四、四次挥手:连接释放的协同过程

主动关闭方(以客户端为例)

  1. 发送FIN:应用程序调用close(),发送FIN包,进入FIN_WAIT_1状态

  2. 等待ACK:收到服务器的ACK后,进入FIN_WAIT_2状态

  3. 等待FIN:等待服务器的FIN包

  4. 发送最终ACK:收到FIN后发送ACK,进入TIME_WAIT状态

  5. 等待2MSL:等待2倍最大段生存时间后进入CLOSED状态

被动关闭方(服务端)

  1. 接收FIN:收到客户端的FIN包,进入CLOSE_WAIT状态

  2. 发送ACK:立即发送ACK确认

  3. 应用层处理:完成剩余数据的发送(如果有)

  4. 发送FIN:应用程序调用close(),发送FIN包,进入LAST_ACK状态

  5. 等待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通信的全过程,包括连接建立、数据传输和连接释放的所有阶段。

http://www.xdnf.cn/news/1410877.html

相关文章:

  • 【车载开发系列】CAN与CANFD下篇
  • Linux-驱动积累
  • docker安装tomcat
  • 1.2 操作系统发展历程
  • dify docker compose操作命令指南
  • 【不懂就问】-手机相关学习
  • 内核等待队列以及用户态的类似机制
  • 基于Spring Cloud Sleuth与Zipkin的分布式链路追踪实战指南
  • 机器学习基础-day01-机器学习介绍
  • syn与quote的简单使用——实现debug
  • 萌宝喂养日志-我用AI做喂养记录小程序1-原型设计
  • 中科大少年班记
  • 编程与数学 03-004 数据库系统概论 10_数据库的实施
  • 【GaussDB】排查应用高可用切换出现数据库整体卡顿及报错自治事务无法创建的问题
  • 基于JavaScript的智能合约平台(Agoric)
  • join怎么用
  • Spring Boot单体项目整合Nacos
  • STAR法则
  • C/C++ 高阶数据结构 —— 二叉搜索树(二叉排序树)
  • 【Linux】系统部分——ELF文件格式与动态库加载
  • 【系统分析师】高分论文:论大数据架构的应用
  • Linux系统比较两个​​已排序文件​​的实用工具之comm
  • 混合润滑表面接触刚度和接触阻尼模型
  • 计算机视觉与深度学习 | 低照度图像处理算法综述:发展、技术与趋势
  • ESP32_实验12_基于光敏传感器的停车场车辆计数系统
  • LeetCode 1855.下标对中的最大距离
  • 基于Python的OCR文字识别系统
  • More Effective C++ 条款19:理解临时对象的来源(Understand the Origin of Temporary Objects)
  • 地信/测绘/遥感就业岗位合集
  • Vue2 与 Vue3 路由钩子的区别及用法详解