答:解決基本C/S模型中,accept()、recv()、send()阻塞的問題數組
其實select模型解決了 實現多個客戶端連接,與多個客戶端分別通訊
兩個模型都存在recv(),send()執行阻塞問題
做用:定義一個用來裝socket的結構體服務器
#ifndef FD_SETSIZE #define FD_SETSIZE 64 /*默認64個*/ #endif /* FD_SETSIZE */ typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
默認裝socket大小爲64,能夠經過在winsock2.h頭文件前聲明宏,給一個更大的值socket
#define FD_SETSIZE 128 #include <WinSock2.h>
由於原理就是不停遍歷檢測,越多效率越低,延遲越大,因此合適大小最好。
select模型應用,就是小用戶量訪問。函數
操做宏 | 做用 | 代碼 |
---|---|---|
FD_ZERO | 將客戶端socket集合清零 | FD_ZERO(&clientSockets); |
FD_SET | 添加一個socket(超過默認值大小再也不處理) | FD_SET(socketListen,&clientSockets); |
FD_CLR | 從集合中刪除指定的socket,必定要close,手動釋放 | FD_CLR(socketListen, &clientSockets);closesocket(socketListen); |
FD_ISSET | 查詢socket是否在集合中,不存在返回0,存在返回非0 | int a = FD_ISSET(socketListen, &clientSockets); |
做用:監視socket集合,若是某個socket發生響應(連接或者收發數據),經過返回值以及參數告訴咱們哪一個socket有響應spa
int WSAAPI select( int nfds, /*填0*/ fd_set *readfds, /*檢查是否有可讀的socket*/ fd_set *writefds, /*檢查是否有可寫的socket*/ fd_set *exceptfds, /*檢查socket上的異常錯誤*/ const timeval *timeout );
爲了兼容Berkeley sockets線程
用法和參數二、3同樣,將有異常錯誤的socket裝進來,反饋給咱們3d
/*獲得異常socket上的具體錯誤碼*/ getsockopt(socket, SOL_SOCKET, SO_ERROR, buf, buflen);
若是調用這個函數(針對這個getsockopt函數)沒有錯誤,返回0,不然返回SOCKET_ERROR,而且能夠調用WSAGetLastError來獲得錯誤代碼。code
一個結構體blog
struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ };
當客戶端沒有響應時,select能夠選擇等一段時間,不等,等到有socket響應,三種方式圖片
tv_sec | tv_usec | 做用 |
---|---|---|
0 | 0 | 不等待,馬上返回 |
3 | 4 | 等待3秒4微秒沒有消息再返回 |
NULL :死等,直到有socket響應
fd_set allsockets; //清零 FD_ZERO(&allSockets); //服務器裝進去 FD_SET(socketServer, &allSockets); while (1) { fd_set readSockets = allSockets; fd_set writeSockets = allSockets; fd_set errorSockets = allSockets; //時間段 struct timeval st; st.tv_sec = 3; st.tv_usec = 0; //select int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st); if (0 == nRes) //沒有響應的socket { continue; } else if (nRes > 0) { //處理錯誤 for (u_int i = 0; i < errorSockets.fd_count; i++) { char str[100] = { 0 }; int len = 99; if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len)) { printf("沒法獲得錯誤信息\n"); } printf("%s\n", str); } for (u_int i = 0; i < writeSockets.fd_count; i++) { //printf("服務器%d,%d:可寫\n", socketServer, writeSockets.fd_array[i]); if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 0)) { int a = WSAGetLastError(); } } //有響應 for (u_int i = 0; i < readSockets.fd_count; i++) { if (readSockets.fd_array[i] == socketServer) { //accept SOCKET socketClient = accept(socketServer, NULL, NULL); if (INVALID_SOCKET == socketClient) { //連接出錯 continue; } FD_SET(socketClient, &allSockets); //send } else { char strBuf[1500] = { 0 }; //客戶端吧 int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0); //send if (0 == nRecv) { //客戶端下線了 //從集合中拿掉 SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //釋放 closesocket(socketTemp); } else if (0 < nRecv) { //接收到了消息 printf(strBuf); } else //SOCK_ERROR { //強制下線也叫出錯 10054 int a = WSAGetLastError(); switch (a) { case 10054: { SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //釋放 closesocket(socketTemp); } } } } } }
將一組socket數組投遞給系統,而後在系統裏去查詢socket是否有信號,過程都是在select函數裏面去進行的,再到返回有操做的socket集合
select()函數執行遍歷和返回有響應的socket,整個過程當中也是阻塞的。
等待時間 | 阻塞 |
---|---|
不等待 | 執行阻塞 |
半等待 | 執行阻塞+軟阻塞 |
全等待 | 執行阻塞+硬阻塞 |
使用CS模型時,當連接了一個客戶端,執行完了recv,while循環又回到了accept(),傻等着客戶端來連接,沒法多客戶端連接通訊。
使用select模型時,是select在遍歷着socket數組,有響應的socket再取出來,沒有就一直遍歷,雖然select()函數的執行也是阻塞的。能夠理解爲,每次都是在處理只有響應的socket,因此能夠進行多客戶端連接通訊。
當第一個客戶端socket來連接時,select()函數將服務端socket從allsocket取出來,將新建的含有客戶端socket添加到allsockets數組中,接着又在遍歷allsocket,查看着時候有響應,因此不會像CS模型那種,在accept()函數阻塞着,傻等着。