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

TCP三次握手、四次挥手+多线程并发处理

目录

一、三次握手建立连接

1.1 标记位

1.2 三次握手的过程

二、四次挥手断开连接

 三、模拟服务器和客户端收发数据

四、多线程并发处理

五、TCP粘包问题

5.1 什么是TCP粘包?

5.2 TCP粘包会有什么问题?

5.3 TCP粘包的解决方法?


一、三次握手建立连接

1.1 标记位

        SYN:用于建立连接的初始握手。发送方发送一个SYN报文段给接收方,请求建立连接。

        ACK:用于确认数据的传输。当成功接收到数据后,接收方发送一个带有ACK标记的报文段回复发送方,确认已经收到了数据。

        FIN:用于关闭连接。当发送方发送完所有数据后,会发送一个带有FIN标记的报文段,请求关闭连接。接收方在收到FIN报文段后,发送一个带有ACK标记的报文段进行确认,并使用一个定时器在一段时间后关闭连接。

1.2 三次握手的过程

三次握手是TCP协议中用于建立连接的过程,具体步骤如下:

  1. 客户端向服务器发送一个SYN(同步)的数据包,表示客户端请求建立连接。该数据包中还包含客户端随机生成的初始序列号(Sequence Number,seq),比如 seq = x。
  2. 服务器收到客户端发送的SYN数据包后,向客户端发送一个ACK(确认)和SYN的组合数据包,服务器将自己的初始序列号(seq = y)发送给客户端,同时将客户端的序列号加 1 作为确认号(Acknowledgment Number,ack = x + 1),表示服务器同意建立连接,并向客户端发送确认信息。
  3. 客户端收到服务器发送的ACK和SYN的数据包后,向服务器发送一个ACK确认数据包,该包的确认号为服务器的序列号加 1(ack = y + 1),而序列号为客户端在第一次握手中发送的序列号加 1(seq = x + 1),表示客户端也同意建立连接。

经过以上三个步骤,客户端和服务器就成功建立了连接,可以开始进行数据传输。这个过程就是TCP协议中三次握手的过程。

图解如下:

二、四次挥手断开连接

四次挥手是TCP协议中用于关闭连接的过程,具体步骤如下:

  1. 第一次挥手:当客户端确定自己已经没有数据要发送时,向服务器发送一个FIN(结束)数据包,其中包含自己的序列号(seq = u),表示客户端关闭数据传输。
  2. 第二次挥手:服务器接收到客户端发送的FIN后,向客户端发送一个ACK确认数据包,确认号为客户端的序列号加 1(ack = u + 1),表示服务器收到了关闭请求。此时,服务器可能还有数据需要继续发送,所以连接不会立即关闭,而是进入半关闭状态。
  3. 第三次挥手:当服务器确定自己没有数据要发送时,向客户端发送一个FIN数据包,其中包含自己的序列号(seq = v),表示服务器也准备关闭连接。
  4. 第四次挥手:客户端接收到服务器发送的FIN后,向服务器发送一个ACK确认数据包,确认号为服务器的序列号加 1(ack = v + 1),序列号为之前发送 FIN 包时的序列号加 1(seq = u + 1),表示客户端收到了关闭请求,连接正式关闭。

通过以上四个步骤,客户端和服务器完成了关闭连接的过程。值得注意的是,四次挥手中的每一次挥手都需要对方发送确认,确保双方都能安全地关闭连接。

图解如下:

那么,挥手能不能是3次呢?答案是可以的,第二次挥手和第三次挥手是可以合并在一起的。

 三、模拟服务器和客户端收发数据

服务器ser

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int socket_init();int main()
{int sockfd=socket_init();if(sockfd==-1){exit(1);}while(1){struct sockaddr_in caddr;//记录客户端地址ip,portint len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//caddr存放客户端ip,port,可能会阻塞if(c<0){continue;}printf("accept c=%d\n",c);while(1){char buff[128]={0};int n=recv(c,buff,127,0);//有可能阻塞,如果返回-1则失败,是0则对方关闭了if(n<=0){break;}printf("recv:%s\n",buff);send(c,"ok",2,0);//write(c,"ok",2);}close(c);printf("cilent close\n");}
}
int socket_init()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);//TCP,创建套接字if(sockfd==-1){return -1;}struct sockaddr_in saddr;//ipv4地址族,对应套接字的地址memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定ip,port,指定if(res==-1){printf("bind err\n");return -1;}res=listen(sockfd,5);//创建监听队列if(res==-1){return -1;}return sockfd;
}
  1. 先调用socket_init()函数初始化服务器端的套接字,绑定IP和端口,并开始监听连接请求。
  2. 不断循环accept()函数接受客户端的连接请求,一旦有客户端连接则创建一个新的套接字来处理与客户端的通信。
  3. 在客户端连接成功后,进入一个无限循环,不断接收客户端发送的消息,并打印消息内容。
  4. 如果接收到的消息长度小于等于0,则说明客户端关闭了连接,跳出循环,关闭与客户端的连接。
  5. 如果收到消息,则回复客户端消息为"ok", 继续接受下一条消息。

客户端cil

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){exit(1);}struct sockaddr_in saddr;//服务器的地址memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//发起连接,三次握手if(res==-1){printf("connet err\n");exit(1);}while(1){printf("input:\n");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}close(sockfd);exit(0);
}
  1. 创建了一个套接字socket,并指定为AF_INET和SOCK_STREAM,表示使用IPv4和TCP协议。
  2. 初始化服务器的地址saddr,包括IP地址为"127.0.0.1"、端口号为6000等信息。
  3. 调用connect()函数连接到服务器端,进行三次握手建立连接。
  4. 进入一个循环,不断接收用户输入的消息,将消息发送给服务器,并接收服务器的回复。
  5. 如果用户输入为"end",则跳出循环,关闭套接字,结束程序。

