connect/select超时, 三次握手就一定不能建立吗? 非也非也

972 篇文章 329 订阅
148 篇文章 34 订阅

       我们知道, 在非阻塞的socket编程中, connect函数失败, 并不意味着连接失败, 还要进行继续判断。本文先不说这个。

       本文我们来讨论这样一种情况, 在connect的时候, 如果select函数检测到连接超时, 那么三次握手就一定不能建立吗? 非也非也。

       客户端调用connect函数后, 触发协议栈发送syn包, 发起连接请求, 我们为connect函数设置1微秒的超时时间, 也就是说, 如果服务端没有在1微秒内没有回syn/ack包, 那么客户端自然认为超时。 但是, 这并不意味着三次握手不再继续了。 也可能是服务端回的syn/ack包还没有来得及达到客户端呢(正在达到的路上)!  客户端在1微秒内, 没有收到服务端的syn/ack,  但这并不影响服务端syn/ack包的姗姗来迟,  客户端收到syn/ack包后, 会照旧回复ack包, 进行第三次握手。

       所以, 我们看到, 客户端的select超时, 并不表明三次握手没法建立。

      

       我们用实际程序来看看, 服务端程序为:

 

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>

int main()
{
	int sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addrSrv;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_addr.s_addr = INADDR_ANY; 
	addrSrv.sin_port = htons(23456);

	bind(sockSrv, (const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));

	listen(sockSrv, 5);

	struct sockaddr_in addrClient;
	int len = sizeof(struct sockaddr_in);

	int sockConn = accept(sockSrv, (struct sockaddr *)&addrClient, (socklen_t*)&len);


	while(1)  
	{  
		char szRecvBuf[50001] = {0};  
		int iRet = recv(sockConn, szRecvBuf, sizeof(szRecvBuf) - 1, 0);  
		printf("iRet is %d\n", iRet);	
  
		getchar();	
		close(sockConn);
	}  

	while(1);
	close(sockSrv);
	
	return 0;
}

       先启动它。

 

 

       客户端的程序为:

 

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <time.h>

int main(int argc, char *argv[]) // 注意输入参数, 带上ip和port, 带上微秒
{
    int sockClient = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addrSrv;
    addrSrv.sin_addr.s_addr = inet_addr(argv[1]);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(atoi(argv[2]));

	fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0)|O_NONBLOCK);  
	
    int iRet = connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
	printf("connect iRet is %d, errmsg:%s\n", iRet, strerror(errno)); // 返回-1不一定是异常

	if (iRet != 0)  
	{  
	   	if(errno != EINPROGRESS)
		{
			printf("connect error:%s\n",strerror(errno));  
		}
	   	else  
		{
			int nTime = atoi(argv[3]); // 微秒
			struct timeval tm = {0, nTime};	
			fd_set wset, rset;  
			FD_ZERO(&wset);	
			FD_ZERO(&rset);	
			FD_SET(sockClient, &wset);  
			FD_SET(sockClient, &rset); 
			int n = select(sockClient + 1, &rset, &wset, NULL, &tm);  
			if(n < 0)	
			{  
			   printf("select error, n is %d\n", n);  
			}  
			else if(n == 0)  
			{  
			   printf("connect time out\n");  
			}  
			else if (n == 1)  
			{
			   if(FD_ISSET(sockClient, &wset))	
			   {  
				   printf("connect ok!\n");  
				   fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0) & ~O_NONBLOCK);  
			   }  
			   else  
			   {  
				   printf("unknow error:%s\n", strerror(errno));	
			   }  
			}
			else
			{
				printf("oh, not care now, n is %d\n", n);
			}
		}  
	}  

	printf("I am here!\n");
    getchar();
    close(sockClient);
    return 0;
}

       我们用./client 10.100.70.140 23456 1 来向服务端发起连接, 结果为:

 

 

