分享

TCP关闭连接close socket发送RST导致client读取结果失败

 嵌入式开发sun 2017-05-09

和同事的联调时碰到一个诡异的问题,server端的业务逻辑层面没有任何异常,但返回结果给client端时,并没有发送完所有的数据,就直接发送了RST给client,导致client读取结果失败。注: client与server直接是短连接,server在write所有的数据后会直接close。
通过tcpdump截包对比之前旧版本正常的client端通信,发送server与旧版本的client通信时,三次握手 => client发送数据 => server发送响应数据 => 四次挥手。但与新版本client通信时,则直接是 三次握手 => client发送数据 => server发送响应数据(未完成) => server发送RST。
借助强大的Google大神,以及万能Stackoverflow。原因如下:当close断开连接时,如果缓冲区中又未被读取的数据,则tcp不会发送正常的FIN包,而发送RST给对端。
简单测试一下,代码如下,client和server的主要代码如下:
  1. /*  
  2.  * client  
  3. */   
  4. void send_plain_buf(int fd, size_t buf_size)   
  5. {   
  6. char buf[buf_size];   
  7. memset(buf, 0, buf_size);   
  8. int ret = write(fd, buf, buf_size);   
  9. if(ret < 0) {   
  10. fprintf(stderr, "send failed, err[%m]\n");   
  11. return;   
  12. }   
  13. ret = read(fd, buf, 1);   
  14. if(ret == 0) {   
  15. fprintf(stderr, "client get FIN\n");   
  16. } else {   
  17. fprintf(stderr, "read ret[%d], err[%m]", ret);   
  18. }   
  19. return;   
  20. }   
  21.    
  22. /*  
  23.  *server  
  24. */   
  25.    
  26. void recv_plain_buf(int fd, size_t buf_size)   
  27. {   
  28. char buf[buf_size];   
  29. memset(buf, 0, buf_size);   
  30. int ret = read(fd, buf, buf_size);   
  31. if(ret < 0) {   
  32. fprintf(stderr, "server recv failed, err[%m]\n");   
  33. return;   
  34. }   
  35. return;   
  36. }   
测试一:client发送1个byte,server读取1个byte,tcpdump截包
  1. 21:35:43.829700 IP tc-im-nrd306.tc.baidu.com.22579 > tc-im-nrd301.tc.baidu.com.8654: S 3630522968:3630522968(0) win 5840 <mss 1460,sackOK,timestamp 1827311072 0,nop,wscale 7> 
  2. 21:35:43.829706 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22579: S 2472593503:2472593503(0) ack 3630522969 win 5792 <mss 1460,sackOK,timestamp 2429650378 1827311072,nop,wscale 7> 
  3. 21:35:43.829846 IP tc-im-nrd306.tc.baidu.com.22579 > tc-im-nrd301.tc.baidu.com.8654: . ack 1 win 46 <nop,nop,timestamp 1827311073 2429650378> 
  4. 21:35:43.829873 IP tc-im-nrd306.tc.baidu.com.22579 > tc-im-nrd301.tc.baidu.com.8654: P 1:2(1) ack 1 win 46 <nop,nop,timestamp 1827311073 2429650378> 
  5. 21:35:43.829876 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22579: . ack 2 win 46 <nop,nop,timestamp 2429650378 1827311073> 
  6. 21:35:43.829888 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22579: F 1:1(0) ack 2 win 46 <nop,nop,timestamp 2429650378 1827311073> 
  7. 21:35:43.830035 IP tc-im-nrd306.tc.baidu.com.22579 > tc-im-nrd301.tc.baidu.com.8654: . ack 2 win 46 <nop,nop,timestamp 1827311073 2429650378> 
  8. 21:35:43.830080 IP tc-im-nrd306.tc.baidu.com.22579 > tc-im-nrd301.tc.baidu.com.8654: F 2:2(0) ack 2 win 46 <nop,nop,timestamp 1827311073 2429650378> 
  9. 21:35:43.830083 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22579: . ack 3 win 46 <nop,nop,timestamp 2429650378 1827311073> 
测试二:client发送2个byte,server读取1个byte,tcpdump截包
  1. 21:36:31.137496 IP tc-im-nrd306.tc.baidu.com.22580 > tc-im-nrd301.tc.baidu.com.8654: S 3686863514:3686863514(0) win 5840 <mss 1460,sackOK,timestamp 1827358388 0,nop,wscale 7> 
  2. 21:36:31.137501 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22580: S 2526723815:2526723815(0) ack 3686863515 win 5792 <mss 1460,sackOK,timestamp 2429697693 1827358388,nop,wscale 7> 
  3. 21:36:31.137666 IP tc-im-nrd306.tc.baidu.com.22580 > tc-im-nrd301.tc.baidu.com.8654: . ack 1 win 46 <nop,nop,timestamp 1827358388 2429697693> 
  4. 21:36:31.137671 IP tc-im-nrd306.tc.baidu.com.22580 > tc-im-nrd301.tc.baidu.com.8654: P 1:3(2) ack 1 win 46 <nop,nop,timestamp 1827358388 2429697693> 
  5. 21:36:31.137676 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22580: . ack 3 win 46 <nop,nop,timestamp 2429697693 1827358388> 
  6. 21:36:31.137705 IP tc-im-nrd301.tc.baidu.com.8654 > tc-im-nrd306.tc.baidu.com.22580: R 1:1(0) ack 3 win 46 <nop,nop,timestamp 2429697693 1827358388> 
在net/ipv4/tcp.c:1900行附近的代码如下:
  1. /* As outlined in RFC 2525, section 2.17, we send a RST here because  
  2. * data was lost. To witness the awful effects of the old behavior of  
  3. * always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk  
  4. * GET in an FTP client, suspend the process, wait for the client to  
  5. * advertise a zero window, then kill -9 the FTP client, wheee...  
  6. * Note: timeout is always zero in such a case.  
  7. */   
  8. if (data_was_unread) {   
  9. /* Unread data was tossed, zap the connection. */   
  10. NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);   
  11. tcp_set_state(sk, TCP_CLOSE);   
  12. tcp_send_active_reset(sk, sk->sk_allocation);   
  13. ..   

标签: TCP
顶一下
(49385)
100.00%
踩一下
(16)
0.00%
0

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多