socket non-blocking mode connect
對於面向連接的socket類型(SOCK_STREAM,SOCK_SEQPACKET),在讀寫數據之前必須建立連接,connect()函數用於完成面向連接的socket的建鏈過程,對於TCP,也就是三次握手過程。
connect()函數
頭文件:
#include<sys/types.h>
#include<sys/socket.h>
聲明:
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
功能:
使用套接字sockfd建立到指定網絡地址serv_addr的socket連接,參數addrlen爲serv_addr指向的內存空間大小,即sizeof(struct sockaddr_in)。
返回值:
1)成功返回0,表示連接建立成功(如服務器和客戶端是同一臺機器上的兩個進程時,會發生這種情況)
2)失敗返回SOCKET_ERROR,相應的設置errno,通過errno獲取錯誤信息。常見的錯誤有對方主機不可達或者超時錯誤,也可能是對方主機沒有進程監聽對應的端口。
非阻塞connect(non-block mode connect)
套接字執行I/O操作有阻塞和非阻塞兩種模式。在阻塞模式下,在I/O操作完成前,執行操作的函數一直等候而不會立即返回,該函數所在的線程會阻塞在這裏。相反,在非阻塞模式下,套接字函數會立即返回,而不管I/O是否完成,該函數所在的線程會繼續運行。
客戶端調用connect()發起對服務端的socket連接,如果客戶端的socket描述符爲阻塞模式,則connect()會阻塞到連接建立成功或連接建立超時(linux內核中對connect的超時時間限制是75s, Soliris 9是幾分鐘,因此通常認爲是75s到幾分鐘不等)。如果爲非阻塞模式,則調用connect()後函數立即返回,如果連接不能馬上建立成功(返回-1),則errno設置爲EINPROGRESS,此時TCP三次握手仍在繼續。此時可以調用select()檢測非阻塞connect是否完成。select指定的超時時間可以比connect的超時時間短,因此可以防止連接線程長時間阻塞在connect處。
select判斷規則:
1)如果select()返回0,表示在select()超時,超時時間內未能成功建立連接,也可以再次執行select()進行檢測,如若多次超時,需返回超時錯誤給用戶。
2)如果select()返回大於0的值,則說明檢測到可讀或可寫的套接字描述符。源自 Berkeley 的實現有兩條與 select 和非阻塞 I/O 相關的規則:
A) 當連接建立成功時,套接口描述符變成 可寫(連接建立時,寫緩衝區空閒,所以可寫)
B) 當連接建立出錯時,套接口描述符變成 既可讀又可寫(由於有未決的錯誤,從而可讀又可寫)
因此,當發現套接口描述符可讀或可寫時,可進一步判斷是連接成功還是出錯。這裏必須將B)和另外一種連接正常的情況區分開,就是連接建立好了之後,服務器端發送了數據給客戶端,此時select同樣會返回非阻塞socket描述符既可讀又可寫。
□對於Unix環境,可通過調用getsockopt來檢測描述符集合是連接成功還是出錯(此爲《Unix Network Programming》一書中提供的方法,該方法在Linux環境上測試,發現是無效的):
A)如果連接建立是成功的,則通過getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 獲取的error 值將是0
B)如果建立連接時遇到錯誤,則errno 的值是連接錯誤所對應的errno值,比如ECONNREFUSED,ETIMEDOUT 等
□一種更有效的判斷方法,經測試驗證,在Linux環境下是有效的:
再次調用connect,相應返回失敗,如果錯誤errno是EISCONN,表示socket連接已經建立,否則認爲連接失敗。
綜上所述,這裏總結一下非阻塞connect的實現過程。
非阻塞connect的實現過程
1. 創建套接字sockfd
-
-
int sock_fd;
-
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
2. 設置套接字爲非阻塞模式
-
-
#if 1
-
int flags = fcntl(sock_fd, F_GETFL, 0);
-
fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);
-
#else
-
int imode = 1;
-
ioctl(sock_fd, FIONBIO, &imode);
3. 調用connect進行連接
-
struct sockaddr_in addr;
-
addr.sin_family = AF_INET;
-
addr.sin_port = htons(PEER_PORT);
-
addr.sin_addr.s_addr = inet_addr(PEER_IP);
-
int ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
-
if (0 == res)
-
{
-
printf("socket connect succeed immediately.\n");
-
ret = 0;
-
}
-
else
-
{
-
printf("get the connect result by select().\n");
-
if (errno == EINPROGRESS)
-
{
-
....
-
}
-
}
connect會立即返回,可能返回成功,也可能返回失敗。如果連接的服務器在同一臺主機上,那麼在調用connect 建立連接時,連接通常會立即建立成功(我們必須處理這種情況)。
4.調用select(),通過FD_ISSET()檢查套接口是否可寫,確定連接請求是否完成
-
fd_set rfds, wfds;
-
struct timeval tv;
-
-
FD_ZERO(&rfds);FD_ZERO(&wfds);
-
FD_SET(sock_fd, &rfds);
-
FD_SET(sock_fd, &wfds);
-
-
tv.tv_sec = 10;
-
tv.tv_usec = 0;
-
int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);
-
switch (selres)
-
{
-
case -1:
-
printf("select error\n");
-
ret = -1;
-
break;
-
case 0:
-
printf("select time out\n");
-
ret = -1;
-
break;
-
default:
-
if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))
-
{
-
.....
-
}
-
}
對於無連接的socket類型(SOCK_DGRAM),客戶端也可以調用connect進行連接,此連接實際上並不建立類似SOCK_STREAM的連接,而僅僅是在本地保存了對端的地址,這樣後續的讀寫操作可以默認以連接的對端爲操作對象。
Linux下常見的socket錯誤碼:
EACCES, EPERM:用戶試圖在套接字廣播標誌沒有設置的情況下連接廣播地址或由於防火牆策略導致連接失敗。
EADDRINUSE 98:Address already in use(本地地址處於使用狀態)
EAFNOSUPPORT 97:Address family not supported by protocol(參數serv_add中的地址非合法地址)
EAGAIN:沒有足夠空閒的本地端口。
EALREADY 114:Operation already in progress(套接字爲非阻塞套接字,並且原來的連接請求還未完成)
EBADF 77:File descriptor in bad state(非法的文件描述符)
ECONNREFUSED 111:Connection refused(遠程地址並沒有處於監聽狀態)
EFAULT:指向套接字結構體的地址非法。
EINPROGRESS 115:Operation now in progress(套接字爲非阻塞套接字,且連接請求沒有立即完成)
EINTR:系統調用的執行由於捕獲中斷而中止。
EISCONN 106:Transport endpoint is already connected(已經連接到該套接字)
ENETUNREACH 101:Network is unreachable(網絡不可到達)
ENOTSOCK 88:Socket operation on non-socket(文件描述符不與套接字相關)
ETIMEDOUT 110:Connection timed out(連接超時)
測試代碼:
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <sys/types.h>
-
#include <errno.h>
-
#include <unistd.h>
-
#include <fcntl.h>
-
#include <sys/select.h>
-
#include<sys/ioctl.h>
-
-
-
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
-
-
#define PEER_IP "192.254.1.1"
-
#define PEER_PORT 7008
-
int main(int argc, char **argv)
-
{
-
int ret = 0;
-
int sock_fd;
-
int flags;
-
struct sockaddr_in addr;
-
-
-
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
-
-
-
#if 1
-
flags = fcntl(sock_fd, F_GETFL, 0);
-
fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);
-
#else
-
int imode = 1;
-
ioctl(sock_fd, FIONBIO, &imode);
-
#endif
-
-
-
-
addr.sin_family = AF_INET;
-
addr.sin_port = htons(PEER_PORT);
-
addr.sin_addr.s_addr = inet_addr(PEER_IP);
-
int res = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
-
if (0 == res)
-
{
-
printf("socket connect succeed immediately.\n");
-
ret = 0;
-
}
-
else
-
{
-
printf("get the connect result by select().\n");
-
if (errno == EINPROGRESS)
-
{
-
int times = 0;
-
while (times++ < 5)
-
{
-
fd_set rfds, wfds;
-
struct timeval tv;
-
-
printf("errno = %d\n", errno);
-
FD_ZERO(&rfds);
-
FD_ZERO(&wfds);
-
FD_SET(sock_fd, &rfds);
-
FD_SET(sock_fd, &wfds);
-
-
-
tv.tv_sec = 10;
-
tv.tv_usec = 0;
-
int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);
-
switch (selres)
-
{
-
case -1:
-
printf("select error\n");
-
ret = -1;
-
break;
-
case 0:
-
printf("select time out\n");
-
ret = -1;
-
break;
-
default:
-
if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))
-
{
-
#if 0
-
int errinfo, errlen;
-
if (-1 == getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &errinfo, &errlen))
-
{
-
printf("getsockopt return -1.\n");
-
ret = -1;
-
break;
-
}
-
else if (0 != errinfo)
-
{
-
printf("getsockopt return errinfo = %d.\n", errinfo);
-
ret = -1;
-
break;
-
}
-
-
ret = 0;
-
printf("connect ok?\n");
-
#else
-
#if 1
-
connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
-
int err = errno;
-
if (err == EISCONN)
-
{
-
printf("connect finished 111.\n");
-
ret = 0;
-
}
-
else
-
{
-
printf("connect failed. errno = %d\n", errno);
-
printf("FD_ISSET(sock_fd, &rfds): %d\n FD_ISSET(sock_fd, &wfds): %d\n", FD_ISSET(sock_fd, &rfds) , FD_ISSET(sock_fd, &wfds));
-
ret = errno;
-
}
-
#else
-
char buff[2];
-
if (read(sock_fd, buff, 0) < 0)
-
{
-
printf("connect failed. errno = %d\n", errno);
-
ret = errno;
-
}
-
else
-
{
-
printf("connect finished.\n");
-
ret = 0;
-
}
-
#endif
-
#endif
-
}
-
else
-
{
-
printf("haha\n");
-
}
-
}
-
-
if (-1 != selres && (ret != 0))
-
{
-
printf("check connect result again... %d\n", times);
-
continue;
-
}
-
else
-
{
-
break;
-
}
-
}
-
}
-
else
-
{
-
printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
-
ret = errno;
-
}
-
}
-
if (0 == ret)
-
{
-
send(sock_fd, "12345", sizeof("12345"), 0);
-
}
-
else
-
{
-
printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
-
}
-
-
close(sock_fd);
-
return ret;
-
}