WinSock I/O 模型 -- Select 模型
簡(jiǎn)介
Select 模型是 WinSock 中最常見(jiàn)的 I/O 模型,這篇文章我們就來(lái)看看如何使用 Select api 來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 TCP 服務(wù)器.
API 基礎(chǔ)
Select 模型依賴 WinSock API Select 來(lái)檢查當(dāng)前 Socket 是否可寫或者可讀。
使用這個(gè) API 的優(yōu)點(diǎn)是我們不需要使用阻塞的 Socket API (recv, send) 來(lái)等待 Socket 狀態(tài)準(zhǔn)備就緒,我們可以異步的檢查 Socket 的狀態(tài)來(lái)進(jìn)行讀數(shù)據(jù)或者寫數(shù)據(jù).
Select 方法的聲明如下:
int WSAAPI select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const timeval *timeout );其中:
nfds: 直接忽略即可,該參數(shù)的設(shè)計(jì)是為了兼容 Berkeley Socket 的實(shí)現(xiàn)
redfds: 返回值,當(dāng)前可讀的 socket 的集合
writefds: 返回值,當(dāng)前可寫的 socket 的集合
exceptfds:返回值,當(dāng)前發(fā)生錯(cuò)誤的 socket 的集合
返回值: 表示當(dāng)前準(zhǔn)備就緒的 socket 的數(shù)量。 這里的準(zhǔn)備就緒包含 可讀,可寫,或者儲(chǔ)出錯(cuò)的socket。如果返回 SOCKET_ERROR,表示發(fā)生錯(cuò)誤,可以使用 WSAGetLastError 來(lái)獲取具體的錯(cuò)誤碼。
fd_set
fd_set 是一個(gè) socket 的集合,作為 select 方法的輸入輸出參數(shù).
這里使用到的操作包括:
- FD_ZERO : 重置 fd_set
- FD_SET: 將 socket handle 添加到當(dāng)前 fd_set
- FD_ISSET: 檢查某個(gè) socket handle 是否處于當(dāng)前 fd_set
實(shí)現(xiàn)思路
解析來(lái)我們通過(guò)一個(gè)例子看看如何使用 Select.
實(shí)例
本文的例子可以直接拷貝運(yùn)行。 讀者如果不需要運(yùn)行,直接注意加注釋的代碼段即可.
服務(wù)器實(shí)現(xiàn)
#include <WinSock2.h> #include <Windows.h> #include <stdio.h>#pragma comment(lib, "ws2_32")#define DEFALT_PORT 8080 #define DATA_BUFFER 8192typedef struct _SOCKET_CONTEXT {SOCKET Socket;WSABUF DataBuf;OVERLAPPED Overlapped;CHAR Buffer[DATA_BUFFER];DWORD BytesSEND;DWORD BytesRECV; } SOCKET_CONTEXT, * LPSOCKET_CONTEXT;BOOL CreateSocketContext(SOCKET s); void FreeSocketContext(DWORD Index);DWORD TotalSockets = 0; LPSOCKET_CONTEXT SocketArray[FD_SETSIZE];int main() {INT Ret;WSADATA wsaData;SOCKET ListenSocket;SOCKET AcceptSocket;SOCKADDR_IN Addr;ULONG NonBlock = 1;FD_SET ReadSet;FD_SET WriteSet;DWORD Total;DWORD Flags;DWORD RecvBytes;DWORD SentBytes;DWORD i;if ((Ret = WSAStartup(0x0202, &wsaData)) != 0) {printf("WSAStartup failed with error %d\n", Ret);WSACleanup();return 1;}if ((ListenSocket = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {printf("WSASocket failed with error %d\n", WSAGetLastError());return 1;}Addr.sin_family = AF_INET;Addr.sin_addr.s_addr= htonl(INADDR_ANY);Addr.sin_port = htons(DEFALT_PORT);if (bind(ListenSocket, (PSOCKADDR) &Addr, sizeof(Addr)) == SOCKET_ERROR) {printf("bind failed with error %d\n", WSAGetLastError());return 1;}if (listen(ListenSocket, 10)) {printf("listen failed with eror %d\n", WSAGetLastError());return 1;}// 設(shè)置監(jiān)聽(tīng)socket為異步模式if (ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR) {printf("ioctlsocket failed with error %d\n", WSAGetLastError());return 1;}while (TRUE) {// 清空 ReadSet 和 WriteSet,我們將該集合中放入我們關(guān)心的 socket handleFD_ZERO(&ReadSet);FD_ZERO(&WriteSet);// 將監(jiān)聽(tīng)socket 放入 ReadSet, 以便當(dāng)有新連接到來(lái)的時(shí)候,我們可以檢查到該事件FD_SET(ListenSocket, &ReadSet);// 我們同時(shí)也關(guān)心已經(jīng)建立的客戶段連接的可讀可寫狀態(tài),以便我們從客戶端接收數(shù)據(jù)或者寫數(shù)據(jù)// 這里一些小邏輯,直接忽略for (i = 0; i < TotalSockets; i++) {if (SocketArray[i]->BytesRECV > SocketArray[i]->BytesSEND) {FD_SET(SocketArray[i]->Socket, &WriteSet);} else {FD_SET(SocketArray[i]->Socket, &ReadSet);}}// 使用 select 檢查當(dāng)前 ReadSet 和 WriteSet 中的socket 是否有新的事件到來(lái)if ((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR) {printf("select failed with error %d\n", WSAGetLastError());return 1;}// 使用 FD_ISSET 判斷監(jiān)聽(tīng) socket 是否可以讀,也就是說(shuō)有新的連接到來(lái)// 如果有,調(diào)用 accept 來(lái)接收該新連接if (FD_ISSET(ListenSocket, &ReadSet)) {Total--;if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET) {NonBlock = 1;if (ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR) {printf("ioctlsocket failed with error %d\n", WSAGetLastError());return 1;}if (CreateSocketContext(AcceptSocket) == FALSE) {printf("CreateSocketContext failed");return 1;}} else {if (WSAGetLastError() != WSAEWOULDBLOCK) {printf("accept failed with error %d\n", WSAGetLastError());return 1;} else {printf("accept returns WSAEWOULDBLOCK\n");}}}// 接下來(lái)檢查可讀的客戶段連接for (i = 0; Total > 0 && i < TotalSockets; i++) {LPSOCKET_CONTEXT Ctx = SocketArray[i];if (FD_ISSET(Ctx->Socket, &ReadSet)) {Total--;Ctx->DataBuf.buf = Ctx->Buffer;Ctx->DataBuf.len = DATA_BUFFER;//當(dāng)前 socket 可讀,那么調(diào)用 WSARecv 從該 socket 讀取數(shù)據(jù)// 如果 WSARecv 返回 0, 是說(shuō)該連接已經(jīng)斷開(kāi)Flags = 0;if (WSARecv(Ctx->Socket, &(Ctx->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR) {if (WSAGetLastError() != WSAEWOULDBLOCK) {printf("WSARecv failed with error %d\n", WSAGetLastError());FreeSocketContext(i);} else {printf("WSARecv returns WSAEWOULDBLOCK");}continue;} else {Ctx->BytesRECV = RecvBytes;// If zero bytes are received, this indicates the peer closed the connection.if (RecvBytes == 0) {FreeSocketContext(i);continue;} else {printf("Recv %d bytes data from the socket %d\n", RecvBytes, Ctx->Socket);}}}// 接下來(lái)檢查可寫的客戶段連接if (FD_ISSET(Ctx->Socket, &WriteSet)) {Total--;Ctx->DataBuf.buf = Ctx->Buffer + Ctx->BytesSEND;Ctx->DataBuf.len = Ctx->BytesRECV - Ctx->BytesSEND;if (WSASend(Ctx->Socket, &(Ctx->DataBuf), 1, &SentBytes, 0, NULL, NULL) == SOCKET_ERROR) {if (WSAGetLastError() != WSAEWOULDBLOCK) {printf("WSASend failed with error %d\n", WSAGetLastError());FreeSocketContext(i);} else {printf("WSASend returns WSAEWOULDBLOCK");}continue;} else {Ctx->BytesSEND += SentBytes;if (Ctx->BytesSEND == Ctx->BytesRECV) {Ctx->BytesSEND = 0;Ctx->BytesRECV = 0;}}}}} }BOOL CreateSocketContext(SOCKET s) {LPSOCKET_CONTEXT Ctx;printf("Accepted a new socket %d\n", s);if ((Ctx = (LPSOCKET_CONTEXT) GlobalAlloc(GPTR, sizeof(SOCKET_CONTEXT))) == NULL) {printf("GlobalAlloc() failed with error %d\n", GetLastError());return FALSE;}Ctx->Socket = s;Ctx->BytesSEND = 0;Ctx->BytesRECV = 0;SocketArray[TotalSockets] = Ctx;TotalSockets++;return TRUE; }void FreeSocketContext(DWORD Index) {DWORD i;LPSOCKET_CONTEXT Ctx = SocketArray[Index];printf("Closing socket %d\n", Ctx->Socket);closesocket(Ctx->Socket);GlobalFree(Ctx);for (i = Index; i < TotalSockets; i++) {SocketArray[i] = SocketArray[i + 1];}TotalSockets--; }客戶端實(shí)現(xiàn)
搭配該服務(wù)器,使用下面 client 實(shí)現(xiàn)進(jìn)行測(cè)試。 這里僅僅做測(cè)試用,忽略了大部分的錯(cuò)誤檢查.
#include <winsock2.h> #include <stdio.h> #include <stdlib.h>#define DEFAULT_COUNT 20 #define DEFAULT_PORT 8080 #define DEFAULT_BUFFER 2048 #define DEFAULT_MESSAGE "\'A test message from client\'"#pragma warning(disable:4996) #pragma comment(lib, "ws2_32")char szMessage[1024]; char szServer[128];int main(int argc, char **argv) {WSADATA wsaData;SOCKET ClientSocket;char szBuffer[DEFAULT_BUFFER];int ret, i;SOCKADDR_IN ServerAddr;struct hostent *host = NULL;WSAStartup(0x0202, &wsaData);strcpy_s(szMessage, sizeof(szMessage), DEFAULT_MESSAGE);strcpy_s(szServer, sizeof(szServer), "127.0.0.1");ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(DEFAULT_PORT);ServerAddr.sin_addr.s_addr = inet_addr(szServer);if (connect(ClientSocket, (struct sockaddr *) &ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR) {printf("connect failed with error %d\n", WSAGetLastError());return 1;}printf("Sending and receiving data if any...\n");for(i = 0; i < DEFAULT_COUNT; i++) {if ((ret = send(ClientSocket, szMessage, strlen(szMessage), 0)) == SOCKET_ERROR) {printf("send() failed with error %d\n", WSAGetLastError());break;}printf("send() is OK. Send %d bytes: %s\n", ret, szBuffer);if ((ret = recv(ClientSocket, szBuffer, DEFAULT_BUFFER, 0)) == SOCKET_ERROR) {printf("recv() failed with error %d\n", WSAGetLastError());break;}if (ret == 0) {printf("It is a graceful close!\n");break;}szBuffer[ret] = '\0';printf("recv() is OK. Received %d bytes: %s\n", ret, szBuffer);}if(closesocket(ClientSocket) == 0) {printf("closesocket() is OK!\n");} else {printf("closesocket() failed with error %d\n", WSAGetLastError());}WSACleanup();return 0; }END!!!
總結(jié)
以上是生活随笔為你收集整理的WinSock I/O 模型 -- Select 模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C语言学习及应用笔记之六:C语言exte
- 下一篇: 风机桨叶故障诊断(三) 识别桨叶——初步