转自:http://blog.csdn.net/wangjieest/article/details/7042108

WSAEventSelect模型编程

WSAEventSelect模型编程
这个模型是一个简单的异步事件模型,使用起来比较方便,现在说一下其的具体的用法和需要注意的地方。
一,模型的例程(服务端):
先举一个王艳平网络通信上的例子:

  1. //////////////////////////////////////////////////
  2. // WSAEventSelect文件
  3. #include "initsock.h"
  4. #include <stdio.h>
  5. #include <iostream.h>
  6. #include <windows.h>
  7. // 初始化Winsock库
  8. CInitSock theSock;
  9. int main()
  10. {
  11. // 事件句柄和套节字句柄表
  12. WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
  13. SOCKET  sockArray[WSA_MAXIMUM_WAIT_EVENTS];
  14. int nEventTotal = 0;
  15. USHORT nPort = 4567; // 此服务器监听的端口号
  16. // 创建监听套节字
  17. SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  18. sockaddr_in sin;
  19. sin.sin_family = AF_INET;
  20. sin.sin_port = htons(nPort);
  21. sin.sin_addr.S_un.S_addr = INADDR_ANY;
  22. if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
  23. {
  24. printf(" Failed bind() \n");
  25. return -1;
  26. }
  27. ::listen(sListen, 5);
  28. // 创建事件对象,并关联到新的套节字
  29. WSAEVENT event = ::WSACreateEvent();
  30. ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
  31. // 添加到表中
  32. eventArray[nEventTotal] = event;
  33. sockArray[nEventTotal] = sListen;
  34. nEventTotal++;
  35. // 处理网络事件
  36. while(TRUE)
  37. {
  38. // 在所有事件对象上等待
  39. int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
  40. // 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态
  41. nIndex = nIndex - WSA_WAIT_EVENT_0;
  42. for(int i=nIndex; i<nEventTotal; i++)
  43. {
  44. nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
  45. if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
  46. {
  47. continue;
  48. }
  49. else
  50. {
  51. // 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件
  52. WSANETWORKEVENTS event;
  53. ::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
  54. if(event.lNetworkEvents & FD_ACCEPT)    // 处理FD_ACCEPT通知消息
  55. {
  56. if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
  57. {
  58. if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
  59. {
  60. printf(" Too many connections! \n");
  61. continue;
  62. }
  63. SOCKET sNew = ::accept(sockArray[i], NULL, NULL);
  64. WSAEVENT event = ::WSACreateEvent();
  65. ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
  66. // 添加到表中
  67. eventArray[nEventTotal] = event;
  68. sockArray[nEventTotal] = sNew;
  69. nEventTotal++;
  70. }
  71. }
  72. else if(event.lNetworkEvents & FD_READ)   // 处理FD_READ通知消息
  73. {
  74. if(event.iErrorCode[FD_READ_BIT] == 0)
  75. {
  76. char szText[256];
  77. int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
  78. if(nRecv > 0)
  79. {
  80. szText[nRecv] = '\0';
  81. printf("接收到数据:%s \n", szText);
  82. }
  83. }
  84. }
  85. else if(event.lNetworkEvents & FD_CLOSE)  // 处理FD_CLOSE通知消息
  86. {
  87. if(event.iErrorCode[FD_CLOSE_BIT] == 0)
  88. {
  89. ::closesocket(sockArray[i]);
  90. for(int j=i; j<nEventTotal-1; j++)
  91. {
  92. sockArray[j] = sockArray[j+1];
  93. sockArray[j] = sockArray[j+1]; //这个是个BUG,应为:   eventArray[j] = eventArray[j+1];还真没注意,直到同事提      起才注意到。
  94. }
  95. nEventTotal--;
  96. }
  97. }
  98. else if(event.lNetworkEvents & FD_WRITE)  // 处理FD_WRITE通知消息
  99. {
  100. }
  101. }
  102. }
  103. }
  104. return 0;
  105. }

