Windows Socket 程式設計_單個伺服器對多個客戶端簡單通訊
阿新 • • 發佈:2019-02-04
單個伺服器對多個客戶端程式:
一。簡要說明
二。檢視效果
三。編寫思路
四。程式原始碼
五。存在問題
一。簡要說明:
程式名為:TcpSocketOneServerToMulClient
程式功能:實現單個伺服器對多個客戶端通訊功能的小程式。
PS: 這是繼上次簡單的 Tcp Windows Socket 程式設計後的再一程式,程式實現依然不是很嚴謹,還待完善~
二。檢視效果:
三。編寫思路:
由上一次的程式思路來看,如果想實現單個伺服器對多個客戶端程式的通訊的話,這次程式編寫嘗試從多執行緒的角度來考慮問題:
在伺服器的實現中:可以main函式作為主執行緒,不斷地接客戶端的連線請求。
再新建子執行緒——每連線一個客戶端,就專為這個客戶端新建一個用於實現接收資訊並顯示到螢幕上功能的子執行緒。
然後,新建子執行緒,專用於本機發送訊息。
在客戶端的實現中:主執行緒負責連線伺服器,新建子執行緒,用於從伺服器接收資訊,再建子執行緒,用於從客戶端向伺服器中傳送資訊。
總的來說,也可以理解為,單個伺服器的程序利用這個伺服器中的執行緒與多個客戶端程序進行通訊。
四。程式原始碼:
1 // OneServerMain.cpp 2 3 #include <iostream> 4 #include <cstdio> 5 #include <string> 6#include <cstring> 7 #include <vector> 8 #include <iterator> 9 #include <algorithm> 10 #include <Winsock2.h> 11 #include <Windows.h> 12 13 using namespace std; 14 HANDLE bufferMutex; // 令其能互斥成功正常通訊的訊號量控制代碼 15 SOCKET sockConn; //客戶端的套接字 16 vector <SOCKET> clientSocketGroup; 17 18 int main() 19 { 20 // 載入socket動態連結庫(dll) 21 WORD wVersionRequested; 22 WSADATA wsaData; // 這結構是用於接收Wjndows Socket的結構資訊的 23 wVersionRequested = MAKEWORD( 2, 2 ); // 請求2.2版本的WinSock庫 24 int err = WSAStartup( wVersionRequested, &wsaData ); 25 if ( err != 0 ) { 26 return -1; // 返回值為零的時候是表示成功申請WSAStartup 27 } 28 if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢測是否2.2版本的socket庫 29 WSACleanup( ); 30 return -1; 31 } 32 33 // 建立socket操作,建立流式套接字,返回套接字號sockSrv 34 SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); 35 36 // 套接字sockSrv與本地地址相連 37 SOCKADDR_IN addrSrv; 38 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 將INADDR_ANY轉換為網路位元組序,呼叫 htonl(long型)或htons(整型) 39 addrSrv.sin_family = AF_INET; 40 addrSrv.sin_port = htons(6000); 41 42 if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二引數要強制型別轉換 43 return -1; 44 } 45 46 // 將套接字設定為監聽模式(連線請求), listen()通知TCP伺服器準備好接收連線 47 listen(sockSrv, 20); 48 49 cout << "伺服器已成功就緒,若伺服器想傳送資訊給客戶端,可直接輸入內容後按回車.\n"; 50 // accept(),接收連線,等待客戶端連線 51 52 bufferMutex = CreateSemaphore(NULL, 1, 1, NULL); 53 54 DWORD WINAPI SendMessageThread(LPVOID IpParameter); 55 DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter); 56 57 HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL); 58 59 while(true){ // 不斷等待客戶端請求的到來 60 sockConn = accept(sockSrv, NULL, NULL); 61 if (SOCKET_ERROR != sockConn){ 62 clientSocketGroup.push_back(sockConn); 63 } 64 HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, (LPVOID)sockConn, 0, NULL); 65 WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用) 66 if(NULL == receiveThread) { 67 printf("\nCreatThread AnswerThread() failed.\n"); 68 } 69 else{ 70 printf("\nCreate Receive Client Thread OK.\n"); 71 } 72 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 73 } 74 75 WaitForSingleObject(sendThread, INFINITE); // 等待執行緒結束 76 CloseHandle(sendThread); 77 CloseHandle(bufferMutex); 78 WSACleanup(); // 終止對套接字型檔的使用 79 printf("\n"); 80 system("pause"); 81 return 0; 82 } 83 84 85 DWORD WINAPI SendMessageThread(LPVOID IpParameter) 86 { 87 while(1){ 88 string talk; 89 getline(cin, talk); 90 WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用) 91 /* if("quit" == talk){ 92 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 93 return 0; 94 } 95 else*/ 96 { 97 talk.append("\n"); 98 } 99 printf("I Say:(\"quit\"to exit):"); 100 cout << talk; 101 for(int i = 0; i < clientSocketGroup.size(); ++i){ 102 // send(clientSocketGroup[i], talk.c_str(), talk.size(), 0); // 傳送資訊 103 send(clientSocketGroup[i], talk.c_str(), 200, 0); // 傳送資訊 104 } 105 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 106 } 107 return 0; 108 } 109 110 111 DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter) 112 { 113 SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter; 114 while(1){ 115 char recvBuf[300]; 116 recv(ClientSocket, recvBuf, 200, 0); 117 WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用) 118 119 if (recvBuf[0] == 'q' && recvBuf[1] == 'u' && recvBuf[2] == 'i' && recvBuf[3] == 't' && recvBuf[4] == '\0'){ 120 vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket); 121 clientSocketGroup.erase(result); 122 closesocket(ClientSocket); 123 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 124 printf("\nAttention: A Client has leave...\n", 200, 0); 125 break; 126 } 127 128 printf("%s Says: %s\n", "One Client", recvBuf); // 接收資訊 129 130 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 131 } 132 return 0; 133 }
1 // MulClientMain.cpp 2 3 #include <iostream> 4 #include <cstdio> 5 #include <string> 6 #include <cstring> 7 #include <winsock2.h> 8 #include <Windows.h> 9 10 using namespace std; 11 12 SOCKET sockClient; // 連線成功後的套接字 13 HANDLE bufferMutex; // 令其能互斥成功正常通訊的訊號量控制代碼 14 15 int main() 16 { 17 // 載入socket動態連結庫(dll) 18 WORD wVersionRequested; 19 WSADATA wsaData; // 這結構是用於接收Wjndows Socket的結構資訊的 20 wVersionRequested = MAKEWORD( 2, 2 ); // 請求2.2版本的WinSock庫 21 int err = WSAStartup( wVersionRequested, &wsaData ); 22 if ( err != 0 ) { // 返回值為零的時候是表示成功申請WSAStartup 23 return -1; 24 } 25 if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢查版本號是否正確 26 WSACleanup( ); 27 return -1; 28 } 29 30 // 建立socket操作,建立流式套接字,返回套接字號sockClient 31 sockClient = socket(AF_INET, SOCK_STREAM, 0); 32 if(sockClient == INVALID_SOCKET) { 33 printf("Error at socket():%ld\n", WSAGetLastError()); 34 WSACleanup(); 35 return -1; 36 } 37 38 // 將套接字sockClient與遠端主機相連 39 // int connect( SOCKET s, const struct sockaddr* name, int namelen); 40 // 第一個引數:需要進行連線操作的套接字 41 // 第二個引數:設定所需要連線的地址資訊 42 // 第三個引數:地址的長度 43 SOCKADDR_IN addrSrv; 44 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地迴路地址是127.0.0.1; 45 addrSrv.sin_family = AF_INET; 46 addrSrv.sin_port = htons(6000); 47 connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); 48 cout << "本客戶端已準備就緒,使用者可直接輸入文字向伺服器反饋資訊。\n"; 49 50 // send(sockClient, "\nAttention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0); 51 send(sockClient, "\nAttention: A Client has enter...\n", 200, 0); 52 53 bufferMutex = CreateSemaphore(NULL, 1, 1, NULL); 54 55 DWORD WINAPI SendMessageThread(LPVOID IpParameter); 56 DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter); 57 58 HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL); 59 HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL); 60 61 62 WaitForSingleObject(sendThread, INFINITE); // 等待執行緒結束 63 closesocket(sockClient); 64 CloseHandle(sendThread); 65 CloseHandle(receiveThread); 66 CloseHandle(bufferMutex); 67 WSACleanup(); // 終止對套接字型檔的使用 68 69 printf("End linking...\n"); 70 printf("\n"); 71 system("pause"); 72 return 0; 73 } 74 75 76 DWORD WINAPI SendMessageThread(LPVOID IpParameter) 77 { 78 while(1){ 79 string talk; 80 getline(cin, talk); 81 WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用) 82 if("quit" == talk){ 83 talk.push_back('\0'); 84 // send(sockClient, talk.c_str(), talk.size(), 0); 85 send(sockClient, talk.c_str(), 200, 0); 86 break; 87 } 88 else{ 89 talk.append("\n"); 90 } 91 printf("\nI Say:(\"quit\"to exit):"); 92 cout << talk; 93 // send(sockClient, talk.c_str(), talk.size(), 0); // 傳送資訊 94 send(sockClient, talk.c_str(), 200, 0); // 傳送資訊 95 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 96 } 97 return 0; 98 } 99 100 101 DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter) 102 { 103 while(1){ 104 char recvBuf[300]; 105 recv(sockClient, recvBuf, 200, 0); 106 WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用) 107 108 printf("%s Says: %s\n", "Server", recvBuf); // 接收資訊 109 110 ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢) 111 } 112 return 0; 113 }
五。存在問題:
1. 將客戶端異常退出(不按程式要求輸入“quit”退出),而直接用ALT+F4關閉後,伺服器會出現死迴圈顯示亂碼的情況。
2. 在未啟動伺服器前提下,直接啟動客戶端時出現死迴圈亂碼情況。
3. 伺服器輸入“quit”時不能正常退出。
轉http://blog.csdn.net/neicole/article/details/7539444