講一下套接字模式和套接字I/O模型的區(qū)別。先說(shuō)明一下,只針對(duì)Winsock,如果你要骨頭里挑雞蛋把UNIX下的套接字概念來(lái)往這里套,那就不關(guān)我的事。
套接字模式:阻塞套接字和非阻塞套接字?;蛘呓型教捉幼趾彤惒教捉幼帧?br>套接字模型:描述如何對(duì)套接字的I/O行為進(jìn)行管理。
Winsock提供的I/O模型一共有五種:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先講解select。
1:select模型(選擇模型)
先看一下下面的這句代碼:
int iResult = recv(s, buffer,1024);
這是用來(lái)接收數(shù)據(jù)的,在默認(rèn)的阻塞模式下的套接字里,recv會(huì)阻塞在那里,直到套接字連接上有數(shù)據(jù)可讀,把數(shù)據(jù)讀到buffer里后recv函數(shù)才會(huì)返回,不然就會(huì)一直阻塞在那里。在單線程的程序里出現(xiàn)這種情況會(huì)導(dǎo)致主線程(單線程程序里只有一個(gè)默認(rèn)的主線程)被阻塞,這樣整個(gè)程序被鎖死在這里,如果永遠(yuǎn)沒(méi)數(shù)據(jù)發(fā)送過(guò)來(lái),那么程序就會(huì)被永遠(yuǎn)鎖死。這個(gè)問(wèn)題可以用多線程解決,但是在有多個(gè)套接字連接的情況下,這不是一個(gè)好的選擇,擴(kuò)展性很差。Select模型就是為了解決這個(gè)問(wèn)題而出現(xiàn)的。
再看代碼:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);
這一次recv的調(diào)用不管套接字連接上有沒(méi)有數(shù)據(jù)可以接收都會(huì)馬上返回。原因就在于我們用ioctlsocket把套接字設(shè)置為非阻塞模式了。不過(guò)你跟蹤一下就會(huì)發(fā)現(xiàn),在沒(méi)有數(shù)據(jù)的情況下,recv確實(shí)是馬上返回了,但是也返回了一個(gè)錯(cuò)誤:WSAEWOULDBLOCK,意思就是請(qǐng)求的操作沒(méi)有成功完成??吹竭@里很多人可能會(huì)說(shuō),那么就重復(fù)調(diào)用recv并檢查返回值,直到成功為止,但是這樣做效率很成問(wèn)題,開銷太大。
感謝天才的微軟工程師吧,他們給我們提供了好的解決辦法。
先看看select函數(shù)
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一個(gè)參數(shù)不要管,會(huì)被系統(tǒng)忽略的。第二個(gè)參數(shù)是用來(lái)檢查套接字可讀性,也就說(shuō)檢查套接字上是否有數(shù)據(jù)可讀,同樣,第三個(gè)參數(shù)用來(lái)檢查數(shù)據(jù)是否可以發(fā)出。最后一個(gè)是檢查是否有帶外數(shù)據(jù)可讀取。
參數(shù)詳細(xì)的意思請(qǐng)去看MSDN,這里限于篇幅不詳細(xì)解釋了。
最后一個(gè)參數(shù)是用來(lái)設(shè)置select等待多久的,是個(gè)結(jié)構(gòu):
struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果將這個(gè)結(jié)構(gòu)設(shè)置為(0,0),那么select函數(shù)會(huì)馬上返回。
說(shuō)了這么久,select的作用到底是什么?
他的作用就是:防止在在阻塞模式的套接字里被鎖死,避免在非阻塞套接字里重復(fù)檢查WSAEWOULDBLOCK錯(cuò)誤。
他的工作流程如下:
1:用FD_ZERO宏來(lái)初始化我們感興趣的fd_set,也就是select函數(shù)的第二三四個(gè)參數(shù)。
2:用FD_SET宏來(lái)將套接字句柄分配給相應(yīng)的fd_set。
3:調(diào)用select函數(shù)。
4:用FD_ISSET對(duì)套接字句柄進(jìn)行檢查,如果我們所關(guān)注的那個(gè)套接字句柄仍然在開始分配的那個(gè)fd_set里,那么說(shuō)明馬上可以進(jìn)行相應(yīng)的IO操作。比如一個(gè)分配給select第一個(gè)參數(shù)的套接字句柄在select返回后仍然在select第一個(gè)參數(shù)的fd_set里,那么說(shuō)明當(dāng)前數(shù)據(jù)已經(jīng)來(lái)了,馬上可以讀取成功而不會(huì)被阻塞。
下面給出一個(gè)簡(jiǎn)單的select模型的服務(wù)端套接字。
#include “iostream.h”
#include “winsock2.h”
#include “windows.h”
#define InternetAddr "127.0.0.1"
#define iPort 5055
#pragma comment(lib, "ws2_32.lib")
void main()
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(InternetAddr);
server.sin_port = htons(iPort);
int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
ret = listen(fdServer, 4);
SOCKET AcceptSocket;
fd_set fdread;
timeval tv;
int nSize;
while(1)
{
FD_ZERO(&fdread);//初始化fd_set
FD_SET(fdServer, &fdread);//分配套接字句柄到相應(yīng)的fd_set
tv.tv_sec = 2;//這里我們打算讓select等待兩秒后返回,避免被鎖死,也避免馬上返回
tv.tv_usec = 0;
select(0, &fdread, NULL, NULL, &tv);
nSize = sizeof(server);
if (FD_ISSET(fdServer, &fdread))//如果套接字句柄還在fd_set里,說(shuō)明客戶端已經(jīng)有connect的請(qǐng)求發(fā)過(guò)來(lái)了,馬上可以accept成功
{
AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
break;
}
else//還沒(méi)有客戶端的connect請(qǐng)求,我們可以去做別的事,避免像沒(méi)有用select方式的阻塞套接字程序被鎖死的情況,如果沒(méi)用select,當(dāng)程序運(yùn)行到accept的時(shí)候客戶端恰好沒(méi)有connect請(qǐng)求,那么程序就會(huì)被鎖死,做不了任何事情
{
//do something
::MessageBox(NULL, "waiting...", "recv", MB_ICONINFORMATION);//別的事做完后,繼續(xù)去檢查是否有客戶端連接請(qǐng)求
}
}
char buffer[128];
ZeroMemory(buffer, 128);
ret = recv(AcceptSocket,buffer,128,0);//這里同樣可以用select,用法和上面一樣
::MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);
closesocket(AcceptSocket);
WSACleanup();
return;
}
基本上就這樣,個(gè)人感覺select模型用處不是很大,我只用過(guò)一次,去年寫端口掃描器的時(shí)候用select來(lái)檢查超時(shí)。
感覺講得不是很清楚,雖然東西我很明白,但是要講解出來(lái)講解得很清楚真不容易。
不知道大家能不能看懂,如果看得過(guò)程中有什么問(wèn)題就問(wèn)吧。