二、例程的分析
1、事件的创建和绑定
前面的一些设置我们略过,从WSAEVENT 开始说起,跟踪发现在winsock2.h中有如下定义:
#define WSAEVENT                HANDLE
这个事件说明是一个句柄,我们知道在事件中有两种状态,一种是手动处理事件,一种是自动的,这里使用WSACreateEvent()这个函数创建返回的事件句柄,正常的返回的情况下,其创建的是一个手工处理的句柄,否则,其返回WSA_INVALID_EVENT,表明创建未成功,如果需要知道更多的信息WSAGetLastError()这个函数来得到具体的信息出错代码。这里埋伏下了一个雷,为什么创建的是手工处理的事件(manually reset ),那后面为什么没有WSAResetEvent()这个函数来处理事件,先记下。
然后接着讲,

  1. ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
  2. // 添加到表中
  3. eventArray[nEventTotal] = event;
  4. sockArray[nEventTotal] = sListen;
  5. nEventTotal++;

将事件绑定到监听的套接字上,这里我们只对这个套接字的接收和关闭两个消息有兴趣,所以只监听这两个消息,那别的读写啥的呢,不要急,慢慢向下看。eventArray和sockArray,定义的是WSA_MAXIMUM_WAIT_EVENTS大小,而在头文件中#define WSA_MAXIMUM_WAIT_EVENTS (MAXIMUM_WAIT_OBJECTS),后者被定义成64,这也是需要注意的一点,这个模型单线程只能处理最多64个事件,再多就只能用多线程了,不过,这里重点说明一下,这个模型即使你使用多线程,最多也只能处理1200个左右的处理量(正常情况),否则,会造成整个程序的性能下降,至于怎么下降,还真没有真正的测试,只是从书上和资料上看是这么讲的。
接着原来,程序然后进入了死循环,在这个循环里,因为是简单的使用嘛,所以很多的异常并没有进行控制,但是为了说明用法,就得简单一些不是么?
2、事件的监听和控制处理
2.1 事件的监听

  1. int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
  2. nIndex = nIndex - WSA_WAIT_EVENT_0;

先说这个索引为什么要减去WSA_WAIT_EVENT_0这个值,因为事件的起始值在内核中是进行定义了的,不过,在这里这个东西最终定义仍然是0。然后我们看这个函数
::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE),

这个函数用来监听多个事件(就是上面我们绑定的事件)的状态,有状态或者是事件被触发,就会返回,否则会按照你设置的参数进行操作。
前面两个参数,第一个是监听的数量,最小是一,MSDN上有,第二是一个事件的数组,第三个是精彩的去处,如果设置成TRUE,那么只有这第二个事件数组中的所有的事件都受信或者说触发,才会动作,如果是FALSE呢,则只要有一个就可以动作。第五个是超时设置,可以是0,是WSA_INFINITE,也可以是其它的数值,这里有一个问题,如果设置为0会造成程序的CPU占用率过高,WSA_INFINITE则可能会出现在等待数量为一个字时,且第三个参数设置为TRUE,产生死套接字的长期阻塞。所以还是设置成一个经验值为好,至于这个经验值是多少,看你的程序的具体的应用了
其实这个函数本质还是调用WaitForMulipleObjectsEx这个函数,MSDN上讲WSAEventSelect模型在等待时不占用CPU时间,就是这个原因,所以其比阻塞的SOCKET通信要效率高很多,其实那个消息的模型WSAAsycSelect和这个事件的模型也差不多,异曲同工之妙吧。不过适用范围是有区别的,这个可以用在WINCE上。消息则不行。
这里就又引出一个注意点,在这个模型里,如果同时有几个事件受信,或者说触发,那么nIndex = ::WSAWaitForMultipleEvents()只返回最前面的一个事件,那么怎么解决其后面的呢,书上有曰:多次循环调用这个就可以了,所以才会引出下面的再次在for循环里调用
nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
注意这里参数的变化,数量为1,事件为[i],但事件会不断的增长,全面受信改成了TRUE,超时为1000,最后的这个参数在这里只能设置成FALSE,具体为什么查MSDN去。
如果这里我们处理的不好,如果把1000改成无限等待的话,就可以出现上面说的死套接字的无限阻塞,也就是说如果一个套接字死掉了,你没有在事件队伍里删除他,那么他就会一直在这儿阻塞,即使后面有事件也无法得到响应,但是,如果你的套接字只有一个连接的话,就没有什么了,可以改成无限等待。不过,最好还是别这样,因为如果你处理一个失误,就会产生死的套接字(比如重连,但你没有删除先前无用的套接字)。
用两个::WSAWaitForMultipleEvents函数,

