- #include <WinSock2.h>
- #include <Windows.h>
- #include <vector>
- #include <iostream>
- using namespace std;
- #pragma comment(lib, "Ws2_32.lib") // Socket編程需用的動態鏈接庫
- #pragma comment(lib, "Kernel32.lib") // IOCP需要用到的動態鏈接庫
- /**
- * 結構體名稱:PER_IO_DATA
- * 結構體功能:重疊I/O需要用到的結構體,臨時記錄IO數據
- **/
- const int DataBuffSize = 2 * 1024;
- typedef struct
- {
- OVERLAPPED overlapped;
- WSABUF databuff;
- char buffer[ DataBuffSize ];
- int BufferLen;
- int operationType;
- }PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
- /**
- * 結構體名稱:PER_HANDLE_DATA
- * 結構體存儲:記錄單個套接字的數據,包括了套接字的變量及套接字的對應的客戶端的地址。
- * 結構體作用:當服務器連接上客戶端時,信息存儲到該結構體中,知道客戶端的地址以便於回訪。
- **/
- typedef struct
- {
- SOCKET socket;
- SOCKADDR_STORAGE ClientAddr;
- }PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
- // 定義全局變量
- const int DefaultPort = 6000;
- vector < PER_HANDLE_DATA* > clientGroup; // 記錄客戶端的向量組
- HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
- DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
- DWORD WINAPI ServerSendThread(LPVOID IpParam);
- // 開始主函數
- int main()
- {
- // 加載socket動態鏈接庫
- WORD wVersionRequested = MAKEWORD(2, 2); // 請求2.2版本的WinSock庫
- WSADATA wsaData; // 接收Windows Socket的結構信息
- DWORD err = WSAStartup(wVersionRequested, &wsaData);
- if (0 != err){ // 檢查套接字庫是否申請成功
- cerr << "Request Windows Socket Library Error!\n";
- system("pause");
- return -1;
- }
- if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){// 檢查是否申請了所需版本的套接字庫
- WSACleanup();
- cerr << "Request Windows Socket Version 2.2 Error!\n";
- system("pause");
- return -1;
- }
- // 創建IOCP的內核對象
- /**
- * 需要用到的函數的原型:
- * HANDLE WINAPI CreateIoCompletionPort(
- * __in HANDLE FileHandle, // 已經打開的文件句柄或者空句柄,一般是客戶端的句柄
- * __in HANDLE ExistingCompletionPort, // 已經存在的IOCP句柄
- * __in ULONG_PTR CompletionKey, // 完成鍵,包含了指定I/O完成包的指定文件
- * __in DWORD NumberOfConcurrentThreads // 真正併發同時執行最大線程數,一般推介是CPU核心數*2
- * );
- **/
- HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
- if (NULL == completionPort){ // 創建IO內核對象失敗
- cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
- // 創建IOCP線程--線程裏面創建線程池
- // 確定處理器的核心數量
- SYSTEM_INFO mySysInfo;
- GetSystemInfo(&mySysInfo);
- // 基於處理器的核心數量創建線程
- for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i){
- // 創建服務器工作器線程,並將完成端口傳遞到該線程
- HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
- if(NULL == ThreadHandle){
- cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
- CloseHandle(ThreadHandle);
- }
- // 建立流式套接字
- SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);
- // 綁定SOCKET到本機
- SOCKADDR_IN srvAddr;
- srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
- srvAddr.sin_family = AF_INET;
- srvAddr.sin_port = htons(DefaultPort);
- int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
- if(SOCKET_ERROR == bindResult){
- cerr << "Bind failed. Error:" << GetLastError() << endl;
- system("pause");
- return -1;
- }
- // 將SOCKET設置爲監聽模式
- int listenResult = listen(srvSocket, 10);
- if(SOCKET_ERROR == listenResult){
- cerr << "Listen failed. Error: " << GetLastError() << endl;
- system("pause");
- return -1;
- }
- // 開始處理IO數據
- cout << "本服務器已準備就緒,正在等待客戶端的接入...\n";
- // 創建用於發送數據的線程
- HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
- while(true){
- PER_HANDLE_DATA * PerHandleData = NULL;
- SOCKADDR_IN saRemote;
- int RemoteLen;
- SOCKET acceptSocket;
- // 接收連接,並分配完成端,這兒可以用AcceptEx()
- RemoteLen = sizeof(saRemote);
- acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
- if(SOCKET_ERROR == acceptSocket){ // 接收客戶端失敗
- cerr << "Accept Socket Error: " << GetLastError() << endl;
- system("pause");
- return -1;
- }
- // 創建用來和套接字關聯的單句柄數據信息結構
- PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中爲這個PerHandleData申請指定大小的內存
- PerHandleData -> socket = acceptSocket;
- memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);
- clientGroup.push_back(PerHandleData); // 將單個客戶端數據指針放到客戶端組中
- // 將接受套接字和完成端口關聯
- CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);
- // 開始在接受套接字上處理I/O使用重疊I/O機制
- // 在新建的套接字上投遞一個或多個異步
- // WSARecv或WSASend請求,這些I/O請求完成後,工作者線程會爲I/O請求提供服務
- // 單I/O操作數據(I/O重疊)
- LPPER_IO_OPERATION_DATA PerIoData = NULL;
- PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
- ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));
- PerIoData->databuff.len = 1024;
- PerIoData->databuff.buf = PerIoData->buffer;
- PerIoData->operationType = 0; // read
- DWORD RecvBytes;
- DWORD Flags = 0;
- WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
- }
- system("pause");
- return 0;
- }
- // 開始服務工作線程函數
- DWORD WINAPI ServerWorkThread(LPVOID IpParam)
- {
- HANDLE CompletionPort = (HANDLE)IpParam;
- DWORD BytesTransferred;
- LPOVERLAPPED IpOverlapped;
- LPPER_HANDLE_DATA PerHandleData = NULL;
- LPPER_IO_DATA PerIoData = NULL;
- DWORD RecvBytes;
- DWORD Flags = 0;
- BOOL bRet = false;
- while(true){
- bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
- if(bRet == 0){
- cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
- return -1;
- }
- PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
- // 檢查在套接字上是否有錯誤發生
- if(0 == BytesTransferred){
- closesocket(PerHandleData->socket);
- GlobalFree(PerHandleData);
- GlobalFree(PerIoData);
- continue;
- }
- // 開始數據處理,接收來自客戶端的數據
- WaitForSingleObject(hMutex,INFINITE);
- cout << "A Client says: " << PerIoData->databuff.buf << endl;
- ReleaseMutex(hMutex);
- // 爲下一個重疊調用建立單I/O操作數據
- ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空內存
- PerIoData->databuff.len = 1024;
- PerIoData->databuff.buf = PerIoData->buffer;
- PerIoData->operationType = 0; // read
- WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
- }
- return 0;
- }
- // 發送信息的線程執行函數
- DWORD WINAPI ServerSendThread(LPVOID IpParam)
- {
- while(1){
- char talk[200];
- gets(talk);
- int len;
- for (len = 0; talk[len] != '\0'; ++len){
- // 找出這個字符組的長度
- }
- talk[len] = '\n';
- talk[++len] = '\0';
- printf("I Say:");
- cout << talk;
- WaitForSingleObject(hMutex,INFINITE);
- for(int i = 0; i < clientGroup.size(); ++i){
- send(clientGroup[i]->socket, talk, 200, 0); // 發送信息
- }
- ReleaseMutex(hMutex);
- }
- return 0;
- }
- // IOCP_TCPIP_Socket_Client.cpp
- #include <iostream>
- #include <cstdio>
- #include <string>
- #include <cstring>
- #include <winsock2.h>
- #include <Windows.h>
- using namespace std;
- #pragma comment(lib, "Ws2_32.lib") // Socket編程需用的動態鏈接庫
- SOCKET sockClient; // 連接成功後的套接字
- HANDLE bufferMutex; // 令其能互斥成功正常通信的信號量句柄
- const int DefaultPort = 6000;
- int main()
- {
- // 加載socket動態鏈接庫(dll)
- WORD wVersionRequested;
- WSADATA wsaData; // 這結構是用於接收Wjndows Socket的結構信息的
- wVersionRequested = MAKEWORD( 2, 2 ); // 請求2.2版本的WinSock庫
- int err = WSAStartup( wVersionRequested, &wsaData );
- if ( err != 0 ) { // 返回值爲零的時候是表示成功申請WSAStartup
- return -1;
- }
- if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢查版本號是否正確
- WSACleanup( );
- return -1;
- }
- // 創建socket操作,建立流式套接字,返回套接字號sockClient
- sockClient = socket(AF_INET, SOCK_STREAM, 0);
- if(sockClient == INVALID_SOCKET) {
- printf("Error at socket():%ld\n", WSAGetLastError());
- WSACleanup();
- return -1;
- }
- // 將套接字sockClient與遠程主機相連
- // int connect( SOCKET s, const struct sockaddr* name, int namelen);
- // 第一個參數:需要進行連接操作的套接字
- // 第二個參數:設定所需要連接的地址信息
- // 第三個參數:地址的長度
- SOCKADDR_IN addrSrv;
- addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地迴路地址是127.0.0.1;
- addrSrv.sin_family = AF_INET;
- addrSrv.sin_port = htons(DefaultPort);
- while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){
- // 如果還沒連接上服務器則要求重連
- cout << "服務器連接失敗,是否重新連接?(Y/N):";
- char choice;
- while(cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N')))){
- cout << "輸入錯誤,請重新輸入:";
- cin.sync();
- cin.clear();
- }
- if (choice == 'Y'){
- continue;
- }
- else{
- cout << "退出系統中...";
- system("pause");
- return 0;
- }
- }
- cin.sync();
- cout << "本客戶端已準備就緒,用戶可直接輸入文字向服務器反饋信息。\n";
- send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
- bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
- DWORD WINAPI SendMessageThread(LPVOID IpParameter);
- DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
- HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
- HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
- WaitForSingleObject(sendThread, INFINITE); // 等待線程結束
- closesocket(sockClient);
- CloseHandle(sendThread);
- CloseHandle(receiveThread);
- CloseHandle(bufferMutex);
- WSACleanup(); // 終止對套接字庫的使用
- printf("End linking...\n");
- printf("\n");
- system("pause");
- return 0;
- }
- DWORD WINAPI SendMessageThread(LPVOID IpParameter)
- {
- while(1){
- string talk;
- getline(cin, talk);
- WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用)
- if("quit" == talk){
- talk.push_back('\0');
- send(sockClient, talk.c_str(), 200, 0);
- break;
- }
- else{
- talk.append("\n");
- }
- printf("\nI Say:(\"quit\"to exit):");
- cout << talk;
- send(sockClient, talk.c_str(), 200, 0); // 發送信息
- ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
- }
- return 0;
- }
- DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
- {
- while(1){
- char recvBuf[300];
- recv(sockClient, recvBuf, 200, 0);
- WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用)
- printf("%s Says: %s", "Server", recvBuf); // 接收信息
- ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
- }
- return 0;
- }