Windows提供了两种方式“套接字模式”和“套接字I/O模型”,可对一个套接字上的I/O行为加以控制。套接字模式用于决定在随一个套接字调用时,那些 Winsock函数的行为。其中的模型包括括select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O(重叠式I/O)以及Completionport(完成端口)等等。
所有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//事件类型 );
事件类型很多,如图
WSAAsyncSelect模式实现,这个实现起来挺简单的
1 // WSAAsyncSelect模式实现,这个实现起来挺简单的。 2 #include <winsock2.h> 3 #include <tchar.h> 4 #define PORT 7890 5 #define MSGSIZE 1024 6 #define WM_SOCKET WM_USER+1 7 #pragma comment(lib, "ws2_32.lib") 8 9 10 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); 11 int WINAPI WinMain( HINSTANCE hInstance, 12 HINSTANCE hPrevInstance, 13 LPSTR lpCmdLine, 14 int nShowCmd 15 ) 16 { 17 18 19 static TCHAR szAppName[] = TEXT ("WSAAsyncSelect Test") ; 20 HWND hwnd ; 21 MSG msg ; 22 WNDCLASS wndclass ; 23 24 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 25 wndclass.lpfnWndProc = WndProc; 26 wndclass.cbClsExtra = 0 ; 27 wndclass.cbWndExtra = 0 ; 28 wndclass.hInstance = hInstance ; 29 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; 30 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; 31 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 32 wndclass.lpszMenuName = NULL ; 33 wndclass.lpszClassName = szAppName ; 34 35 if (!RegisterClass (&wndclass)) 36 { 37 MessageBox (NULL, TEXT ("Program requires Windows NT!"), 38 szAppName, MB_ICONERROR) ; 39 return 0 ; 40 } 41 42 hwnd = CreateWindow (szAppName, TEXT ("WSAAsyncSelect"), 43 WS_OVERLAPPEDWINDOW, 44 CW_USEDEFAULT, CW_USEDEFAULT, 45 CW_USEDEFAULT, CW_USEDEFAULT, 46 NULL, NULL, hInstance, NULL) ; 47 48 ShowWindow (hwnd, nShowCmd) ; 49 UpdateWindow (hwnd) ; 50 51 while (GetMessage (&msg, NULL, 0, 0)) 52 { 53 TranslateMessage (&msg) ; 54 DispatchMessage (&msg) ; 55 } 56 return msg.wParam ; 57 } 58 59 60 61 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 62 { 63 WSADATA wsd; 64 static SOCKET sListen; 65 SOCKET sClient; 66 SOCKADDR_IN local, client; 67 int ret, iAddrSize = sizeof(client); 68 char szMessage[MSGSIZE] ; 69 switch (message) 70 { 71 case WM_CREATE: 72 // 初始化 73 WSAStartup(0x0202, &wsd); 74 75 // 创建socket 76 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 77 78 // 绑定 79 local.sin_addr.S_un.S_addr = inet_addr("192.168.0.87"); 80 local.sin_family = AF_INET; 81 local.sin_port = htons(PORT); 82 bind(sListen, (struct sockaddr *)&local, sizeof(local)); 83 84 // 监听 85 listen(sListen, 3); 86 // 设置WSAAsyncSelect模式 87 WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT); 88 return 0; 89 case WM_DESTROY: 90 closesocket(sListen); 91 WSACleanup(); 92 PostQuitMessage(0); 93 return 0; 94 95 case WM_SOCKET: 96 if (WSAGETSELECTERROR(lParam))//使用宏 WSAGETSELECTERROR 判断lParam高字节是否有错误 97 { 98 closesocket(wParam); 99 break; 100 } 101 102 switch (WSAGETSELECTEVENT(lParam))//使用宏WSAGETSELECTEVENT判断lParam低字节是什么操作 103 { 104 case FD_ACCEPT: 105 // 从客户端接收连接 106 sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize); 107 108 // 设置事件监听 109 WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE); 110 break; 111 case FD_READ: //读 112 ret = recv(wParam, szMessage, MSGSIZE, 0); 113 if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET) 114 { 115 closesocket(wParam); 116 } 117 else 118 { 119 szMessage[ret] = '