一个用来处理监听多个事件数组,一个用来遍历每个数组事件,

防止出现丢失响应的现象,所以其参数的设置是不同的,一定要引起注意。

2.2事件的处理

然后戏又来了,上面说的读写监听呢,就在这里出现了,包括上面埋伏下的一个雷,也在这里处理了:

首先调用::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event),把上面的雷给拆了,

::WSAEnumNetworkEvents会自动重置事件

然后得到事件的索引或者说ID,

  1. if(event.lNetworkEvents & FD_ACCEPT)    // 处理FD_ACCEPT通知消息
  2. {
  3. if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
  4. {
  5. if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
  6. {
  7. printf(" Too many connections! \n");
  8. continue;
  9. }
  10. SOCKET sNew = ::accept(sockArray[i], NULL, NULL);
  11. WSAEVENT event = ::WSACreateEvent();
  12. ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
  13. // 添加到表中
  14. eventArray[nEventTotal] = event;
  15. sockArray[nEventTotal] = sNew;
  16. nEventTotal++;
  17. }
  18. }

代码里重新调用了事件创建和事件绑定函数,并且将两个数组自动增大,最最重要的是我们终于看到了,FD_READ|FD_CLOSE|FD_WRITE

明白了吧,这个简单的程序的本质其实是将 读 写 和 接收关闭 的套接字混合到了一起

而在后面的服务器例程里,我们发现,这个已经拆开,并且重新手动设置受信的事件,调用了::ResetEvent(event)。这样不就完美的拆除了上面的雷么。

2.3 其它处理方法
当程序继续循环到最外层时,::WSAWaitForMultipleEvents无限等待所有的事件,只要有一个事件响应,就会进入到下一层循环,如果是接收就重复上述的动作,如果是读写就进入:

  1. else if(event.lNetworkEvents & FD_READ)   // 处理FD_READ通知消息
  2. {
  3. if(event.iErrorCode[FD_READ_BIT] == 0)
  4. {
  5. char szText[256];
  6. int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
  7. if(nRecv > 0)
  8. {
  9. szText[nRecv] = '\0';
  10. printf("接收到数据:%s \n", szText);
  11. }
  12. }
  13. }
  14. else if(event.lNetworkEvents & FD_CLOSE)  // 处理FD_CLOSE通知消息
  15. {
  16. if(event.iErrorCode[FD_CLOSE_BIT] == 0)
  17. {
  18. ::closesocket(sockArray[i]);
  19. for(int j=i; j<nEventTotal-1; j++)
  20. {
  21. sockArray[j] = sockArray[j+1];
  22. sockArray[j] = sockArray[j+1];
  23. }
  24. nEventTotal--;
  25. }
  26. }
  27. else if(event.lNetworkEvents & FD_WRITE)  // 处理FD_WRITE通知消息
  28. {
  29. }

如此往复,不就达到了不断接收连接和处理数据的问题么。
这里还重复一下,网上很多程序都没有处理多个事件同时受信的情况,在网上和各种资料中,也有的只使用一个::WSAWaitForMultipleEvents函数,但参数的设置得重新来过,而且得小心的处理各种的事件和异常的发生。可能在小并发量和小数据量时没有问题,但并发一多数据一大,可能会出现丢数据的问题,没有做过测试,但可能是很大的。否则不会说遍历调用这个函数了。

2.4 FD_WRITE 事件的触发

这里得罗嗦两句FD_WRITE 事件的触发,前面的都好理解,主要是啥时候儿会触发这个事件呢,我们在一开始只对接收和关闭进行了监听,为什么没有这个FD_WRITE事件的

监听呢,

这就引出了下面的东东:(从一个网友那转来)

下面是MSDN中对FD_WRITE触发机制的解释:

The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or

accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that

sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the

application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set

FD_WRITE事件只有在以下三种情况下才会触发

①client 通过connect(WSAConnect)首次和server建立连接时,在client端会触发FD_WRITE事件

②server通过accept(WSAAccept)接受client连接请求时,在server端会触发FD_WRITE事件

③send(WSASend)/sendto(WSASendTo)发送失败返回WSAEWOULDBLOCK,并且当缓冲区有可用空间时,则会触发FD_WRITE事件

①②其实是同一种情况,在第一次建立连接时,C/S端都会触发一个FD_WRITE事件。

主要是③这种情况:send出去的数据其实都先存在winsock的发送缓冲区中,然后才发送出去,如果缓冲区满了,那么再调用send(WSASend,sendto,WSASendTo)的话,就会返回一个 WSAEWOULDBLOCK的错误码,接下来随着发送缓冲区中的数据被发送出去,缓冲区中出现可用空间时,一个 FD_WRITE 事件才会被触发,这里比较容易混淆的是 FD_WRITE 触发的前提是 缓冲区要先被充满然后随着数据的发送又出现可用空间,而不是缓冲区中有可用空间,也就是说像如下的调用方式可能出现问题

  1. else if(event.lNetworkEvents & FD_WRITE)
  2. {
  3. if(event.iErrorCode[FD_WRITE_BIT] == 0)
  4. {
  5. send(g_sockArray[nIndex], buffer, buffersize);
  6. ....
  7. }
  8. else
  9. {
  10. }
  11. }

问题在于建立连接后 FD_WRITE 第一次被触发, 如果send发送的数据不足以充满缓冲区,虽然缓冲区中仍有空闲空间,但是 FD_WRITE 不会再被触发,程序永远也等不到可以发送的网络事件。

基于以上原因,在收到FD_WRITE事件时,程序就用循环或线程不停的send数据,直至send返回WSAEWOULDBLOCK,表明缓冲区已满,再退出循环或线程。

当缓冲区中又有新的空闲空间时,FD_WRITE 事件又被触发,程序被通知后又可发送数据了。

上面代码片段中省略的对 FD_WRITE 事件处理

  1. else if(event.lNetworkEvents & FD_WRITE)
  2. {
  3. if(event.iErrorCode[FD_WRITE_BIT] == 0)
  4. {
  5. while(TRUE)
  6. {
  7. // 得到要发送的buffer,可以是用户的输入,从文件中读取等
  8. GetBuffer....
  9. if(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)
  10. {
  11. // 发送缓冲区已满
  12. if(WSAGetLastError() == WSAEWOULDBLOCK)
  13. break;
  14. else
  15. ErrorHandle...
  16. }
  17. }
  18. }
  19. else
  20. {
  21. ErrorHandle..
  22. break;
  23. }
  24. }

如果你不是大数据量的不断的发送数据,建议你忽略这个事件,毕竟缓冲区不是很容易被弄满的,结果就是你的发送事件无法完成。

2.5异常的处理
主要是0个连接时,处理CPU的占用率的问题,以及在多于64个事件时的监听处理问题。而且包括上面讲的,没有双循环时的多事件同时受信的问题。
 
2.6 多线程服务端
这个大家可以看王艳平的书,说得很清楚,需要注意的是在他的主服务程序里,使用的是int nRet = ::WaitForSingleObject(event, 5*1000);
所以下面要手动的重新对事件进行设置,否则这个事件就再无法监听得到了。
其它的难度主要是面向对象的设计封装要弄明白,如果这个弄明白知道封装SOCKET和THREAD结构体的目的是什么,再照着书上看就不会有错了,
但提醒一点,线程结构体中的第一个事件是重建事件,不要和其它的监听事件弄混了。
如果做一个介于书上两种代码间的小框架,可以用一个线程来监听ACCEPT和CLOSE事件,另外的线程监听小于64个的读写等事件,一般的小的SOCKET通信应该就没有什么问题了。重要的是你要把这个服务端封装好,有时间做一下。

