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

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. 增加消息头:在数据包头部添加额外的消息头信息,包括消息长度等,接收方通过消息头信息来解析数据包。

相关文章:

  • ceph存储原理
  • 【UE5】“对不起,您的客户端未能传递登录所需的参数”解决办法
  • Linux Quota 显示空间占用远大于实际数据的问题排查记录
  • 01 mysql 安装(Windows)
  • 32单片机——独立看门狗
  • 算法基础学习|03整数二分
  • 如何编制研发部门绩效考核制度
  • 删除k8s某命名空间,一直卡住了怎么办?
  • java之Integer、Double自动拆装箱比较,踩坑值int和Integer比较之空指针异常
  • 垒球世界纪录多少米·棒球1号位
  • 三格电子上新了——超高频RFID读写器
  • 2025最新福昕PDF编辑器,PDF万能处理工具
  • PostgreSQL事务与并发清理
  • Electron Forge【实战】自定义菜单 -- 顶部菜单 vs 右键快捷菜单
  • 力扣HOT100——207.课程表
  • pbchsim.c中main函数流程
  • 数据库有哪些特性是什么
  • 如何用GPU Instancing来优化树木草石重复模型
  • 【网络原理】 《TCP/IP 协议深度剖析:从网络基础到协议核心》
  • 产品VP简历模板案例
  • 国台办:民进党当局所谓“对等尊严”,就是企图改变两岸同属一中
  • 巴西外长维埃拉:国际形势日益复杂,金砖国家必须发挥核心作用
  • 69岁朱自强被查,曾任南京地铁总经理
  • 西班牙葡萄牙电力基本恢复
  • 西班牙葡萄牙遭遇史上最严重停电:交通瘫了,通信崩了,民众疯抢物资
  • 太好玩了!坐进大卫·霍克尼的敞篷车穿越他画笔下的四季