Windows网络编程笔记6 --- WinSock I/O 控制方法
2013-11-27 15:21
281 查看
Windows提供了两种方式“套接字模式”和“套接字I/O模型”,可对一个套接字上的I/O行为加以控制。套接字模式用于决定在随一个套接字调用时,那些 Winsock函数的行为。其中的模型包括括select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O(重叠式I/O)以及Completionport(完成端口)等等。
所有Windows平台都支持套接字以锁定或非锁定方式工作。在锁定模式下,在I/O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无论如何都会立即返回。
锁定模式使用线程同步机制完成数据的I/O,扩展性不好,尤其是有多个套接字时。
非锁定模式的使用需要函数 ioctlsocket(),使用如下:
套接字I/O模型
参数readfds 表示以下几种情况
有数据可读入、连接已经关闭(重设或者终止)、如果已经listen,并且正在建立连接,那么accept函数会返回成功。
参数writefds表示以下几种情况
有数据可发出、如果已完成对一个非锁定连接调用的处理,连接就会成功。
参数exceptfds表示如下
如果已完成对一个非锁定连接调用的处理,连接尝试就会失败。有带外(Out-of-band,OOB)数据可供读取。
其中fd_set结构如下:
超时时间
用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是否正在发生上述的I/O活动。
下面是对fd_set进行操作的一些宏
FD_CLR(s,*set):从set中删除套接字s。
FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
FD_SET(s,*set):将套接字s加入集合set。
FD_ZERO(*set):将set初始化成空集合。
用select操作一个或多个套接字句柄的全过程:
1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
2)使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄。select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4)根据select的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待决)的I/O操作—具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。
5)知道了每个集合中“待决”的I/O操作之后,对I/O进行处理,然后返回步骤1),继续进行select处理。
简单过程如下
事件类型很多,如图
View Code
关键字 WSA_FLAG_OVERLAPPED
函数WSASocket在创建时可以手动指定模式
SOCKET s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
而函数socket默认就是重叠I/O模式。
前几个参数有系统维护,只有hEvent字段有点儿特殊,它允许应用程序将一个事件对象句柄同一个套接字关联起来。
对重叠I/O操作进行管理过程
1)创建一个套接字,开始在指定的端口上监听连接请求。
2)接受一个进入的连接请求。
3)为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
4)在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(I/O操作尚未完成)。
5)使用步骤3)的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6)WSAWaitForMultipleEvents函数完成后,针对事件数组,调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7)使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。
8)在套接字上投递另一个重叠WSARecv请求。
9)重复步骤5-8)。
使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函数。函数有两种功能:
1. 用于创建一个完成端口对象。
2. 将一个句柄同完成端口关联到一起。
参数ExistingCompletionPort表示已经存在的完成端口。如果为NULL,则为新建一个IOCP(I/O完成端口)。
创建过程
HANDLE completionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
一旦所有套接字句柄都已关闭,便需在完成端口上,终止所有工作者线程的运行,此时调用函数PostQueuedCompletionStatus向所有线程发送终止命令。
使用过程
1、创建一个完成端口,第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程
2、判断系统内到底安装了几个处理器
3、创建工作者线程,根据步骤2得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务
4、准备好一个监听套接字
5、使用accept函数,接受进入的请求
6、创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
7、调用CreateCompletionPort,将之accept返回的新套接字同完成端口关联到一起
8、开始在已接受的连接上进行I/O操作
9、重复5到8,直到服务器终止。
这些操作对套接字I/O至关重要,需要理解
所有Windows平台都支持套接字以锁定或非锁定方式工作。在锁定模式下,在I/O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无论如何都会立即返回。
锁定模式使用线程同步机制完成数据的I/O,扩展性不好,尤其是有多个套接字时。
非锁定模式的使用需要函数 ioctlsocket(),使用如下:
SOCKET s; unsigned long ub =1; int nRet; s = socketAF_INET,SOCK_STREAM,0); nRet = ioctlsocket(s,FIOBIO,(unsigned long *)&ub);//设置非锁定模式 if(nRet == SOCKET_ERROR) { //进入非锁定模式失败 }
套接字I/O模型
1、select模型
//函数原型 int select( int nfds, //与早期程序兼容,可忽略 fd_set FAR * readfds,// 可读性 fd_set FAR * writefds,// 可写性 fd_set FAR * exceptfds,// 例外数据 const struct timeval FAR * timeout//超时时间 );
参数readfds 表示以下几种情况
有数据可读入、连接已经关闭(重设或者终止)、如果已经listen,并且正在建立连接,那么accept函数会返回成功。
参数writefds表示以下几种情况
有数据可发出、如果已完成对一个非锁定连接调用的处理,连接就会成功。
参数exceptfds表示如下
如果已完成对一个非锁定连接调用的处理,连接尝试就会失败。有带外(Out-of-band,OOB)数据可供读取。
其中fd_set结构如下:
typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
超时时间
struct timeval { long tv_sec; /* seconds */秒 long tv_usec; /* and microseconds */毫秒 };
用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是否正在发生上述的I/O活动。
下面是对fd_set进行操作的一些宏
FD_CLR(s,*set):从set中删除套接字s。
FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
FD_SET(s,*set):将套接字s加入集合set。
FD_ZERO(*set):将set初始化成空集合。
用select操作一个或多个套接字句柄的全过程:
1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
2)使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄。select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4)根据select的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待决)的I/O操作—具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。
5)知道了每个集合中“待决”的I/O操作之后,对I/O进行处理,然后返回步骤1),继续进行select处理。
简单过程如下
SOCKET s; fd_set fdread; int ret; //创建 //bind() //accept() //开始 while (1) { FD_ZERO(&fdread);//初始化 FD_SET(s,&fdread);//添加 if ((ret = select(0,&fdread,NULL,NULL,NULL)) == SOCKET_ERROR) { //添加失败 } if (ret > 0) { if (FD_ISSET(s,&fdread)) { //已经是集合的一部分,正在读取数据 } } //其他操作 }
2、WSAAsyncSelect模型
WSAAsyncSelect 的使用必须要在窗口应用程序中使用,在回调函数中实现处理。int WSAAsyncSelect( _In_ SOCKET s,//关心的socket _In_ HWND hWnd,//窗口句柄 _In_ unsigned int wMsg,//消息 _In_ long lEvent//事件类型 );
事件类型很多,如图
//WSAEnumNetworkEvent 使用举例 #include <windows.h> #include <winsock2.h> #include <Ws2tcpip.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") int main() { //------------------------- WSADATA wsaData; int iResult; SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS], ListenSocket; WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; WSANETWORKEVENTS NetworkEvents; sockaddr_in InetAddr; DWORD EventTotal = 0; DWORD Index; DWORD i; HANDLE NewEvent = NULL; // 初始化 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { wprintf(L"WSAStartup failed with error: %d\n", iResult); return 1; } //创建 ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { wprintf(L"socket function failed with error: %d\n", WSAGetLastError() ); return 1; } InetAddr.sin_family = AF_INET; InetAddr.sin_addr.s_addr = htonl(INADDR_ANY); InetAddr.sin_port = htons(7890); //绑定 iResult = bind(ListenSocket, (SOCKADDR *) & InetAddr, sizeof (InetAddr)); if (iResult != 0) { wprintf(L"bind failed with error: %d\n", WSAGetLastError() ); return 1; } // 创建事件 NewEvent = WSACreateEvent(); if (NewEvent == NULL) { wprintf(L"WSACreateEvent failed with error: %d\n", GetLastError() ); return 1; } // 将事件类型绑定到套接字上 iResult = WSAEventSelect(ListenSocket, NewEvent, FD_ACCEPT | FD_CLOSE); if (iResult != 0) { wprintf(L"WSAEventSelect failed with error: %d\n", WSAGetLastError() ); return 1; } // 监听 iResult = listen(ListenSocket, 10); if (iResult != 0) { wprintf(L"listen failed with error: %d\n", WSAGetLastError() ); return 1; } // 添加事件 SocketArray[EventTotal] = ListenSocket; EventArray[EventTotal] = NewEvent; EventTotal++; // 等待所有套接字上的事件发生 Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE); Index = Index - WSA_WAIT_EVENT_0; //枚举事件类型,直到失败 for (i = Index; i < EventTotal; i++) { Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE); if ((Index != WSA_WAIT_FAILED) && (Index != WSA_WAIT_TIMEOUT)) { WSAEnumNetworkEvents(SocketArray[i], EventArray[i], &NetworkEvents); } } //... return 0; }
View Code
4、重叠I/O模式
重叠I./O模式只支持Winsock2 ,可以使用WSARecv和WSASend实现关键字 WSA_FLAG_OVERLAPPED
函数WSASocket在创建时可以手动指定模式
SOCKET s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
而函数socket默认就是重叠I/O模式。
typedef struct _WSAOVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; }; PVOID Pointer; }; HANDLE hEvent;// } WSAOVERLAPPED, *LPWSAOVERLAPPED;
前几个参数有系统维护,只有hEvent字段有点儿特殊,它允许应用程序将一个事件对象句柄同一个套接字关联起来。
//结构Overlapped BOOL WSAAPI WSAGetOverlappedResult( _In_ SOCKET s, _In_ LPWSAOVERLAPPED lpOverlapped, _Out_ LPDWORD lpcbTransfer,//一次收发的实际字节数 _In_ BOOL fWait,//在重叠I/O模式中无效 _Out_ LPDWORD lpdwFlags//接收结果标志 );
对重叠I/O操作进行管理过程
1)创建一个套接字,开始在指定的端口上监听连接请求。
2)接受一个进入的连接请求。
3)为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
4)在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(I/O操作尚未完成)。
5)使用步骤3)的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6)WSAWaitForMultipleEvents函数完成后,针对事件数组,调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7)使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。
8)在套接字上投递另一个重叠WSARecv请求。
9)重复步骤5-8)。
5、完成端口模型
适合在管理成千上万的套接字时使用。使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函数。函数有两种功能:
1. 用于创建一个完成端口对象。
2. 将一个句柄同完成端口关联到一起。
//创建一个完成端口对象 HANDLE CreateIoCompletionPort( HANDLE FileHandle, //关联的文件句柄,可能为套接字句柄 HANDLE ExistingCompletionPort,//已经存在的完成端口 LONG_PTR CompletionKey,// 传送给处理函数的参数 DWORD NumberOfConcurrentThreads//有多少个线程在访问这个消息队列 );
参数ExistingCompletionPort表示已经存在的完成端口。如果为NULL,则为新建一个IOCP(I/O完成端口)。
创建过程
HANDLE completionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
//获取排队完成的状态 BOOL GetQueuedCompletionStatus( __in HANDLE CompletionPort,//完成端口 __out LPDWORD lpNumberOfBytesTransferred,//实际接受的数据 __out PULONG_PTR lpCompletionKey,//“单句柄数据” __out LPOVERLAPPED *lpOverlapped,//重叠I/O结构 __in DWORD dwMilliseconds//超时时间 );
一旦所有套接字句柄都已关闭,便需在完成端口上,终止所有工作者线程的运行,此时调用函数PostQueuedCompletionStatus向所有线程发送终止命令。
BOOL PostQueuedCompletionStatus( __in HANDLE CompletionPort,//完成端口句柄 __in DWORD dwNumberOfBytesTransferred,//实际接受数据 __in ULONG_PTR dwCompletionKey,//完成端口关键字“单句柄数据” __in_opt LPOVERLAPPED lpOverlapped//重叠I/O结构 );
使用过程
1、创建一个完成端口,第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程
2、判断系统内到底安装了几个处理器
3、创建工作者线程,根据步骤2得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务
4、准备好一个监听套接字
5、使用accept函数,接受进入的请求
6、创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
7、调用CreateCompletionPort,将之accept返回的新套接字同完成端口关联到一起
8、开始在已接受的连接上进行I/O操作
9、重复5到8,直到服务器终止。
这些操作对套接字I/O至关重要,需要理解
相关文章推荐
- 【Java学习笔记】Java中方法和成员变量的访问控制
- 笔记:setAccessible方法启用/禁用权限控制检查
- 笔记:setAccessible方法启用/禁用访问控制权限
- Windows网络编程笔记之二:设计Winsock
- j2ee核心模式笔记一——控制客户端访问服务器资源的方法
- java 学习笔记(入门篇)_程序流程控制结构和方法
- ffmpeg转码速度控制方法(笔记)
- 【python学习笔记】4:精度控制/文件读/split()方法
- java---多线程调度与控制常见方法一览
- 源代码控制VisualSVN Server的配置和使用方法
- 简明python教程学习笔记之九-str类方法简单介绍
- 2015.03.10,专业,文献笔记-“水轮机压力脉动的混频幅值置信度分析方法研究” (2)
- jQuery select操作控制方法小结
- VB中Winsock控制的UDP协议的使用
- flash如何控制外部加载的图片大小的解决方法
- 【zepto学习笔记01】核心方法$()(补)
- 原来天真的认为apache的访问控制的方法一直不会变,但2.4.6和2.0.52确实有区别,why change?
- Ext学习笔记01 - NameSpace,类实例属性,类实例方法,类静态方法
- JSP笔记:forward和include方法的区别
- 【js设计模式笔记---方法的链式调用】