問題
此前開發實現了一個手機掃碼連接PC,PC端調用手機端證書做簽名的功能,最近為了優化通信質量和穩定性,將通信協議由UDP改為TCP,局域網用TCP直連,外網用MQTT做中轉,優先使用局域網。
在調試的過程中,發現用4G連接的時候,程序會一直卡在局域網的connect()方法,大概1,2分鐘才返回錯誤。后來才發現,阻塞模式下,TCP的connect超時時間可能為75
秒到幾分鐘。。。坑爹啊,等這么久的嗎??
原因
- 阻塞模式
客戶端socket為阻塞模式,connect()會一直阻塞到連接建立或連接失敗(超時時間可能為75
秒到幾分鐘)
- 非阻塞模式
調用connect()后,如果連接不能馬上建立則返回-1,并且errno設置為EINPROGRESS,表示正在嘗試連接(注意連接也可能馬上建立成功比如連接本機的服務器進程),此時TCP的三次握手動作在背后繼續進行,而程序可以做其他的東西,然后調用select()檢測非阻塞connect是否完成(此時可以指定select的超時時間,這個超時時間可以設置為比connect的超時時間短),如果select超時則關閉socket,然后可以嘗試創建新的socket重新連接,如果select返回非阻塞socket描述符可寫則表明連接建立成功。
解決方案
那么,如果希望超時時間可以自己設置,我們可以這樣做:
- 設置socket為非阻塞模式
- connect
- 判斷errno是否為EINPROGRESS
- select,大于0表示連接成功
- 設置socket為阻塞模式,用于收發數據
代碼
int testTCP()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(30538);
server_addr.sin_addr.s_addr= inet_addr("192.168.20.124");
// 設成非阻塞模式
int flags;
flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1)
{
close(sockfd);
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) == -1)
{
close(sockfd);
return -1;
}
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
if (errno == EINPROGRESS)
{
printf("connecting...\n");
}
if (errno != EINPROGRESS && errno != EWOULDBLOCK)
{
close(sockfd);
return -1;
}
struct timeval tv;
fd_set wset;
tv.tv_sec = 3; //timeout
tv.tv_usec = 0;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
int n = select(sockfd + 1, NULL, &wset, NULL, &tv);
if (n < 0)
{
close(sockfd);
return -1;
}
else if (n == 0) //timeout
{
close(sockfd);
return -1;
}
else
{
printf("connected");
}
}
// 設成阻塞模式
flags &= ~ O_NONBLOCK;
fcntl(sockfd,F_SETFL, flags);
// 開始數據收發
// ....
// ....
close(sockfd);
return 0;
}