三、例程(客户端)
先上一段代码:

  1. DWORD WINAPI Connect(LPVOID lpParam)
  2. {
  3. //////////////////第1步:初始化,创建,连接套接字//////////////////
  4. WSADATA WsaData;int err;
  5. err = WSAStartup (0x0002, &WsaData);if(err!=0) return 1;    //0x0002代表版本2.0
  6. socket_client=socket(AF_INET,SOCK_STREAM,0);
  7. if(socket_client==INVALID_SOCKET){AfxMessageBox("创建套接字错误!\n");return 1;}
  8. SOCKADDR_IN sconnect_pass;
  9. sconnect_pass.sin_family=AF_INET;
  10. sconnect_pass.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
  11. sconnect_pass.sin_port=htons(55551);
  12. if (SOCKET_ERROR==connect(socket_client,(SOCKADDR*)&sconnect_pass,sizeof(SOCKADDR)))
  13. {
  14. AfxMessageBox("连接服务端错误\n");
  15. return 1;
  16. }
  17. else
  18. {
  19. //将套接口s置于”非阻塞模式“
  20. u_long u1=1;//0为保持默认的阻塞,非0表示改为非阻塞
  21. ioctlsocket(socket_client,FIONBIO,(u_long*)&u1);
  22. //--------------①创建事件对象-----------------
  23. WSAEVENT ClientEvent=WSACreateEvent();
  24. if (ClientEvent==WSA_INVALID_EVENT)
  25. {
  26. #ifdef _DEBUG
  27. ::OutputDebugString("创建事件错误!\n");
  28. #endif // _DEBUG
  29. AfxMessageBox("WSACreateEvent() Failed,Error=【%d】\n");
  30. return 1;
  31. }
  32. //--------------②网络事件注册------------
  33. int WESerror=WSAEventSelect(socket_client,ClientEvent,FD_READ|FD_CLOSE);
  34. if (WESerror==INVALID_SOCKET)
  35. {
  36. #ifdef _DEBUG
  37. ::OutputDebugString("网络事件注册错误!\n");
  38. #endif // _DEBUG
  39. AfxMessageBox("WSAEventSelect() Failed,Error=【%d】\n");
  40. return -1;
  41. }
  42. //-----------准备工作---------------
  43. //WSAWaitForMultipleEvents只能等待64个事件,若想更多,则创建额外的工作线程
  44. SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS]; WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
  45. int nEventCount = 0;
  46. sockArray[0]=socket_client; eventArray[nEventCount]=ClientEvent;
  47. nEventCount++;//事件个数+1,第1次等待1个事件,注意WSAWaitForMultipleEvents的参数1是动态
  48. int t=1;//超时次数
  49. //------------循环处理-------------
  50. while (1)
  51. {
  52. //---------------⑦等待事件对象--------------
  53. int nIndex=WSAWaitForMultipleEvents(nEventCount,eventArray,FALSE,40000,FALSE);//参数1:注意是动态增减的,不能固定死 .注:参数1与2本质一样,但数值不一样.如果参
  54. 数1为1个,那么数组括号内[]为0
  55. //参数3:参数1中的任何一个有消息进来,都立刻停止阻塞,运行下一步操作
  56. AfxMessageBox("响应事件,进入下一步\n");//进来时为0,响应时为对应的数组标签号
  57. if (nIndex==WSA_WAIT_FAILED)//------7.1调用失败---------
  58. {
  59. AfxMessageBox("WSAEventSelect调用失败\n");
  60. break;//退出while(1)循环
  61. }
  62. else if (nIndex==WSA_WAIT_TIMEOUT)//-------7.2超时---------
  63. {
  64. if (t<3)
  65. {
  66. AfxMessageBox("第【%d】次超时\n");
  67. t++;
  68. continue;
  69. }
  70. else
  71. {
  72. AfxMessageBox("第【%d】次超时,退出\n");
  73. break;
  74. }
  75. }
  76. //---------------7.3网络事件触发事件对象句柄的工作状态--------
  77. else
  78. {
  79. WSANETWORKEVENTS event;//该结构记录网络事件和对应出错代码
  80. //---------⑧网络事件查询-----------
  81. WSAEnumNetworkEvents(sockArray[nIndex-WSA_WAIT_EVENT_0],NULL,&event);
  82. WSAResetEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);
  83. if (event.lNetworkEvents&FD_READ)    //-------8.2处理FD_READ通知消息
  84. {
  85. if (event.iErrorCode[FD_READ_BIT]==0)
  86. {
  87. char m_RecvBuffer[4096];
  88. PCMD_HEADER pcm = (PCMD_HEADER)m_RecvBuffer;
  89. if(recv(sockArray[nIndex-WSA_WAIT_EVENT_0],(char*)&m_RecvBuffer,sizeof(m_RecvBuffer),0)==SOCKET_ERROR)
  90. {
  91. AfxMessageBox("接收失败,退出重recv接收!");
  92. break;
  93. }
  94. else
  95. {
  96. switch ( pcm->ncmd )
  97. {
  98. case CMD_AS_REP_C_MACHINE_LOGIN://很明显这个pcm->ncmd,是登录包中ncmd标识符
  99. {
  100. PAREP_C_MACHINE_LOGIN cmd = (PAREP_C_MACHINE_LOGIN)pcm;
  101. if (cmd->nStatus==1)
  102. {
  103. AfxMessageBox("收到登录回复包(Client->Server)状态:成功!");
  104. }
  105. else
  106. {
  107. AfxMessageBox("收到登录回复包(Client->Server)状态:失败!");
  108. }
  109. }
  110. break;
  111. }
  112. }
  113. }
  114. }
  115. else if (event.lNetworkEvents&FD_CLOSE)  //---------8.3处理FD_CLOSE通知消息
  116. {
  117. if (event.iErrorCode[FD_CLOSE_BIT]==0)                                       //客户端正常关闭
  118. {
  119. closesocket(sockArray[nIndex-WSA_WAIT_EVENT_0]);
  120. WSACloseEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);
  121. AfxMessageBox("套接字已关闭连接\n");//注:会触发7.1调用失败
  122. }
  123. else                                                                         //客户端异常已关闭
  124. {
  125. if (event.iErrorCode[FD_CLOSE_BIT]==10053)//右键->转到定义,可以查看到很多错误标识.按需设置(此处仅设置了客户端没有通知服务端,就非法关闭了)
  126. {
  127. closesocket(sockArray[nIndex-WSA_WAIT_EVENT_0]);
  128. WSACloseEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);
  129. AfxMessageBox("服务端非法关闭连接\n");//注:会触发7.1调用失败
  130. }
  131. }
  132. for (int j=nIndex-WSA_WAIT_EVENT_0;j<nEventCount-1;j++)
  133. {
  134. sockArray[j]=sockArray[j+1];
  135. eventArray[j]=eventArray[j+1];
  136. }
  137. nEventCount--;
  138. }
  139. }// end 网络事件触发
  140. }//end while
  141. //////////////////////////////////////////////////////////////////////////
  142. }
  143. AfxMessageBox("服务端已退出.客户端退出中\n");
  144. closesocket(socket_client);
  145. WSACleanup();
  146. return 0;
  147. }
  148. void CMyDlg::OnBnClickedButtonRun()
  149. {
  150. //发包
  151. C_MACHINE_LOGIN_SYSTEM cmd;
  152. strcpy(cmd.sMachineCode,"20100904164702750199");//机器码
  153. CString str;
  154. str.Format("%d",cmd.nVersion);
  155. if(send(socket_client,(char*)&cmd,sizeof(cmd),0)==SOCKET_ERROR)
  156. {
  157. #ifdef _DEBUG
  158. ::OutputDebugString("发送失败:发送机器码!\n");
  159. #endif // _DEBUG
  160. }
  161. }

