触发connect超时事件
有关于如何触发connect超时事件,之前相当然的认为在服务器程序accpet函数前阻塞一段事件就好了,这个思路是完全错误的!
这是我犯了的一个错误,没有严格的验证自己的程序就将其发布了出来,被小组的小伙伴提问时才发现了这个问题,在这里深表歉意!!!同时也非常感谢我的哪位小伙伴!下边是那篇文章,现已更正。
代码还是以前的代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int timeout_connect(const char *ip, int port, int time) // 5
{
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
struct timeval timeout;
timeout.tv_sec = time;
timeout.tv_usec = 0;
socklen_t len = sizeof(timeout);
ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
assert(ret != -1);
ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
printf("ret ==%d \n", ret);
if (ret == -1)
{
if (errno == EINPROGRESS)
{
printf("connecting timeout\n");
return -1;
}
printf("error occur when connecting to server\n");
return -1;
}
printf("%d\n", sockfd);
return sockfd;
}
int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int sockfd = timeout_connect(ip, port, 5);
if (sockfd < 0)
{
return 1;
}
return 0;
}
接下来我将谈一谈为什么在accept之前sleep是不可以的,以及怎样做是可行的。
三次握手
提到TCP网络连接,就必须提到三次握手。(如下图所示)
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
- 客户端向服务器发送一个SYN J, 这时connect进入阻塞状态;
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
为什么不可以
基于此,我们自然而然的想到了如果在accept之前阻塞,那么服务端就不会向客户发送ack确认信息了,那么客户端就不会成功建立连接,可能就会触发超时。
代码可能是这样的
sleep(100);
int connfd = accept ( sock, ( struct sockaddr* )&client, &client_addrlength );
好的,编译运行。
!!!
什么,客户端直接就返回了,客户端就直接建立连接成功了???
假装分析!!!
得出来一个结论,发向客户端的ACK确认信息,不是由accept函数发送的。跟accept一点关系都没有
那再想一想,往前推一步,会不会是由listen函数发送的呢。
代码可能是这样的
sleep(100);
ret = listen( sock, 5 );
int connfd = accept ( sock, ( struct sockaddr* )&client, &client_addrlength );
好的,编译运行。
!!!
什么,客户端直接就返回 -1 了,怎么肥四???我好方鸭,这倒底是谁干的!!!是什么神仙函数!!!
综上的出来一个结论
发向客户端的ACK确认信息,**既不是由accept函数发送的,也不是由listen函数发送,**据我猜测,是由TCP协议栈完成的具体的,细节我还不太清楚。求大佬赐教,
或者等我看了协议栈的源码就明白辽。
2018 1.19
经过查阅资料,《UNIX网络编程(第一卷)——套接口 API 和 X/Open 传输接口 API》一书的4.3节有写到:
对于TCP套接口来说,函数 connect 激发TCP的三路握手过程,且仅在链接成功建立或出错时才返回,返回的错误可能有如下几种情况:
-
如果TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT。例如在4.4BSD中,当调用函数 connect 时,发出一个SYN,若无响应,等待6秒之后再发一个;若仍无响应,24秒钟之后再发一个。若总共等待了75秒钟之后仍未响应,则返回错误…
从书中可以看到 connect 建立TCP链接的过程中,会发送SYN包,如果没有收到SYN包的回包,内核会多次发送SYN包,并且每次重试的间隔会逐渐增加,避免发送太多的SYN包影响网络。
解决办法
第一种方案
记得之前做实验的时候客户端在启动的时候调用了 connect 函数,因为连接了一个不可用的端口,导致connect最后报出了 “Connection timed out” 的错误。但是这中间过了六十多秒的时间(非本节代码)。
所以第一种解决方案便是,去连接一个远程的服务器(没有开启的),就可以啦!!!这样本地的客户端是查不到远程的端口到底有没有打开,就会一直等待返回。
第二种方案
据我猜测,还有一种可能就是你把服务器假设在银河系以外,服务器给客户端发送ACK确认时传输时间过长,也会导致超时!!!
好了大功告成