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

实现思路

  1. 创建一个 socket 作为监听 socket,并将该 socket 设置为非阻塞模式.
  2. 使用 select api 来非阻塞的简单该监听socket 是否有新连接进来。如果有,则调用 accept 来接收该 client socket
  3. 对于已经与客户段建立的连接,同样的设置为非阻塞模式,使用 select api 来检查该 socket 上是否有数据可读,或者该 socket 是否可写,以便往客户端发送数据。还需要检查socket 是否出错,本文的例子里忽略这点,思路是一样的。
  4. 注意,这里所有的操作都是非阻塞的。

解析来我们通过一个例子看看如何使用 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!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499670.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WinSock I/O 模型 -- WSAEventSelect 模型

简介 WSAEventSelect 模型也是 WinSock 中最常见的异步 I/O 模型。 这篇文章我们就来看看如何使用 WSAEventSelect api 来实现一个简单的 TCP 服务器. API 基础 WSAEventSelect WSAEventSelect 用来把一个 SOCKET 对象和一个 WSAEVENT 对象关联起来。 lNetworkEvents 表示…

WinSock I/O 模型 -- WSAAsyncSelect 模型

简介 WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。 使用这个模型&#xff0c;网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。 这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器. API 基础 要使用 W…

WinSock I/O 模型 -- OVERLAPPED I/O 模型

简介 OVERLAPPED I/O 模型也是 WinSock 中常见的异步 I/O 模型&#xff0c;相比于我们之前提到的 Select 模型&#xff0c;WSAAsyncSelect 模型 和 WSAEventSelect 模型有更好的性能. 为了方便描述&#xff0c;下文我们将称 Overlapped I/O 模型为 “重叠模型”. 重叠模型的…

WinSock I/O 模型 -- IOCP 模型

前言 IOCP 全称 Input/Ouput Completion Ports&#xff0c;中文中翻译一般为“完成端口”&#xff0c;本文中我们使用 IOCP 简写. IOCP 模型是迄今为止最为复杂的一种 I/O 模型&#xff0c;但是同时通过使用 IOCP 我们往往可以达到最佳的系统性能. 当你的网络应用程序需要管理…

GTank iOS App Technical Support

GTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots ​​

证书体系: CSR 解析

原文同时发布于本人个人博客&#xff1a; https//kutank.com/blog/cert-csr/ 简介 CSR 全称 “证书签名请求”(Certificate Signing Request). 本文我们将来详细的学习 CSR 的知识&#xff0c;重点集中在 CSR 所包含的信息&#xff0c;及其意义。 CSR 的作用: CSR 通常由想要获…

胡思乱想

学了一段时间的OGRE,才知道什么才称得上"建筑师",而我们只不过是"砌墙匠" 他们在算法算法,我们在Coding Coding,怎样才能有所改观呢~~~想当初还不如选数学专业再来学计算机可能好些, 但是既然选择了先学计算机这条路,那就先Coding,边Coding边提高数学能力…

关于数据库备份的问题

首先我们来看数据库帮助上面的sql语句&#xff1a; BACKUP DATABASE Northwind TO DISK c:/Northwind.bakRESTORE FILELISTONLY FROM DISK c:/Northwind.bakRESTORE DATABASE TestDB FROM DISK c:/Northwind.bak WITH MOVE Northwind TO c:/test/testdb.mdf, MOVE N…

关于函数指针调用C++非静态成员

当在类里面定义函数指针&#xff0c;而函数指针会指向类里面的成员的时候&#xff0c;这个时候成员需要定义为静态成员。实例代码如下&#xff1a; //.h#define SCMD_REGISTER 0class CCallFuctionList{public:CCallFuctionList();virtual ~CCallFuctionList(void);typedef…

重构心得

重构入手&#xff1a; 1. 找到牵连最广模块。 2. 找到上述模块中需要重构的相关的子类。 3. 原来代码不删除&#xff0c;保证编译运行。 4. 陆续重构其他模块 再列出我觉得可以借鉴的重构方法。【摘自代码大全】 1.保存初始代码。用你的版本控制系统保存一个初始版本&#x…

