觸發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確認時傳輸時間過長,也會導致超時!!!
好了大功告成