xxx$ ./client 10.100.70.140 23456 1
connect iRet is -1, errmsg:Operation now in progress
connect time out
I am here!

       可以看到, select超时。 看看网络包吧:

 

 

16:38:58.988614 IP 10.100.70.139.39077 > 10.100.70.140.aequus: Flags [S], seq 3890616217, win 14280, options [mss 1428,sackOK,TS val 1577168992 ecr 0,nop,wscale 8], length 0
        0x0000:  4500 003c a7f9 4000 4006 f0e3 0a64 468b  E..<..@.@....dF.
        0x0010:  0a64 468c 98a5 5ba0 e7e6 1799 0000 0000  .dF...[.........
        0x0020:  a002 37c8 a20d 0000 0204 0594 0402 080a  ..7.............
        0x0030:  5e01 b060 0000 0000 0103 0308 0000 0000  ^..`............
        0x0040:  0000 0000 0000 0000 0000 0000            ............
16:38:58.993283 IP 10.100.70.140.aequus > 10.100.70.139.39077: Flags [S.], seq 3012010132, ack 3890616218, win 14160, options [mss 1428,sackOK,TS val 1577167729 ecr 1577168992,nop,wscale 8], length 0
        0x0000:  4500 003c 0000 4000 4006 98dd 0a64 468c  E..<..@.@....dF.
        0x0010:  0a64 468b 5ba0 98a5 b387 a094 e7e6 179a  .dF.[...........
        0x0020:  a012 3750 0f28 0000 0204 0594 0402 080a  ..7P.(..........
        0x0030:  5e01 ab71 5e01 b060 0103 0308 0000 0000  ^..q^..`........
        0x0040:  0000 0000 0000 0000 0000 0000            ............
16:38:58.993310 IP 10.100.70.139.39077 > 10.100.70.140.aequus: Flags [.], ack 1, win 56, options [nop,nop,TS val 1577168993 ecr 1577167729], length 0
        0x0000:  4500 0034 a7fa 4000 4006 f0ea 0a64 468b  E..4..@.@....dF.
        0x0010:  0a64 468c 98a5 5ba0 e7e6 179a b387 a095  .dF...[.........
        0x0020:  8010 0038 a205 0000 0101 080a 5e01 b061  ...8........^..a
        0x0030:  5e01 ab71 0000 0000 0000 0000 0000 0000  ^..q............
        0x0040:  0000 0000                                ....

       可以看到, 第二次握手的syn/ack包, 确实在1微秒之后才到达。 也就是说, 在1微秒时, 客户端判定超时, 但syn/ack还在长途奔袭, 还在路上。 

 

       此刻, 服务端syn/ack的心情如何?  我替syn/ack包起了那句歌词: 我来到你的城市, 走过你来时的路。 可惜你耐心不够, 没等我达到, 你就先走(超时)哭大哭快哭了

 

       最后三次握手正常建立, 这也在告诉我们, 在连接的时候, 设置的时间不能太短, 否则容易欺骗客户端程序员。 还有个疑问, 为啥没有看到客户端的超时重传呢? 大家可以思考一下。

 

       好,  打开音响, 听听陈奕迅的歌------《好久不见》

 

我来到 你的城市
走过你来时的路
想像着 没我的日子
你是怎样的孤独
拿着你 给的照片
熟悉的那一条街
只是没了你的画面
我们回不到那天
你会不会忽然的出现
在街角的咖啡店
我会带着笑脸 挥手寒暄
和你 坐着聊聊天我多么想和你见一面
看看你最近改变
不再去说从前 只是寒暄
对你说一句 只是说一句
好久不见
拿着你 给的照片
熟悉的那一条街
只是没了你的画面
我们回不到那天
你会不会忽然的出现
在街角的咖啡店
我会带着笑脸 挥手寒暄
和你 坐着聊聊天
我多么想和你见一面
看看你最近改变
不再去说从前 只是寒暄
对你说一句 只是说一句
好久不见

 

 







 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值