跨模块中的分配内存问题

现在有dll模块A,dll模块B以及静态库C, C中有个全局Create()函数负责创建对象M,当第二次调用Create()的时候会增加引用计数&#xff0c;并返回原来创建的对象M。Relase()会减少引用计数。当引用计数为0时&#xff0c;销毁对象M。现在在模块A中创建的初始化对象M&#xff0c;模块…

CListControl的OnMouseMove和OnNcHitTest

实际案例如下&#xff1a; 将CListCtrl做成菜单样式。需要处理当鼠标移到ClistCtrl上的事件。 处理逻辑这样&#xff1a;当鼠标移动到CListCtrl区域时候&#xff0c;将CListCtrl上所有ITem置为非选中状态&#xff0c;然后调用HitTest得到行数再将所选行置为选中状态。当鼠标移…

关于函数指针续

前面有提到过关于函数指针调用C非静态成员&#xff0c;解决了在类内调用函数指针的问题。 class CCallFuctionList { public: CCallFuctionList(); virtual ~CCallFuctionList(void); typedef void (CCallFuctionList::*FUNCPTR)(); typedef std::multimap<unsi…

关于函数指针续二

前篇文章解决了关于函数指针的不同类成员函数传递的问题。不知道细心的朋友看见我上篇文章后&#xff0c;是否发现参数必须被限制为特定的参数。 原来改进的代码如下&#xff1a; class CCallFuctionList { public:CCallFuctionList(); virtual ~CCallFuctionL…

HGE2D引擎按键消息分析

我们自己先动手来模仿HGE其键盘特殊按键消息响应&#xff0c;其中所涉及到的数据操作含义稍后再介绍。 首先创建基于对话框CGetKeyBoardTestDlg的程序&#xff0c;创建一个STATIC控件ID为IDC_STATIC_CONTENT在对话框上面。添加成员 unsigned char kbstate[256]; 和int flag; 在…

HGE引擎适用于MFC的修改

打开hge181/src/core/system.cpp 找到System_Initiate()函数&#xff0c;可以看见里面有段代码是用于创建窗口。 // Register window classwinclass.style CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;winclass.lpfnWndProc WindowProc;winclass.cbClsExtra 0;wincl…

关于CString

昨天重构代码的时候&#xff0c;这样一段代码&#xff1a; CString str _T("bbbbbbbb");LVITEM item GetItem(str);LVITEM CLVIItemTestDlg::GetItem(CString text){LVITEM item;item.iItem 0;item.iSubItem 0;item.mask LVIF_TEXT;item.pszText text.GetBuffer…

HGE2D引擎按键消息分析(续)

继续上一章对其按键消息处理抽丝剥茧。看BuildEvent()函数里面 我们先来分析其这段代码 if(typeINPUT_KEYDOWN){if((flags & HGEINP_REPEAT) 0) keyz[key] | 1;ToAscii(key, scan, kbstate, (unsigned short *)&eptr->event.chr, 0);}if(typeINPUT_KEYUP){keyz[key…

动态链接MFC引发的血案

首先简单描述下程序运行的步骤&#xff0c; 我们要去加载两个DLL&#xff0c;先加载的称为A,后加载的称为B&#xff0c;加载A在里面做的事情是动态创建一个全局对象&#xff0c;加载B在里面做的事情是取得这个全局对象&#xff0c;然后干其他事情。 我们机子上运行的非常完美。…

规避软件架构风险之反模式

在QCON大会上&#xff0c;Michael Nygard&#xff0c;以及 李伟专家都提到了一个概念&#xff0c;容错能力。 衡量软件架构最佳的一个很重要的因素就是看软件的容错能力。没有容错能力的软件&#xff0c;哪怕你QA都非常优秀&#xff0c;但一发生故障就出现集联失效&#xff0c;…