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 模型相关推荐
- 网络基础 select模型
网络基础 select 模型 (一)select简介 select模型为五种IO模型中的一种(I/O多路复用模型).该模型的函数包括select.poll.epoll等函数.这个函数能够允许指示内核等 ...
- SOCKET学习第三阶段(SELECT模型)
/* 2009-11-18 20:57:05 AUTHOR:BY.Feng */ SELECT套接字模型的学习 Winsock提供了五种类型的套接字I/O 模型,可让Winsock应用程序 对I/O ...
- WinSock学习笔记3:Select模型
WinSock学习笔记3:Select模型 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, G ...
- winsock select模型实现
select模型的原理是在指定的socket 的数组中轮询的采集是否可写.可读.有异常的信息,然后将可以操做的套接字 保存在指定的数组中. // selectsocket.cpp : 定义控制台应用程 ...
- java socket编程 select_windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 fd_set* writefds, / ...
- socket select模型
由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续. 如果想改变这种一直等下去的焦急状态,可以多线程来实现(不 ...
- 转:socket select模型示例
// selectSocketMode.cpp : Defines the entry point for the console application. // #include "std ...
- Windows环境下IOCP和SELECT模型性能比较
在大量客户端连接的情况下,IOCP模型应该是具有先天优势的,首先是每次调用时不需要传入socket列表,其次是他在通知时就已经完成了IO操作,节省了系统调用. 道理是这么个道理,然而在实际应用过程当中 ...
- 详细解析SELECT模型
先看一下下面的这句代码: int iResult = recv(s, buffer,1024); 这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把 ...
最新文章
- Python使用sklearn和statsmodels构建多元线性回归模型(Multiple Linear Regression)并解读
- c语言Winmain 错误,C语言编译错误 undefined reference to `WinMain@16'
- Python学习笔记__8章错误、调试和测试__8.1章错误处理
- 算法描述怎么写_管件材料描述怎么写
- LeetCode 907. 子数组的最小值之和(单调栈)
- vue循环出来列表里面的列表点击click事件只对当前列表有效;
- bzoj3612 平衡 (dp)
- jw player 5去掉share,info,embed页面
- B. Suffix Structures 模拟吧,情况比较多要想周全
- MySQL存储过程实例
- 转载:我的外语学习历程(如何学会十门外语)
- 第6周作业2-IF语句大显身手之成绩判断(网络131黄宇倩)
- Smart3D(ContextCapture)跑三维到底要啥配置?!40000元来组建建模集群
- 强化学习之Q学习与SARSA
- 网站更换服务器对于SEO有哪些影响?
- AMESim2020.1仿真编译失败解决方法之一
- cesesesese
- 日志20140704~1226
- How to Flash a ROM to Your Android Phone
- STM32使用硬件定时器6制作us级别延时函数
热门文章
- STM32F105 PA9/OTG_FS_VBUS Issues
- 基于原版Hadoop的YDB部署(转)
- 基于哈夫曼编码完成的文件压缩及解压
- SQL Server安全(8/11):数据加密(Data Encryption)
- jQuery的radio,checkbox,select操作
- 2021-07-27-jeesite学习笔记
- linux的常用操作——makefile
- 【计算机网络复习 数据链路层】3.3.1 差错控制(检错编码)
- 【计算机网络复习】1.2.1 分层结构、协议、接口、服务
- Leetcode--347. 前k个高频元素