http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268023.html講一下套接字模式和套接字I/O模型的區(qū)別。先說明一下,只針對Winsock,如果你要骨頭里挑雞蛋把UNIX下的套接字概念來往這里套,那就不關(guān)我的事。
套接字模式:
阻塞套接字和
非阻塞套接字。或者叫同步套接字和異步套接字。
套接字模型:描述如何對套接字的I/O行為進(jìn)行管理。
Winsock提供的I/O模型一共有五種:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先講解select。
1:select模型擇模(選型)
先看一下下面的這句代碼:
阻塞socket:
int iResult = recv(s, buffer,1024);
這是用來接收數(shù)據(jù)的,在默認(rèn)的阻塞模式下的套接字里,recv會阻塞在那里,直到套接字連接上有數(shù)據(jù)可讀,把數(shù)據(jù)讀到buffer里后recv函數(shù)才會返 回,不然就會一直阻塞在那里。
在單線程的程序里出現(xiàn)這種情況會導(dǎo)致主線程(單線程程序里只有一個默認(rèn)的主線程)被阻塞,這樣整個程序被鎖死在這里,如果永 遠(yuǎn)沒數(shù)據(jù)發(fā)送過來,那么程序就會被永遠(yuǎn)鎖死。這個問題可以用多線程解決,但是在有多個套接字連接的情況下,這不是一個好的選擇,擴(kuò)展性很差。
非阻塞 socket:
再看代碼:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);
//-------------------------
// Initialize Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != NO_ERROR)
printf("Error at WSAStartup()\n");
//-------------------------
// Create a SOCKET object.
SOCKET m_socket;
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
WSACleanup();
return;
}
//-------------------------
// Set the socket I/O mode: In this case FIONBIO
// enables or disables the blocking mode for the
// socket based on the numerical value of iMode.
// If iMode = 0, blocking is enabled;
// If iMode != 0, non-blocking mode is enabled.
int iMode = 0;
ioctlsocket(m_socket, FIONBIO, (u_long FAR*) &iMode);
這一次recv的調(diào)用不管套接字連接上有沒有數(shù)據(jù)可以接收都會馬上返回。原因就在于我們用ioctlsocket把套接字設(shè)置為非阻塞模式了。不過你跟蹤 一下就會發(fā)現(xiàn),在沒有數(shù)據(jù)的情況下,recv確實(shí)是馬上返回了,但是也返回了一個錯誤:WSAEWOULDBLOCK,意思就是請求的操作沒有成功完成。 看到這里很多人可能會說,那么就重復(fù)調(diào)用recv并檢查返回值,直到成功為止,但是這樣做效率很成問題,開銷太大。
多線程來解決使用阻塞套接字存在的問題:
多線程來解決阻塞套接字的方法是為阻塞套接字的IO操作創(chuàng)建單獨(dú)的線程,阻塞的套接字IO操作放在單獨(dú)的線程中,而不會因?yàn)樘捉幼諭O操作的阻塞造成整個主線程的阻塞,但是這樣也會造成一定的問題:
1) 如果是多個套接字的場合通過多線程來解決主線程阻塞就會顯得不合適了,server端創(chuàng)建一個監(jiān)聽socket來負(fù)責(zé)監(jiān)聽連接,而為accept函數(shù)
為每個client端連接創(chuàng)建一個套接字,這樣就會創(chuàng)建很多的套接字。如果是創(chuàng)建不同的套接字則應(yīng)該創(chuàng)建多個線程,而每個線程的線程函數(shù)是
不同的,這樣就造成了所謂的擴(kuò)展性很差。
2)如果不是每個連接創(chuàng)建一個套接字的話,duoxanch方法比較直觀,程序非常簡單而且可移植性好,但是不能利用平臺相關(guān)的特性。例如,如 果連接數(shù)增多的時候(成千上萬的連接),那么線程數(shù)成倍增長,操作 系統(tǒng)忙于頻繁的線程間切換,而且大部分線程在其生命周期內(nèi)都是處于非活動狀態(tài)的,這大大浪費(fèi)了系統(tǒng)的資源。所以,如果你已經(jīng)知道你的代碼只會運(yùn)行在 Windows平臺上,建議采用Winsock I/O模型。
微軟提供了select函數(shù)來解決這個問題:
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一個參數(shù)不要管,會被系統(tǒng)忽略的。第二個參數(shù)是用來檢查套接字可讀性,也就說檢查套接字上是否有數(shù)據(jù)可讀,同樣,第三個參數(shù)用來檢查數(shù)據(jù)是否可以發(fā)出。最后一個是檢查是否有帶外數(shù)據(jù)可讀取。
最后一個參數(shù)是用來設(shè)置select等待多久的,是個結(jié)構(gòu):
struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果將這個結(jié)構(gòu)設(shè)置為(0,0),那么select函數(shù)會馬上返回。
說了這么久,select的作用到底是什么?
他的作用就是:
1)防止在在阻塞模式的套接字里被鎖死
2)避免在非阻塞套接字里重復(fù)檢查WSAEWOULDBLOCK錯誤。
他的工作流程如下:
1:用FD_ZERO宏來初始化我們感興趣的fd_set,也就是select函數(shù)的第二三四個參數(shù)。
2:用FD_SET宏來將套接字句柄分配給相應(yīng)的fd_set。
3:調(diào)用select函數(shù)。
4:用FD_ISSET對套接字句柄進(jìn)行檢查,如果我們所關(guān)注的那個套接字句柄仍然在開始分配的那個fd_set里,那么說明馬上可以進(jìn)行相應(yīng)的IO操 作。比如一個分配給select第一個參數(shù)的套接字句柄在select返回后仍然在select第一個參數(shù)的fd_set里,那么說明當(dāng)前數(shù)據(jù)已經(jīng)來了, 馬上可以讀取成功而不會被阻塞。
1 #include "stdafx.h"
2 #include <iostream>
3 #include <winsock2.h>
4 #include <windows.h>
5
6 #define TRACE ATLTrace //必須要加上這個宏定義,否則在WIN32的控制臺程序中是不能直接用的
7
8 #define InternetAddr "127.0.0.1"
9 #define iPort 5055
10
11 #pragma comment(lib, "ws2_32.lib")
12
13 int _tmain(int argc, _TCHAR* argv[])
14 {
15 WSADATA wsa;
16 WORD wVersionRequested;
17 int err;
18
19 wVersionRequested = MAKEWORD( 2, 2 );
20 err = WSAStartup( wVersionRequested, &wsa);
21 if ( err != 0 ) {
22 //Tell the user that we could not find a usable
23 //WinSock DLL.
24 TRACE("你忘記添加WinSock DLL了\n");
25 WSACleanup();
26 return 1;
27 }
28
29 // Create a SOCKET for listening for incoming connection requests
30 SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
31
32 sockaddr_in server;
33
34 server.sin_family = AF_INET;
35 server.sin_addr.s_addr = inet_addr(InternetAddr);
36 server.sin_port = htons(iPort);
37 //Bind the socket.
38 int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
39 ret = listen(fdServer, 4);
40
41 SOCKET AcceptSocket;
42 fd_set fdread;
43 timeval tv;
44 int nSize;
45 //其實(shí)也算是輪訓(xùn),那么對阻塞socket用select和對使用非阻塞socket的優(yōu)點(diǎn)在哪?
46 //可能的優(yōu)點(diǎn)就是避免在非阻塞套接字里重復(fù)檢查WSAEWOULDBLOCK錯誤。
47 while(1)
48 {
49
50 FD_ZERO(&fdread);//初始化fd_set
51 FD_SET(fdServer, &fdread);//分配套接字句柄到相應(yīng)的fd_set
52
53 tv.tv_sec = 2;//這里我們打算讓select等待兩秒后返回,避免被鎖死,也避免馬上返回
54 tv.tv_usec = 0;
55
56 select(0, &fdread, NULL, NULL, &tv);
57
58 nSize = sizeof(server);
59 //先判斷fdServer是否還在fd_set內(nèi)來判斷是否可以讀,這樣就避免因?yàn)?nbsp;accept在等待
60 //時造成的阻塞
61 if (FD_ISSET(fdServer, &fdread))
62 //如果套接字句柄還在fd_set里,說明客戶端已經(jīng)有connect的請求發(fā)過來了,
63 //馬上可以accept成功
64 {
65 AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
66 break;
67 }
68 else
69 //還沒有客戶端的connect請求,我們可以去做別的事,避免像沒有用select方式
70 //的阻塞套接字程序被鎖死的情況,如果沒用select,當(dāng)程序運(yùn)行到accept的時候客戶
71 //端恰好沒有connect請求,那么程序就會被鎖死,做不了任何事情
72 {
73 //do something
74 MessageBox(NULL, "waiting
", "recv", MB_ICONINFORMATION);
75 //別的事做完后,繼續(xù)去檢查是否有客戶端連接請求
76 }
77 }
78
79 char buffer[128];
80 ZeroMemory(buffer, 128);
81
82 ret = recv(AcceptSocket,buffer,128,0);//這里同樣可以用select,用法和上面一樣
83
84 MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);
85
86 closesocket(AcceptSocket);
87 WSACleanup();
88 return 0;
89 }
90
select函數(shù)的返回值 :
函數(shù)失敗的返回值:調(diào)用失敗返回SOCKET_ERROR,超時返回0。
int ret;
if((ret=select(0,&fdread,NULL,NULL,NULL))==SOCKET_ERROR)
{
//Error Condition
}
if(ret > 0)//ret>0這個ret值表示滿足條件的socket的數(shù)量,不止一個socket滿足IO操作的條件
{
if(FD_ISSET(fdServer,&fdread))
{
//A read event has occured on socket fdServer
}
}