运行结果:

那么send后会直接将数据发送出去吗?答案不然,会将数据先发送到发送缓冲区,然后将数据发给接收缓冲区,最后才会接收到数据。

我们来验证一下:

 把ser.c中的int n = recv(c,buff,127,0)改为int n = recv(c,buff,1,0),执行后结果:

每输一个应该输出五个ok,但此时只输出一个ok,剩下四个ok在recv的缓冲区中,4个ok八个字符所以缓冲区有8个字符。

通过netstat -natp 命令可以显示

再输入一个a,输出四个ok

此时缓冲区中有一个ok,2个字符

TCP字节流服务

四、多线程并发处理

服务器ser

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>int socket_init();
void* fun(void* arg)
{int* p=(int*)arg;int c=*p;free(p);while(1){char buff[128]={0};int n=recv(c,buff,127,0);//有可能阻塞,如果返回-1则失败,是0则对方关闭了if(n<=0){break;}printf("recv:%s\n",buff);send(c,"ok",2,0);//write(c,"ok",2);}close(c);printf("cilent close\n");
}
int main()
{int sockfd=socket_init();if(sockfd==-1){exit(1);}while(1){struct sockaddr_in caddr;//记录客户端地址ip,portint len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//caddr存放客户端ip,port,可能会阻塞if(c<0){continue;}printf("accept c=%d\n",c);int* p=(int*)malloc(sizeof(int));if(p==NULL){close(c);continue;}*p=c;pthread_t id;pthread_create(&id,NULL,fun,(void*)p);}
}
int socket_init()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);//TCP,创建套接字if(sockfd==-1){return -1;}struct sockaddr_in saddr;//ipv4地址族,对应套接字的地址memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定ip,port,指定if(res==-1){printf("bind err\n");return -1;}res=listen(sockfd,5);//创建监听队列if(res==-1){return -1;}return sockfd;
}

客户端cil

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){exit(1);}struct sockaddr_in saddr;//服务器的地址memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//发起连接,三次握手if(res==-1){printf("connet err\n");exit(1);}while(1){printf("input:\n");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}close(sockfd);exit(0);
}

 运行结果:

五、TCP粘包问题

5.1 什么是TCP粘包?

        TCP粘包是指发送方连续发送的多个数据包,在传输过程中可能会被TCP协议合并成一个大的数据包,在接收方收到时无法正确区分这些数据包的边界,导致粘在一起,从而引起粘包现象。这样就会影响接收方对数据的解析和处理简而言之,就是多次send发送数据,被对方一次recv收到了。

5.2 TCP粘包会有什么问题?

  1. 数据解析错误:如果接收方无法正确区分数据包的边界,可能导致数据解析错误,无法按照预期的方式处理数据。
  2. 数据错误:如果多个数据包粘在一起,可能导致数据包数据内容混杂,造成数据错误或丢失。
  3. 性能影响:数据粘包会增加解析数据的复杂性,影响系统性能。

5.3 TCP粘包的解决方法?

  1. 消息定长:在发送方和接收方约定固定的消息长度,每次发送和接收的数据长度相同,这样接收方可以根据固定长度来截取数据包。
  2. 使用特殊符号分隔:在数据包之间加入特殊符号作为分隔符,接收方根据分隔符来区分不同数据包。
  3. 增加消息头:在数据包头部添加额外的消息头信息,包括消息长度等,接收方通过消息头信息来解析数据包。
http://www.xdnf.cn/news/3271.html

相关文章:

  • 昆仑万维:AI短剧出海布局,中型公司如何突破AI商业化?
  • 可视化图解算法:判断是否完全二叉树
  • PH热榜 | 2025-04-30
  • 如何使用C语言手搓斐波那契数列?
  • 如何设计一个100w QPS高并发抢券系统
  • 海外社交软件技术深潜:实时互动系统与边缘计算的极限优化
  • 借助电商 API 接口实现电商平台商品数据分析的详细步骤分享
  • MCP 服务器搭建【sse 类型】实现上市公司年报查询总结, 127.0.0.1:8000/sse直接配置配合 Cherry Studio使用简单
  • 徐州旅行体验分享:从扬州出发的 24 小时碳水之旅
  • Wireshark使用教程
  • NAMUR NE 43是什么标准?
  • Windows 匿名管道通信
  • 自尊量表(SES)在线测试:探索你的自我价值认知
  • AI智能体 | 使用Coze制作提取单条抖音文案并二创
  • 百家号等新媒体私信入口是否可以聚合到企业微信的客服,如何实现
  • Nginx — http、server、location模块下配置相同策略优先级问题
  • 【AI提示词】二八法则专家
  • 【今日探针卡行业分析】2025年4月30日
  • 在Electron中爬取CSDN首页的文章信息
  • 【神经网络与深度学习】探索全连接网络如何学习数据的复杂模式,提取高层次特征
  • 无水印短视频素材下载网站有哪些?十个高清无水印视频素材网站分享
  • vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值
  • 【自然语言处理与大模型】大模型意图识别实操
  • 【MCP Node.js SDK 全栈进阶指南】高级篇(6):MCP服务大规模部署方案
  • 分享5款让电脑更方便更有趣的软件
  • 树的序列化 - 学习笔记
  • 聚焦数字中国|AI赋能与安全守护:Coremail引领邮件办公智能化转型
  • DeepSeek最新大模型发布-DeepSeek-Prover-V2-671B
  • Depth Anything V2:深度万象 V2
  • 【Prometheus-OracleDB Exporter安装配置指南,开机自启】