觸發connect超時事件

觸發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網絡連接,就必須提到三次握手。(如下圖所示)  
enter description here

我們知道tcp建立連接要進行“三次握手”,即交換三個分組。大致流程如下:

  1. 客戶端向服務器發送一個SYN J, 這時connect進入阻塞狀態;
  2. 服務器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
  3. 客戶端再想服務器發一個確認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的三路握手過程,且僅在鏈接成功建立或出錯時才返回,返回的錯誤可能有如下幾種情況:

  1. 如果TCP客戶沒有收到SYN分節的響應,則返回ETIMEDOUT。例如在4.4BSD中,當調用函數 connect 時,發出一個SYN,若無響應,等待6秒之後再發一個;若仍無響應,24秒鐘之後再發一個。若總共等待了75秒鐘之後仍未響應,則返回錯誤…

    從書中可以看到 connect 建立TCP鏈接的過程中,會發送SYN包,如果沒有收到SYN包的回包,內核會多次發送SYN包,並且每次重試的間隔會逐漸增加,避免發送太多的SYN包影響網絡。


解決辦法

第一種方案

記得之前做實驗的時候客戶端在啓動的時候調用了 connect 函數,因爲連接了一個不可用的端口,導致connect最後報出了 “Connection timed out” 的錯誤。但是這中間過了六十多秒的時間(非本節代碼)。

所以第一種解決方案便是,去連接一個遠程的服務器(沒有開啓的),就可以啦!!!這樣本地的客戶端是查不到遠程的端口到底有沒有打開,就會一直等待返回。

第二種方案

據我猜測,還有一種可能就是你把服務器假設在銀河系以外,服務器給客戶端發送ACK確認時傳輸時間過長,也會導致超時!!!

在這裏插入圖片描述

好了大功告成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章