C++多线程实现群聊服务端和客户端

服务端实现代码:

#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define MAX_CLNT 256
#define MAX_BUF_SIZE 1024

using namespace std;

SOCKET clnSocks[MAX_CLNT]; // 所有的连接的客户端的socket
int clntCnt = 0; // 客户端连接的个数

HANDLE hMutex; 

// 将收到的转发给客户端
void SendMsg(char *szMsg, int iLen) {
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clntCnt; i++)
	{
		send(clnSocks[i], szMsg, iLen, 0);
	}
	ReleaseMutex(hMutex);
}

// 处理收发消息
unsigned WINAPI HandleCln(void* arg) {
	// 此时连接的socket的标记
	SOCKET hClntSock = *((SOCKET*)arg);
	int iLen = 0, i;
	char szMsg[MAX_BUF_SIZE] = { 0 };
	while (1)
	{
		iLen = recv(hClntSock, szMsg, sizeof(szMsg), 0);
		if (iLen != -1) {
			// 将收到的转发给客户端
			SendMsg(szMsg, iLen);
		}
		else {
			// 断开
			break;
		}
	}
	cout << "此时连接的数目为:" << clntCnt << endl;

	// 处理下线的过程
	// 3 确定是哪一个连接下线 遍历
	WaitForSingleObject(hMutex, INFINITE);

	for (i = 0; i < clntCnt; i++) {
		if (hClntSock == clnSocks[i]) {
			// 已经找到了是哪个连接下线的 移除这个连接
			while (i ++ < clntCnt)
			{
				clnSocks[i] = clnSocks[i + 1];
			}
			// 移除完毕
			break;
		}
	}
	// 移除之后总数减少1
	clntCnt--;
	cout << "断开此时的连接后总数是:"<< clntCnt << endl;
	ReleaseMutex(hMutex);
	closesocket(hClntSock);
	return 0;
}

int main() {
	cout << "This is Server!" << endl;
	// 加载套节字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	HANDLE hThread;
	wVersionRequested = MAKEWORD(1, 1);
	
	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();
		return -1;
	}
	// 创建一个互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);

	// 创建服务器套节字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	// 3 分配电话号码
	 // 绑定套接字到本地IP地址
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		cout << "socket error code = " << GetLastError() << endl;
		return -1;
	}

	// 4、监听 listen
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
		cout << "listen error code = " << GetLastError() << endl;
		return -1;
	}
	cout << "start listen" << endl;

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (1)
	{
		// 接收来自客户端的连接
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		WaitForSingleObject(hMutex, INFINITE);
		// 每来一个连接,就会起一个线程去维护
		clnSocks[clntCnt++] = sockConn;
		ReleaseMutex(hMutex);
		hThread = (HANDLE)_beginthreadex(NULL, 0, HandleCln, (void*)&sockConn, 0, NULL);
		cout << "Client IP = "<< inet_ntoa(addrCli.sin_addr)<< " Client Num = " << clntCnt << endl;
	}

	closesocket(sockSrv);
	WSACleanup();
	return 0;
}

 

客户端实现代码:

// 客户端做的事情
// 1 请求上线
// 2 发消息 等待用户输入消息 发给服务端
// 3 客户端等待服务端的消息 收完数据后 输出到控制台
// 4 等待用户自己的关闭

#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define NAME_SIZE 256
#define MAX_BUF_SIZE 1024

using namespace std;

char szName[NAME_SIZE] = "[DEFAULT]";
char szMsg[MAX_BUF_SIZE];

unsigned WINAPI SendMsg(void *arg) {
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE]; // 又要有昵称 又要有消息
	while (1) {
		// 阻塞在这一句 等待用户输入
		fgets(szMsg, MAX_BUF_SIZE, stdin);
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n")) {
			// 处理下线的问题
			closesocket(hClntSock);
			exit(0);
		}
		// 拿到消息后拼包发送给服务器
		sprintf(szNameMsg, "%s %s", szName, szMsg);
		send(hClntSock, szNameMsg, strlen(szNameMsg), 0);
	}
	return 0;
}

// 接收服务端的消息
unsigned WINAPI RecvMsg(void* arg) {
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE]; // 又要有昵称 又要有消息
	int iLen = 0;

	while (1) {
		// 阻塞 等待来自服务器的消息
		iLen = recv(hClntSock, szNameMsg, NAME_SIZE + MAX_BUF_SIZE - 1, 0);
		// 服务器断开
		if (iLen == -1) {
			return -1;
		}
		// szNameMsg的0到iLen -1 都是收到的数据 iLen个
		szNameMsg[iLen] = 0;
		fputs(szNameMsg, stdout);
	}
}

int main(int argc, char *argv[]) {
	cout << "This is Client!" << endl;

	if (argc != 2) {
		cout << "必须输入两个参数" << endl;
		cout << "例如:MyThreadClient.exe WXS" << endl;
		system("pause");
		return -1;
	}

	// 赋值昵称
	sprintf(szName, "[%s]", argv[1]);

	// 加载套节字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	SOCKET hSock;
	SOCKADDR_IN servAdr;
	HANDLE hSendThread, hRecvThread;
	wVersionRequested = MAKEWORD(1, 1);

	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();
		return -1;
	}
	// 1 建立socket
	hSock = socket(PF_INET, SOCK_STREAM, 0);

	// 2 配置端口和地址
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	servAdr.sin_family = AF_INET;
	servAdr.sin_port = htons(6000);
	
	// 3 连接服务器
	if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) {
		cout << "connect error code = " << GetLastError() << endl;
		return -1;
	}

	// 4 发送给服务端消息的线程
	hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL);

	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL);

	// 等待 hSendThread hRecvThread 执行完毕
	WaitForSingleObject(hSendThread, INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	// 关闭套节字
	closesocket(hSock);
	WSACleanup();
	return 0;
}