單個伺服器對多個用戶端程式:
一。簡要說明
二。檢視效果
三。編寫思路
四。程式源代碼
五。存在問題
一。簡要說明:
程式名為:TcpSocketOneServerToMulClient
程式功能:實作單個伺服器對多個用戶端通訊功能的小程式。
二。檢視效果:

三。編寫思路:
由上一次的程式思路來看,如果想實作單個伺服器對多個用戶端程式的通訊的話,這次程式編寫嘗試從多線程的角度來考慮問題:
在伺服器的實作中:可以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(資源占用完畢)
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;
while(1){
char recvBuf[300];
recv(ClientSocket, recvBuf, 200, 0);
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); // 接收資訊
// MulClientMain.cpp
#include <winsock2.h>
SOCKET sockClient; // 連接配接成功後的套接字
if ( err != 0 ) { // 傳回值為零的時候是表示成功申請WSAStartup
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢查版本号是否正确
// 建立socket操作,建立流式套接字,傳回套接字号sockClient
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if(sockClient == INVALID_SOCKET) {
printf("Error at socket():%ld\n", WSAGetLastError());
WSACleanup();
}
// 将套接字sockClient與遠端主機相連
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一個參數:需要進行連接配接操作的套接字
// 第二個參數:設定所需要連接配接的位址資訊
// 第三個參數:位址的長度
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路位址是127.0.0.1;
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);
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
closesocket(sockClient);
CloseHandle(receiveThread);
printf("End linking...\n");
if("quit" == talk){
talk.push_back('\0');
// send(sockClient, talk.c_str(), talk.size(), 0);
send(sockClient, talk.c_str(), 200, 0);
else{
printf("\nI Say:(\"quit\"to exit):");
cout << talk;
// send(sockClient, talk.c_str(), talk.size(), 0); // 發送資訊
send(sockClient, talk.c_str(), 200, 0); // 發送資訊
recv(sockClient, recvBuf, 200, 0);
printf("%s Says: %s\n", "Server", recvBuf); // 接收資訊
五。存在問題:
1. 将用戶端異常退出(不按程式要求輸入“quit”退出),而直接用ALT+F4關閉後,伺服器會出現死循環顯示亂碼的情況。
2. 在未啟動伺服器前提下,直接啟動用戶端時出現死循環亂碼情況。
3. 伺服器輸入“quit”時不能正常退出。