單個服務器對多個客戶端程序:
一。簡要說明
二。查看效果
三。編寫思路
四。程序源代碼
五。存在問題
一。簡要說明:
程序名爲:TcpSocketOneServerToMulClient
程序功能:實現單個服務器對多個客戶端通訊功能的小程序。
PS: 這是繼上次簡單的 Tcp Windows Socket 編程後的再一程序,程序實現依然不是很嚴謹,還待完善~
二。查看效果:
三。編寫思路:
由上一次的程序思路來看,如果想實現單個服務器對多個客戶端程序的通訊的話,這次程序編寫嘗試從多線程的角度來考慮問題:
在服務器的實現中:可以main函數作爲主線程,不斷地接客戶端的連接請求。
再新建子線程——每連接一個客戶端,就專爲這個客戶端新建一個用於實現接收信息並顯示到屏幕上功能的子線程。
然後,新建子線程,專用於本機發送消息。
在客戶端的實現中:主線程負責連接服務器,新建子線程,用於從服務器接收信息,再建子線程,用於從客戶端向服務器中發送信息。
總的來說,也可以理解爲,單個服務器的進程利用這個服務器中的線程與多個客戶端進程進行通訊。
四。程序源代碼:
// OneServerMain.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <iterator>
#include <algorithm>
#include <Winsock2.h>
#include <Windows.h>
using namespace std;
HANDLE bufferMutex; // 令其能互斥成功正常通信的信號量句柄
SOCKET sockConn; // 客戶端的套接字
vector <SOCKET> clientSocketGroup;
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 ) {
return -1; // 返回值爲零的時候是表示成功申請WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢測是否2.2版本的socket庫
WSACleanup( );
return -1;
}
// 創建socket操作,建立流式套接字,返回套接字號sockSrv
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 套接字sockSrv與本地地址相連
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 將INADDR_ANY轉換爲網絡字節序,調用 htonl(long型)或htons(整型)
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二參數要強制類型轉換
return -1;
}
// 將套接字設置爲監聽模式(連接請求), listen()通知TCP服務器準備好接收連接
listen(sockSrv, 20);
cout << "服務器已成功就緒,若服務器想發送信息給客戶端,可直接輸入內容後按回車.\n";
// accept(),接收連接,等待客戶端連接
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);
while(true){ // 不斷等待客戶端請求的到來
sockConn = accept(sockSrv, NULL, NULL);
if (SOCKET_ERROR != sockConn){
clientSocketGroup.push_back(sockConn);
}
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, (LPVOID)sockConn, 0, NULL);
WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用)
if(NULL == receiveThread) {
printf("\nCreatThread AnswerThread() failed.\n");
}
else{
printf("\nCreate Receive Client Thread OK.\n");
}
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
}
WaitForSingleObject(sendThread, INFINITE); // 等待線程結束
CloseHandle(sendThread);
CloseHandle(bufferMutex);
WSACleanup(); // 終止對套接字庫的使用
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){
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
return 0;
}
else*/
{
talk.append("\n");
}
printf("I Say:(\"quit\"to exit):");
cout << talk;
for(int i = 0; i < clientSocketGroup.size(); ++i){
// send(clientSocketGroup[i], talk.c_str(), talk.size(), 0); // 發送信息
send(clientSocketGroup[i], talk.c_str(), 200, 0); // 發送信息
}
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;
while(1){
char recvBuf[300];
recv(ClientSocket, recvBuf, 200, 0);
WaitForSingleObject(bufferMutex, INFINITE); // P(資源未被佔用)
if (recvBuf[0] == 'q' && recvBuf[1] == 'u' && recvBuf[2] == 'i' && recvBuf[3] == 't' && recvBuf[4] == '\0'){
vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket);
clientSocketGroup.erase(result);
closesocket(ClientSocket);
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
printf("\nAttention: A Client has leave...\n", 200, 0);
break;
}
printf("%s Says: %s\n", "One Client", recvBuf); // 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
}
return 0;
}
// MulClientMain.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h>
using namespace std;
SOCKET sockClient; // 連接成功後的套接字
HANDLE bufferMutex; // 令其能互斥成功正常通信的信號量句柄
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(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
cout << "本客戶端已準備就緒,用戶可直接輸入文字向服務器反饋信息。\n";
// send(sockClient, "\nAttention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);
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(), talk.size(), 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(), talk.size(), 0); // 發送信息
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\n", "Server", recvBuf); // 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(資源佔用完畢)
}
return 0;
}
五。存在問題:
1. 將客戶端異常退出(不按程序要求輸入“quit”退出),而直接用ALT+F4關閉後,服務器會出現死循環顯示亂碼的情況。
2. 在未啓動服務器前提下,直接啓動客戶端時出現死循環亂碼情況。
3. 服務器輸入“quit”時不能正常退出。