WSAEventSelect模型编程 详解

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

WSAEventSelect模型编程

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

[cpp] view plaincopyprint?

  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()这个函数来处理事件,先记下。
然后接着讲,

[cpp] view plaincopyprint?

  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 事件的监听

[cpp] view plaincopyprint?

  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,

[cpp] view plaincopyprint?

  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无限等待所有的事件,只要有一个事件响应,就会进入到下一层循环,如果是接收就重复上述的动作,如果是读写就进入:

[cpp] view plaincopyprint?

  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 触发的前提是 缓冲区要先被充满然后随着数据的发送又出现可用空间,而不是缓冲区中有可用空间,也就是说像如下的调用方式可能出现问题

[cpp] view plaincopyprint?

  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 事件处理

[cpp] view plaincopyprint?

  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通信应该就没有什么问题了。重要的是你要把这个服务端封装好,有时间做一下。


三、例程(客户端)

先上一段代码:

[cpp] view plaincopyprint?

  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,使用起来更简单明了。

时间: 2024-10-13 11:51:07

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

Java多线程编程详解

线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synch

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

或许每个软件从业者都有从学习控制台应用程序到学习可视化编程的转变过程,控制台应用程序的优点在于可以方便的练习某个语言的语法和开发习惯(如.net和java),而可视化编程的学习又可以非常方便开发出各类人机对话界面(HMI).可视化编程或许是一个初学者开始对软件感兴趣的开始,也可能是一个软件学习的里程碑点,因为我们可以使用各类软件集成开发环境(IDE)方便的在现成的界面窗口上拖放各类组件(Component),这类组件包括我们常见的按钮(Button),单选按钮(Radio Button),复选框

Java内存模型(JMM)详解

在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的理解底层原理,写出更高效的代码. 就Java内存模型而言,它是深入了解Java并发编程的先决条件.对于后续多线程中的线程安全.同步异步处理等更是大有裨益. 硬件内存架构 在学习Java内存模型之前,先了解一下计算机硬件内存模型.我们多知道处理器与计算机存储设备运算速度有几个数量级的差别.总不能让处理

[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)

原文:[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [顶]ORACLE PL/SQL编程详解之二: PL/SQL块结构和组成元素(为山九仞,岂一日之功) 继上四篇:ORACLE PL/SQL编程之八:把触发器说透                ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)                [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]

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

原文:[强烈强烈推荐]<ORACLE PL/SQL编程详解>全原创(共八篇)--系列文章导航 <ORACLE PL/SQL编程详解> 系列文章目录导航 ——通过知识共享树立个人品牌. 本是成书的,但后来做其他事了,就无偿的贡献出来,被读者夸其目前为止最“实在.经典”的写ORACLE PL/SQL编程的文章-! 觉得对你有帮助,请留言与猛点推荐,谢谢. [推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下) 本篇主要内容如下:第一章 PL/S

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

原文:[推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆) [推荐]ORACLE PL/SQL编程详解之三: PL/SQL流程控制语句(不给规则,不成方圆) ——通过知识共享树立个人品牌. 继上五篇: [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼.百战不

[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下)

原文:[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下) [推荐]ORACLE PL/SQL编程详解之一: PL/SQL 程序设计简介(千里之行,始于足下) ——通过知识共享树立个人品牌. 继上六篇: [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆) [推荐]ORACLE PL/SQL编程之四:把游标说透(

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

原文:[强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!) [强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天才在于积累!) ——通过知识共享树立个人品牌.   继上七篇:            [推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下)            [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之

Qt Quick 之 QML 与 C++ 混合编程详解

Qt Quick 技术的引入,使得你能够快速构建 UI ,具有动画.各种绚丽效果的 UI 都不在话下.但它不是万能的,也有很多局限性,原来 Qt 的一些技术,比如低阶的网络编程如 QTcpSocket ,多线程,又如 XML 文档处理类库 QXmlStreamReader / QXmlStreamWriter 等等,在 QML 中要么不可用,要么用起来不方便,所以呢,很多时候我们是会基于这样的原则来混合使用 QML 和 C++: QML 构建界面, C++ 实现非界面的业务逻辑和复杂运算. 请给