Socket I/O模型之完成端口(completion port)
C++代码
1. // write by larry
2. // 2009-8-20
3. // This is a server using completion port.
4. #include "stdafx.h"
5. #include <WINSOCK2.H>
6. #include <stdio.h>
7. #pragma comment(lib, "ws2_32.lib")
8. #define PORT 5150
9. #define MSGSIZE 1024
10. typedef enum
11. {
12. RECV_POSTED
13. } OPERATION_TYPE;
14. typedef struct
15. {
16. WSAOVERLAPPED overlap;
17. WSABUF Buffer;
18. char szMessage[MSGSIZE];
19. DWORD NumberOfBytesRecvd;
20. DWORD Flags;
21. OPERATION_TYPE OperationType;
22. } PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
23. DWORD WINAPI WorkerThread(LPVOID CompletionPortID);
24.
25. int main(int argc, char* argv[])
26. {
27. WSADATA wsaData;
28. SOCKET sListen, sClient;
29. SOCKADDR_IN local, client;
30. DWORD i, dwThreadId;
31. int iAddrSize = sizeof(SOCKADDR_IN);
32. HANDLE CompletionPort = INVALID_HANDLE_VALUE;
33. SYSTEM_INFO sysinfo;
34. LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
35. // Initialize windows socket library
36. WSAStartup(0x0202, &wsaData);
37. // Create completion port
38. CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
39. // Create worker thread
40. GetSystemInfo(&sysinfo);
41. for (i = 0; i < sysinfo.dwNumberOfProcessors; i++)
42. {
43. CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
44. }
45. // Create listening socket
46. sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
47. // Bind
48. local.sin_family = AF_INET;
49. local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
50. local.sin_port = htons(PORT);
51. bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
52. // Listen
53. listen(sListen, 3);
54. while (TRUE)
55. {
56. // Accept a connection
57. sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
58. printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
59. // Associate the newly arrived client socket with completion port
60. CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
61. // Launch an asynchronous operation for new arrived connection
62. lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
63. GetProcessHeap(),
64. HEAP_ZERO_MEMORY,
65. sizeof(PER_IO_OPERATION_DATA));
66. lpPerIOData->Buffer.len = MSGSIZE;
67. lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
68. lpPerIOData->OperationType = RECV_POSTED;
69. WSARecv(sClient,
70. &lpPerIOData->Buffer,
71. 1,
72. &lpPerIOData->NumberOfBytesRecvd,
73. &lpPerIOData->Flags,
74. &lpPerIOData->overlap,
75. NULL);
76. }
77. PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
78. CloseHandle(CompletionPort);
79. closesocket(sListen);
80. WSACleanup();
81. return 0;
82. }
83. DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
84. {
85. HANDLE CompletionPort = (HANDLE)CompletionPortID;
86. DWORD dwBytesTransferred;
87. SOCKET sClient;
88. LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
89. while (TRUE)
90. {
91. GetQueuedCompletionStatus(
92. CompletionPort,
93. &dwBytesTransferred,
94. (DWORD*)&sClient,
95. (LPOVERLAPPED*)&lpPerIOData,
96. INFINITE);
97. if (dwBytesTransferred == 0xFFFFFFFF)
98. {
99. return 0;
100. }
101. if (lpPerIOData->OperationType == RECV_POSTED)
102. {
103. if (dwBytesTransferred == 0)
104. {
105. // Connection was closed by client
106. closesocket(sClient);
107. HeapFree(GetProcessHeap(), 0, lpPerIOData);
108. }
109. else
110. {
111. lpPerIOData->szMessage[dwBytesTransferred] = '\0';
112. send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);
113.
114. // Launch another asynchronous operation for sClient
115. memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
116. lpPerIOData->Buffer.len = MSGSIZE;
117. lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
118. lpPerIOData->OperationType = RECV_POSTED;
119. WSARecv(sClient,
120. &lpPerIOData->Buffer,
121. 1,
122. &lpPerIOData->NumberOfBytesRecvd,
123. &lpPerIOData->Flags,
124. &lpPerIOData->overlap,
125. NULL);
126. }
127. }
128. }
129. return 0;
130. }
服务器端得主要流程: 1.创建完成端口对象2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能) 3.创建监听套接字,绑定,监听,然后程序进入循环4.在循环中,我做了以下几件事情: (1).接受一个客户端连接
(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递; (3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。在工作者线程的循环中,我们 1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等) 2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端3.再次触发一个WSARecv异步操作