日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSock I/O 模型 -- Select 模型

發布時間:2024/7/23 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock I/O 模型 -- Select 模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介

Select 模型是 WinSock 中最常見的 I/O 模型,這篇文章我們就來看看如何使用 Select api 來實現一個簡單的 TCP 服務器.

API 基礎

Select 模型依賴 WinSock API Select 來檢查當前 Socket 是否可寫或者可讀。

使用這個 API 的優點是我們不需要使用阻塞的 Socket API (recv, send) 來等待 Socket 狀態準備就緒,我們可以異步的檢查 Socket 的狀態來進行讀數據或者寫數據.

Select 方法的聲明如下:
int WSAAPI select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const timeval *timeout );

其中:
nfds: 直接忽略即可,該參數的設計是為了兼容 Berkeley Socket 的實現
redfds: 返回值,當前可讀的 socket 的集合
writefds: 返回值,當前可寫的 socket 的集合
exceptfds:返回值,當前發生錯誤的 socket 的集合
返回值: 表示當前準備就緒的 socket 的數量。 這里的準備就緒包含 可讀,可寫,或者儲出錯的socket。如果返回 SOCKET_ERROR,表示發生錯誤,可以使用 WSAGetLastError 來獲取具體的錯誤碼。

fd_set

fd_set 是一個 socket 的集合,作為 select 方法的輸入輸出參數.

這里使用到的操作包括:

  • FD_ZERO : 重置 fd_set
  • FD_SET: 將 socket handle 添加到當前 fd_set
  • FD_ISSET: 檢查某個 socket handle 是否處于當前 fd_set

實現思路

  • 創建一個 socket 作為監聽 socket,并將該 socket 設置為非阻塞模式.
  • 使用 select api 來非阻塞的簡單該監聽socket 是否有新連接進來。如果有,則調用 accept 來接收該 client socket
  • 對于已經與客戶段建立的連接,同樣的設置為非阻塞模式,使用 select api 來檢查該 socket 上是否有數據可讀,或者該 socket 是否可寫,以便往客戶端發送數據。還需要檢查socket 是否出錯,本文的例子里忽略這點,思路是一樣的。
  • 注意,這里所有的操作都是非阻塞的。
  • 解析來我們通過一個例子看看如何使用 Select.

    實例

    本文的例子可以直接拷貝運行。 讀者如果不需要運行,直接注意加注釋的代碼段即可.

    服務器實現
    #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;}// 設置監聽socket為異步模式if (ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR) {printf("ioctlsocket failed with error %d\n", WSAGetLastError());return 1;}while (TRUE) {// 清空 ReadSet 和 WriteSet,我們將該集合中放入我們關心的 socket handleFD_ZERO(&ReadSet);FD_ZERO(&WriteSet);// 將監聽socket 放入 ReadSet, 以便當有新連接到來的時候,我們可以檢查到該事件FD_SET(ListenSocket, &ReadSet);// 我們同時也關心已經建立的客戶段連接的可讀可寫狀態,以便我們從客戶端接收數據或者寫數據// 這里一些小邏輯,直接忽略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 檢查當前 ReadSet 和 WriteSet 中的socket 是否有新的事件到來if ((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR) {printf("select failed with error %d\n", WSAGetLastError());return 1;}// 使用 FD_ISSET 判斷監聽 socket 是否可以讀,也就是說有新的連接到來// 如果有,調用 accept 來接收該新連接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");}}}// 接下來檢查可讀的客戶段連接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;//當前 socket 可讀,那么調用 WSARecv 從該 socket 讀取數據// 如果 WSARecv 返回 0, 是說該連接已經斷開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);}}}// 接下來檢查可寫的客戶段連接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--; }
    客戶端實現

    搭配該服務器,使用下面 client 實現進行測試。 這里僅僅做測試用,忽略了大部分的錯誤檢查.

    #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!!!

    總結

    以上是生活随笔為你收集整理的WinSock I/O 模型 -- Select 模型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。