AcceptEx,汗...),当然,你可以用类似:while(true) { AcceptEx(...);}, 但是这种busy loop显然是极其恶劣的。 先来看.h
view plaincopy to clipboardprint?
1. #pragma once
2. #include \ 3. #include
6. class CTcpServer 7. {
8. WSADATA wsd;
9. SOCKET m_ListeningSocket; 10. ADDRINFOW* m_pAddrInfo; 11. HANDLE m_AcceptEvent; 12.
13. public:
14. CTcpServer(PCWSTR pIPAddress, PCWSTR port); 15.
16. ~CTcpServer(); 17.
18. SOCKET& Socket() 19. {
20. return m_ListeningSocket; 21. } 22.
23. bool StartListening(); 24.
25. int AddressFamily() 26. {
27. return m_pAddrInfo->ai_family; 28. } 29.
30. int SocketType() 31. {
32. return m_pAddrInfo->ai_socktype; 33. } 34.
35. int Protocol() 36. {
37. return m_pAddrInfo->ai_protocol; 38. }
39.
40. BOOL WaitForAcceptEvent(DWORD timeout); 41.
42. BOOL AcceptNewConnection(); 43. 44. };
没什么稀奇的,如我所说,关键在于WaitForAcceptEvent()上,下面的代码是具体实现:
view plaincopy to clipboardprint?
1. #include \ 2.
3. #include \ 4. #include \ 5.
6. //初始化socket,稍微改改可以支持ipv6,先不管它
7. CTcpServer::CTcpServer(PCWSTR pAddress, PCWSTR port)
8. :m_ListeningSocket(INVALID_SOCKET), m_pAddrInfo(NULL), m_AcceptEvent
(NULL) 9. {
10. int rc = WSAStartup(MAKEWORD(2, 2), &wsd); 11. if (rc != 0) {
12. wprintf(L\); 13. throw new exception(); 14. } 15.
16. // Initialize the hints to retrieve the server address for IPv4
17. ADDRINFOW *result = NULL, 18. *ptr = NULL, 19. hints = {0}; 20.
21. hints.ai_family = AF_INET; 22. hints.ai_socktype = SOCK_STREAM; 23. hints.ai_protocol = IPPROTO_TCP; 24.
25. rc =::GetAddrInfoW(pAddress, port, &hints, &m_pAddrInfo); 26. if (rc != 0) {
27. printf(\, rc ); 28. throw new exception(); 29. } 30.
31. m_ListeningSocket = WSASocket(AF_INET, 32. SOCK_STREAM,
33. IPPROTO_TCP, NULL, 0, WSA_FLAG_OVE
RLAPPED); 34. 35. } 36.
37. // dtor负责cleanup,没什么特别 38. CTcpServer::~CTcpServer() 39. {
40. if (m_ListeningSocket != INVALID_SOCKET) 41. {
42. closesocket(m_ListeningSocket); 43. } 44.
45. if (m_AcceptEvent != NULL && m_AcceptEvent != INVALID_HANDLE_VAL
UE) 46. {
47. ::CloseHandle(m_AcceptEvent); 48. } 49.
50. WSACleanup(); 51. } 52.
53. // bind和listen,然后构造FD_ACCEPT event 54. bool CTcpServer::StartListening() 55. {
56. if (bind(m_ListeningSocket, m_pAddrInfo->ai_addr, m_pAddrInfo->a
i_addrlen) == SOCKET_ERROR) 57. {
58. return false; 59. } 60.
61. if (listen(m_ListeningSocket, 1 ) == SOCKET_ERROR) 62. {
63. printf(\); 64. return false; 65. } 66.
67. //这个很重要,先创建event,然后用WSAEventSelect表示我们只对 68. // FD_ACCEPT感兴趣
69. m_AcceptEvent = WSACreateEvent();
70. if(SOCKET_ERROR == WSAEventSelect(m_ListeningSocket, m_AcceptEve
nt, FD_ACCEPT)) 71. {
72. printf(\, WSAGetLastError());
73. return false; 74. } 75.
76. return true; 77. } 78.
79. // 等待FD_ACCEPT event
80. // 注意,跟普通event不一样,我们无需用ResetEvent()/SetEvent() 81. // 这些API,这个类似于Pulse
82. BOOL CTcpServer::WaitForAcceptEvent(DWORD timeout) 83. {
84. DWORD ret = WSAWaitForMultipleEvents(1, &m_AcceptEvent, FALSE, t
imeout, FALSE);
85. if (WSA_WAIT_TIMEOUT == ret || WSA_WAIT_FAILED == ret) 86. {
87. printf(\); 88. return false; 89. }
90. // 收到event
91. printf(\); 92.
93. WSANETWORKEVENTS events; 94.
95. // 检查该event是否在正确socket上
96. int nRet = WSAEnumNetworkEvents(m_ListeningSocket, m_AcceptEvent
, &events); 97.
98. if (nRet == SOCKET_ERROR) 99. {
100. printf(\); 101. return false; 102. } 103.
104. // 确认是FD_ACCEPT event
105. if (events.lNetworkEvents & FD_ACCEPT) 106. {
107. printf(\); 108. return true; 109. } 110.
111. return false; 112. } 113.
114. // 接受新连接请求
115. BOOL CTcpServer::AcceptNewConnection() 116. {
117. DWORD dwBytes; 118.
119. // OVERLAPPEDPLUS的初始化比较繁琐,所以专门用一个function
120. OVERLAPPEDPLUS* pOverlapPlus = ::CreateOverlappedPlus(); 121.
122. pOverlapPlus->serverSock = m_ListeningSocket; 123. pOverlapPlus->OpCode = OP_ACCEPT; 124.
125. pOverlapPlus->clientSock = socket(m_pAddrInfo->ai_family, m
_pAddrInfo->ai_socktype, m_pAddrInfo->ai_protocol); 126.
127. // 注意 AcceptEx是立即返回的,而且一般都是FALSE,如果要确认 128. // 是否出错,需要再调用WSAGetLastError()看是否是IO_PENDING,如 129. // 果是pending就没关系
130. // 另外, AcceptEx把client address放在接收buffer的最后,所以下 131. // 面会看到有一些\
132. return AcceptEx(m_ListeningSocket, pOverlapPlus->clientSock
, pOverlapPlus->wbuf.buf, pOverlapPlus->wbuf.len - ((sizeof(sockaddr_in) + 16) * 2), sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16, &dwBytes, &pOverlapPlus->overlapped); 133. } 134.
135. //创建OVERLAPPEDPLUS
136. // 其实还有个Free的function,也就是多free一下mbuf,就 137. //不写了:D
138. OVERLAPPEDPLUS* CreateOverlappedPlus() 139. {
140. OVERLAPPEDPLUS* pOverlapPlus = (OVERLAPPEDPLUS *)malloc(sizeof(
OVERLAPPEDPLUS));
141. memset(pOverlapPlus, 0, sizeof(OVERLAPPEDPLUS)); 142.
143. char* pBuffer = (char*)malloc(DATA_BUFSIZE); 144. pOverlapPlus->wbuf.buf = pBuffer; 145. pOverlapPlus->wbuf.len = DATA_BUFSIZE; 146.
147. return pOverlapPlus; 148. }
差不多就酱紫。另外说明在thread中,收到的Accept是带了第一个TCP payload的,所以如果你在AcceptEx之后去WSARecvFrom,是收不到东西的(因为已经收到了, 注意AcceptEx用pOverlapPlus->wbuf.buf 接收数据) 偶认为本人这篇是世界上最清晰易懂的IO Completion 教程:D