• TCP链接异常: SYN_RECV


    1. 异常数据包分析:

    从数据包分析来看应该是网关这边出问题了,应该是网关的服务程序在1217上出问题了,

    (注意左右量变的数据包的一个syn的Seq都是1358143899)

    从重发2开始网关这边就一直认为它没有收到client回复给它的ACK,但是从量变的数据包来看,client一直都有收到syn_ack, 并且也回复了每次的ACK到网关,网关的网口也有收到这个ACK,但是服务程序就是认为自己没有收到。图片的左边是tcpclient上的数据包,右边是网关上的数据包。

    192.170.146.254是模拟hmi的电脑ip,192.170.146.5是网关的IP。出问题时重新从.254的pc上是可以ssh连接上网关的,重启hmi后,网口也没有使hmi恢复正常(也有重启hmi的动作).

    2 补充知识:

    tcp的三次握手如下拖所示,也可以看到相关的状态机:

    tcp完整状态机:

    状态名称

    含义

    LISTEN

    服务端需要打开一个socket进行监听,状态为LISTEN

    SYN_SENT

    客户端通过应用程序调用connect进行active open.于是客户端tcp发送一个SYN以请求建立一个连接.之后状态置为SYN_SENT(半链接)

    SYN_RECV

    服务端应发出ACK确认客户端的SYN,同时自己向客户端发送一个ACK. 之后状态置为SYN_RECV(半链接)

    ESTABLISHED

    代表一个打开的连接,双方可以进行或已经在数据交互了(全连接)

    FIN_WAIT1

    主动关闭(active close)端应用程序调用close,于是其TCP发出FIN请求主动关闭连接,之后进入FIN_WAIT1状态.等待远程TCP的连接中断请求,或先前的连接中断请求的确认

    FIN_WAIT2

    主动关闭端接到ACK后,就进入了FIN-WAIT-2

    TIME_WAIT

    在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME-WAIT状态。等待足够的时间以确保远程TCP接收到连接中断请求的确认。(TIME_WAIT状态的形成只发生在主动关闭连接的一方)

    CLOSE_WAIT

    被动关闭(passive close)端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入CLOSE_WAIT. 等待从本地用户发来的连接中断请求(等待本地应用程序执行close)

    LAST_ACK

    被动关闭端一段时间后,接收到文件结束符的应用程序将调用CLOSE关闭连接。这导致它的TCP也发送一个 FIN,等待对方的ACK.就进入了LAST-ACK

    CLOSING

    比较少见,维持时间非常短,等待远程TCP对连接中断的确认

    CLOSED

    被动关闭端在接受到ACK包后,就进入了closed的状态,本地连接结束。

    3. 项目代码(略)

    4. 特别解释下面三种状态

    SYN_RECV

    服务端应发出ACK确认客户端的SYN,同时自己向客户端发送一个SYN. 之后状态置为SYN_RECV,等待客户端回应的ACK,触发accept()完成,如果accept没有完成,状态就会维持。(还有一种情况就是等待队列里的连接数超过listen的第二个参数的个数)

    CLOSE_WAIT

    被动关闭(passive close)端TCP接到FIN后,就发出ACK以回应FIN请求,但是网关还没有调用close()。比如出问题时,网关的很多连接应该会是这样。

    TIME_WAIT

    在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME-WAIT状态。比如出问题时,HMI的很多连接应该会是这样。

    5. 测试代码:

    5.1 通过测试代码来控制tcp的流程,然后观察连接状态的变化,使用tcp助手作为client去连接下面的测试代码的8888端口,

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<string.h>
    4. #include<errno.h>
    5. #include<sys/types.h>
    6. #include<sys/socket.h>
    7. #include<netinet/in.h>
    8. #include<netinet/ip.h>
    9. #include<unistd.h>
    10. #include<arpa/inet.h>
    11. #include <sys/time.h>
    12. #include <unistd.h>
    13. #define MAXLINE 25
    14. int main(int argc, char** argv)
    15. {
    16. int listenfd, connfd;
    17. struct sockaddr_in servaddr;
    18. char buff[25];
    19. int n,cnt=0,i;
    20. int res,ret;
    21. struct timeval tv1,tv2;
    22. if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    23. printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    24. return 0;
    25. }
    26. memset(&servaddr, 0, sizeof(servaddr));
    27. servaddr.sin_family = AF_INET;
    28. servaddr.sin_addr.s_addr =htonl(INADDR_ANY);
    29. servaddr.sin_port = htons(8888);
    30. int on = 1;
    31. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    32. if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    33. printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    34. return 0;
    35. }
    36. if( listen(listenfd, 3) == -1){//控制sync队列的大小,方便测试。等待队列里的连接数超过listen的第二个参数的个数
    37. printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    38. return 0;
    39. }
    40. struct sockaddr_in cin;
    41. socklen_t addrlen = sizeof(cin);
    42. while(1) sleep(10);//故意不进入accept,让连接状态保持在SYN_RECV
    43. if((connfd = accept(listenfd,(struct sockaddr *)&cin,&addrlen)) < 0)
    44. {
    45. printf("accept err\n");
    46. exit(1);
    47. }
    48. char ipv4_addr[16];
    49. if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
    50. {
    51. printf("inet_ntop\n");
    52. exit(1);
    53. }
    54. printf("Client(%s:%d) is connected!\n",ipv4_addr,htons(cin.sin_port));
    55. printf("======waiting for client's request======\n");
    56. while(1){
    57. cnt++;
    58. printf("%drecv msg from client:",cnt);
    59. memset(buff, 0, sizeof(buff));
    60. n = recv(connfd, buff, MAXLINE, 0);
    61. if(n > 0)
    62. {
    63. for(i = 0;i< n;i++)
    64. printf("%c ", buff[i]);
    65. }
    66. printf("\n");
    67. send(connfd, buff, n,MSG_NOSIGNAL);
    68. }
    69. close(connfd);
    70. close(listenfd);
    71. return 0;
    72. }

    5.2 测试代码测试分析如下:

    在TCP的三次握手中,走后一个ACK已经从client端(PC侧)发到Service端(网关)的网口上了,但是网关的服务程序没有收到,所以网关的状态维持在SYN_RECV,并且协议栈开始重发SYN_ACK。这个行为和我们在现场抓的数据包的行为是一样的。

  • 相关阅读:
    Socket编程实现简易聊天室
    指纹浏览器功能对比:AdsPower VS Multilogin
    医疗产品设计的四个主要因素
    select精准定时器分析
    ORACLE内存结构
    STM32F1课程学习
    Games101笔记-计算机图形学概述
    shell脚本基础
    初识 Linux Shell
    osgEarth示例分析——osgearth_cluster
  • 原文地址:https://blog.csdn.net/paky_du/article/details/127952983