今天只想談一談關(guān)于阻塞和非阻塞的socket。
調(diào)用socket函數(shù)得到的socket的文件標(biāo)示符,默認(rèn)情況下是一個(gè)阻塞的socket,從應(yīng)用角度,就是每當(dāng)調(diào)用accept,recv,send
等函數(shù)的時(shí)候,如果對(duì)方?jīng)]有相應(yīng),那么進(jìn)程會(huì)阻塞在那里,直到對(duì)方相應(yīng)。這在應(yīng)用中有很多不便,尤其是在windows環(huán)境中的編程,如果進(jìn)程被阻塞那么
看上去有一點(diǎn)像死機(jī)的感覺(jué)。
其實(shí)把一個(gè)socket設(shè)置為非阻塞的也很簡(jiǎn)單。但是,應(yīng)用select函數(shù),阻塞socket也可以到達(dá)類似的效果。一會(huì)詳細(xì)討論。
先考慮一個(gè)TCP的連接,當(dāng)服務(wù)器程序listen以后,如果直接調(diào)用accept,那么進(jìn)程會(huì)阻塞在那里,一直到被client端connect,然后
開(kāi)啟一個(gè)新的線程處理客戶的服務(wù),主線程繼續(xù)監(jiān)聽(tīng)請(qǐng)求。這樣可以實(shí)現(xiàn)同時(shí)處理多個(gè)客戶端連接,但是這樣是會(huì)阻塞主線程,不是一個(gè)好的辦法。其實(shí),我當(dāng)然也
可添加一個(gè)線程,在“后臺(tái)”accept,但是我覺(jué)得這里面有一個(gè)多線程操作同一個(gè)文件描述符socket的問(wèn)題。也不是很好。也就是說(shuō),如果只用到線程
機(jī)制,并不能很好的解決阻塞的問(wèn)題。
select函數(shù)可以對(duì)一套文件描述符操作(windows里叫handle,我覺(jué)得沒(méi)有什么本
質(zhì)的區(qū)別。都是進(jìn)程空間里面的一個(gè)整數(shù)而已),觀察這套文件描述符的可讀或可寫(xiě)的情況。這樣就給我們提供這樣一個(gè)思路,只有當(dāng)一個(gè)socket是可讀的,
也就是有遠(yuǎn)程連接請(qǐng)求connect或是對(duì)套接口write,那么才去調(diào)用accept或是read,這樣就可以避免阻塞了。
以準(zhǔn)備accept的套接口為例,如果調(diào)用socket函數(shù)得到的文件描述符為s=3,那么,我可以調(diào)用select監(jiān)視所有4以下的文件描述符的讀寫(xiě)特
性。
select(s+1,&fd_readset,&fd_writeset,&fd_errorset,&tv_time).
這個(gè)函數(shù)是內(nèi)核等待特定事件的函數(shù),并不是只有套接口可以用。原型是
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
第一個(gè)參數(shù)表示觀察的最大的文件描述符,一般是你想要觀察的socket+1,
第二個(gè)參數(shù)是可讀文件描述符set,第三個(gè)是可寫(xiě)set,第四個(gè)參數(shù)是error
set,第五個(gè)是超時(shí)的時(shí)間。如果在超時(shí)的時(shí)間內(nèi)沒(méi)有觀察過(guò)任何改動(dòng),那么函數(shù)會(huì)清空這幾個(gè)set(我個(gè)人理解),并且返回0;如果出錯(cuò),會(huì)返回-1。否
則返回>0,并且只把可讀(可寫(xiě))的文件描述符保留在對(duì)應(yīng)的set內(nèi)。所以只要在調(diào)用select以前,把等待連接的套接口放到可讀set中,如果
發(fā)生connect,select會(huì)返回>0,這時(shí)候可以觀察一下套接口是否還在這個(gè)set中,如果是,則說(shuō)明有請(qǐng)求。這個(gè)方法可以用于多個(gè)套接口
同時(shí)監(jiān)聽(tīng)多個(gè)端口的情況。也可以用于一個(gè)已經(jīng)連接的套接口等待對(duì)方發(fā)送的消息,比如write。
對(duì)于set的操作,有如下幾個(gè)宏:FD_ZERO,FD_SET,FD_ISSET,FD_CLR.
fd_set myset; //定義描述字集數(shù)據(jù)類型
FD_ZERO (&myset); //對(duì)描述字集初始化
FD_SET(s, &myset); //打開(kāi)描述字的第s位,也即把套接口加入set中
FD_ISSET(s, &myest) //測(cè)試描述字的第s位,這個(gè)宏一般是在select之后才會(huì)用到。
FD_CLR(s, &myset) // //關(guān)閉描述字的第s位,就是在set中清除s.
這個(gè)函數(shù)的使用方法,可以參見(jiàn)下面的程序。就不再細(xì)訴了。
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);
}
至于套接口的通信,我還想說(shuō)一點(diǎn),套接口和文件管道差不多,一方讀一方寫(xiě),read會(huì)阻塞到對(duì)方write,而write不會(huì)阻塞。應(yīng)該是這樣了。如果我個(gè)人對(duì)這個(gè)部分的理解有錯(cuò)誤。會(huì)在以后改正。