今天只想談一談關于阻塞和非阻塞的socket。
調用socket函數得到的socket的文件標示符,默認情況下是一個阻塞的socket,從應用角度,就是每當調用accept,recv,send
等函數的時候,如果對方沒有相應,那么進程會阻塞在那里,直到對方相應。這在應用中有很多不便,尤其是在windows環境中的編程,如果進程被阻塞那么
看上去有一點像死機的感覺。
其實把一個socket設置為非阻塞的也很簡單。但是,應用select函數,阻塞socket也可以到達類似的效果。一會詳細討論。
先考慮一個TCP的連接,當服務器程序listen以后,如果直接調用accept,那么進程會阻塞在那里,一直到被client端connect,然后
開啟一個新的線程處理客戶的服務,主線程繼續監聽請求。這樣可以實現同時處理多個客戶端連接,但是這樣是會阻塞主線程,不是一個好的辦法。其實,我當然也
可添加一個線程,在“后臺”accept,但是我覺得這里面有一個多線程操作同一個文件描述符socket的問題。也不是很好。也就是說,如果只用到線程
機制,并不能很好的解決阻塞的問題。
select函數可以對一套文件描述符操作(windows里叫handle,我覺得沒有什么本
質的區別。都是進程空間里面的一個整數而已),觀察這套文件描述符的可讀或可寫的情況。這樣就給我們提供這樣一個思路,只有當一個socket是可讀的,
也就是有遠程連接請求connect或是對套接口write,那么才去調用accept或是read,這樣就可以避免阻塞了。
以準備accept的套接口為例,如果調用socket函數得到的文件描述符為s=3,那么,我可以調用select監視所有4以下的文件描述符的讀寫特
性。
select(s+1,&fd_readset,&fd_writeset,&fd_errorset,&tv_time).
這個函數是內核等待特定事件的函數,并不是只有套接口可以用。原型是
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
第一個參數表示觀察的最大的文件描述符,一般是你想要觀察的socket+1,
第二個參數是可讀文件描述符set,第三個是可寫set,第四個參數是error
set,第五個是超時的時間。如果在超時的時間內沒有觀察過任何改動,那么函數會清空這幾個set(我個人理解),并且返回0;如果出錯,會返回-1。否
則返回>0,并且只把可讀(可寫)的文件描述符保留在對應的set內。所以只要在調用select以前,把等待連接的套接口放到可讀set中,如果
發生connect,select會返回>0,這時候可以觀察一下套接口是否還在這個set中,如果是,則說明有請求。這個方法可以用于多個套接口
同時監聽多個端口的情況。也可以用于一個已經連接的套接口等待對方發送的消息,比如write。
對于set的操作,有如下幾個宏:FD_ZERO,FD_SET,FD_ISSET,FD_CLR.
fd_set myset; //定義描述字集數據類型
FD_ZERO (&myset); //對描述字集初始化
FD_SET(s, &myset); //打開描述字的第s位,也即把套接口加入set中
FD_ISSET(s, &myest) //測試描述字的第s位,這個宏一般是在select之后才會用到。
FD_CLR(s, &myset) // //關閉描述字的第s位,就是在set中清除s.
這個函數的使用方法,可以參見下面的程序。就不再細訴了。
FD_ZERO(&fs_ReadSet);
FD_SET(iSockets, &fs_ReadSet);
iSockNum = iSockets+1;
while(1)
{
FD_ZERO(&fs_WriteSet);
for (iSocketz = 0; iSocketz<iSockNum; iSocketz++)
{
if (FD_ISSET(iSocketz, &fs_ReadSet))
FD_SET(iSocketz, &fs_WriteSet);
}
tv_time.tv_sec = 2;
tv_time.tv_usec = 500000;
iRtn = select(iSockNum, &fs_WriteSet, NULL, NULL, &tv_time);
if (iRtn == -1)
{
printf("function select error\n");
goto error;
}
else if(iRtn == 0)
{
continue;
}
if (FD_ISSET(iSockets, &fs_WriteSet))
{
iSockLen = sizeof(sa_client);
iSocketz = accept(iSockets, (struct sockaddr*)&sa_client, &iSockLen);
if (iSocketz == -1)
{
printf("function accept error \n");
goto error;
}
printf("z : %d\n", iSocketz);
if (iSocketz >=MAX_CLIENT)
{
close(iSocketz);
continue;
}
if (iSockNum < iSocketz+1)
iSockNum = iSocketz+1;
FD_SET(iSocketz, &fs_ReadSet);
}
for (iSocketz=iSockets+1; iSocketz < iSockNum; iSocketz++)
{
if (FD_ISSET(iSocketz, &fs_WriteSet))
{
memset(pRcvBuf, 0, BUFFERSIZE);
memset(pSndBuf, 0, BUFFERSIZE);
iRtn = read(iSocketz, pRcvBuf, BUFFERSIZE);
if (iRtn == -1)
{
printf("function read error \n");
goto error;
}
printf("%s\n", pRcvBuf);
pSndBuf[0] = 0;
strcpy(pSndBuf, "fuck");
iBufLen = sizeof("fuck");
iRtn = write(iSocketz, pSndBuf, iBufLen);
if (iRtn == -1)
{
printf("function write error \n");
goto error;
}
if(strcmp(pRcvBuf,"exit")==0)
{
FD_CLR(iSocketz, &fs_ReadSet);
close(iSocketz);
}
}
}
for (iSocketz = iSockNum-1; (iSocketz >iSockets&&!FD_ISSET(iSocketz, &fs_ReadSet)); iSocketz = iSockNum-1)
{
iSockNum = iSocketz;
}
//printf("isockNum: %d\n",iSockNum);
}
至于套接口的通信,我還想說一點,套接口和文件管道差不多,一方讀一方寫,read會阻塞到對方write,而write不會阻塞。應該是這樣了。如果我個人對這個部分的理解有錯誤。會在以后改正。