1 TCP粘包问题

TCP粘包: 指在一次接收数据不能完全地体现一个完整的消息数据。发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
主要原因有以下几种:

1.1 发送方原因

  1. 应用缓冲区大于TCP socket发送缓冲区的大小,导致消息被分成多部分发送
  2. 发送的数据大于TCP MSS的达到,导致TCP消息进行分节
  3. 发送的消息大于IPcengMTU大小,IP分组

snipaste_20181226_222358.png

1.2 接收方原因

TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。

1.3 粘包如何解决

方案:要在应用层维护消息与消息的边界。具体方法如下:

  • 每次发定长的包(设置一个固定大小的消息struct)
  1. struct packet
  2. {
  3. int len; //消息实际长度
  4. char buf[1024]; //消息buffer长度
  5. };
  • 包尾加\r\n(FTP的方法)或其他特定符号
  • 在消息头加上数据长度(Q921的做法)
  • 设计复杂应用层协议

2 client关闭后的server端僵尸进程问题

2.1 问题原因

client连接server完成传输之后,在client端关闭时, 服务器子进程会向服务器父进程发送SIGCHLD信号,导致server端进程出现僵尸进程。如图:

imgclip131faf.png

2.2 解决方法

解决方法如下:

  • 忽略所有SIGCHLD信号: 在代码开始加入如下代码

    1. signal(SIGCHLD, SIG_IGN);
  • 对SIGCHLD指定处理函数: 处理函数中使用waitpid ```c signal(SIGCHLD, customHandler);

void customHandler(int signal) { //循环处理多个子进程的信号 while(waitpid(-1, NULL, WNOHANG) > 0) ; }

  1. <a name="sMM9x"></a>
  2. # 3 关闭一端通路SIGPIPE信号导致进程终止问题
  3. <a name="IsApj"></a>
  4. ## 3.1 问题原因
  5. 如下图所示为TCP两端建立和断开的流程和状态:
  6. ![indgsfmgclip.png](https://cdn.nlark.com/yuque/0/2020/png/690827/1577888334145-16e75f56-e9f3-4988-81a2-c704a01f6a02.png#align=left&display=inline&height=406&margin=%5Bobject%20Object%5D&name=indgsfmgclip.png&originHeight=406&originWidth=384&size=117293&status=done&style=none&width=384)
  7. 在断开时,由于TCP是一个全双工的通道,所以需要两端都需要向对端发送**FIN断开请求**。<br />如果A方发送FIN到B方,表示A方不再发送数据;但此时B方并没有发送FIN,所以还是可以发送数据到A方的。B方第一次发送数据之后,A方会再回复**RST**消息,表示socket通道变成了单通道,需要reset连接。<br />如果在B方收到RST之后,仍然调用write函数发送数据到A方,B方就会收到**SIGPIPE**信号(通道有问题),导致B方进程终止。
  8. <a name="7ejqa"></a>
  9. ## 3.2 解决方法
  10. 对于SIGPIPE信号,通常的做法是忽略,禁止该信号终止进程:
  11. ```c
  12. signal(SIGPIPE, SIP_IGN);