这里就不再进行详细的分析,比照服务端,这里会更简单,需要说明的是,在这里可以使用WSAConnect这个函数来达到连接的目的,不用使用这个东西,当然,如果这样的话,你的发送和接收都要使用WSARecv和 WSASend函数。主要是使用overloapped重叠IO,使用起来更简单明了。

WSAEventSelect模型编程 详解的更多相关文章

  1. 事件驱动模型实例详解(Java篇)

    或许每个软件从业者都有从学习控制台应用程序到学习可视化编程的转变过程,控制台应用程序的优点在于可以方便的练习某个语言的语法和开发习惯(如.net和java),而可视化编程的学习又可以非常方便开发出各类 ...

  2. (转)Linux下select, poll和epoll IO模型的详解

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

  3. Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...

  4. ORACLE PL/SQL编程详解

    ORACLE PL/SQL编程详解 编程详解 SQL语言只是访问.操作数据库的语言,并不是一种具有流程控制的程序设计语言,而只有程序设计语言才能用于应用软件的开发.PL /SQL是一种高级数据库程序设 ...

  5. BS模式的模型结构详解

    编号:1004时间:2016年4月12日16:59:17功能:BS模式的模型结构详解 URL:http://blog.csdn.net/icerock2000/article/details/4000 ...

  6. Linux串口编程详解(转)

    串口本身,标准和硬件 † 串口是计算机上的串行通讯的物理接口.计算机历史上,串口曾经被广泛用于连接计算机和终端设备和各种外部设备.虽然以太网接口和USB接口也是以一个串行流进行数据传送的,但是串口连接 ...

  7. [强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!)

    原文:[强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!) [强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天 ...

  8. [推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆)

    原文:[推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆) [推荐]ORACLE PL/SQL编程详解之三: PL/SQL流程控制语句(不给规则,不成方圆) ...

  9. 【强烈强烈推荐】《ORACLE PL/SQL编程详解》全原创(共八篇)--系列文章导航

    原文:[强烈强烈推荐]<ORACLE PL/SQL编程详解>全原创(共八篇)--系列文章导航 <ORACLE PL/SQL编程详解> 系列文章目录导航 ——通过知识共享树立个人 ...

随机推荐

  1. 【ARTS】01_04_左耳听风-20181203~1209

    ARTS: Algrothm: leetcode算法题目 Review: 阅读并且点评一篇英文技术文章 Tip/Techni: 学习一个技术技巧 Share: 分享一篇有观点和思考的技术文章 Algo ...

  2. windows下使用pip安装python模块lxml

    pip install lxml 1 1 会有如下问题:  结果一路解决下去,解决了一个坑还是有一个坑,遂放弃,查找有没有别的解决办法. 亲测使用wheel+pip可以成功安装lxml! wheel本 ...

  3. Python学习四|变量、对象、引用的介绍

    变量 变量创建:一个变量也就是变量名,就像a,当代码第一次赋值时就创建了它.之后的赋值将会改变已创建的变量名的值,从技术上讲,Python在代码运行之前先检测变量名,可以当成是最初的赋值创建了变量. ...

  4. tomcat -> 简介&部署

    Tomcat 简介 Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache.Sun和其他一些公司及个人共同开 ...

  5. jquery-easyui:如何设置组件属性

    在这里以面板为例: $().ready(function() { $('#menu').tree({ url : '/menu', onClick : function(node) { $('#cen ...

  6. 小白学习安全测试(三)——扫描工具-Nikto使用

    扫描工具-Nikto #基于WEB的扫描工具,基本都支持两种扫描模式.代理截断模式,主动扫描模式 手动扫描:作为用户操作发现页面存在的问题,但可能会存在遗漏 自动扫描:基于字典,提高速度,但存在误报和 ...

  7. Network Principle Course Summary 001

    1.物理层 物理层 协议:RJ45.CLOCK.IEEE802.3 (中继器,集线器) 作用:通过媒介传输比特,确定机械及电气规范(比特Bit) 1.1 通信基础 数据 (data) —— 运送消息的 ...

  8. HDOJ题目分类

    模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 104 ...

  9. chrome使用技巧(转)

    原文:http://www.cnblogs.com/liyunhua/p/4544738.html 阅读目录 写在前面 快速切换文件 在源代码中搜索 在源代码中快速跳转到指定的行 使用多个插入符进行选 ...

  10. 005.Zabbix-Agent客户端安装

    一 安装Zabbix-Agent [root@imxhy ~]# rpm -ivh http://repo.zabbix.com/zabbix/3.2/rhel/7/x86_64/zabbix-rel ...