c++——聊天室程序(涉及多线程、互斥量mutex、TCP连接)

一、服务端

#include <stdio.h>
#include <WinSock2.h>
#include "gyklib.h"
#include <Ws2tcpip.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "gyklib.lib")

// 定义聊天室最大人数
#define MAX_CLI_NUM 256
// 定义消息最大值为1KB
#define MAX_MSG_SIZE 1024
// 当前聊天室人数
int current_cli_num = 0;
// 所有连接的数组
SOCKET* cli_socket_arr = new SOCKET[MAX_CLI_NUM]{ 0 };
// 创建一个互斥体
HANDLE hmutex = CreateMutex(NULL, FALSE, NULL);
// 发送消息的方法
void SendMsgToOther(SOCKET* socket, char* msg);
// 处理客户端连接的函数,多线程中使用
unsigned WINAPI HandleClient(void* arg);

int main()
{
	printf("ChatRoom Server开启\n");
	// 线程句柄
	HANDLE hThread;
	// 初始化socker网络库
	InitSocketNet();
	// 3、建立socket连接,AF_INET:表示ipv4协议
	// SOCK_STREAM:表示TCP连接
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockSrv)
	{
		printf("socket errorno = %d\n", GetLastError());
		return -1;
	}
	// 配置连接参数
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 绑定socket
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		printf("bind errorno = %d\n", GetLastError());
		return -1;
	}
	// 4、监听 listen
	if (SOCKET_ERROR == listen(sockSrv, 100))
	{
		printf("listen errorno = %d\n", GetLastError());
		return -1;
	}
	// 客户端地址
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	while (TRUE)
	{
		// 5、分配一台分机去处理客户端的连接
		printf("等待客户端连接中...\n");
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		// 等待互斥体变为有信号状态
		WaitForSingleObject(hmutex, INFINITE);
		cli_socket_arr[current_cli_num] = sockConn;
		current_cli_num++;
		char ip_str[INET6_ADDRSTRLEN];
		const char* ip = InetNtop(AF_INET, &(addrCli.sin_addr), ip_str, INET_ADDRSTRLEN);
		if (ip != NULL) {
			printf("客户端:{%s}连接成功!\n", ip);
		}
		// 释放互斥体
		ReleaseMutex(hmutex);
		// 开启一个线程去处理这个连接的消息
		hThread = (HANDLE)_beginthreadex(NULL, 0, &HandleClient, &sockConn, 0, NULL);
	}

	// 8、关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

unsigned WINAPI HandleClient(void* arg)
{
	// 获取连接
	SOCKET socket = *(SOCKET*)arg;
	// 获取数据
	char* clientMsg = new char[MAX_MSG_SIZE];
	while (TRUE)
	{
		// 将消息置为0
		memset(clientMsg, 0, MAX_MSG_SIZE);
		// 获取消息
		if (recv(socket, clientMsg, MAX_MSG_SIZE, 0) > 0)
		{
			printf("收到客户端消息:%s\n", clientMsg);
			// 消息获取之后,需要发送给其他的人
			SendMsgToOther(&socket, clientMsg);
		}
		else // 断开连接,跳出while循环
		{
			break;
		}

	}

	// 等待互斥体变为有信号状态
	WaitForSingleObject(hmutex, INFINITE);
	// 移除数组中失效的连接
	int currentIndex = current_cli_num;
	for (int i = 0; i < current_cli_num; i++)
	{
		if (socket == cli_socket_arr[i])
		{
			currentIndex = i;
		}
		if (i == (current_cli_num - 1))
		{
			cli_socket_arr[i] = 0;
		}
		else if (i >= currentIndex)
		{
			cli_socket_arr[i] = cli_socket_arr[i + 1];
		}
	}
	current_cli_num--;
	// 释放互斥体
	ReleaseMutex(hmutex);
	// 销毁堆数据
	delete[] clientMsg;
	return 0;
}

void SendMsgToOther(SOCKET* socket, char* msg)
{
	for (int i = 0; i < current_cli_num; i++)
	{
		SOCKET s = cli_socket_arr[i];
		printf("s:%x socket:%x\n", s, *socket);
		if (s != *socket)
		{
			SocketSend(s, msg, strlen(msg));
		}
	}
}

二、客户端

#include <string> // 包含strcpy_s函数的头文件
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#include "gyklib.h"
#include <process.h>
#include <ctype.h>

// 使用 #pragma comment(lib, "ws2_32.lib") 可以使你的代码更加自包含,因为你不需要依赖外部的项目设置。
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "gyklib.lib")

#define MAX_NAME_SIZE 255
#define MAX_MSG_SIZE 1024
// 用户的名称
char* name = new char[255] {"DEFAULT"};
// 用户输入的消息
char* msg = new char[MAX_MSG_SIZE];
// 要发送的数据
char* newMsg = new char[MAX_NAME_SIZE + MAX_MSG_SIZE];

// 处理收到的消息
unsigned HandleRecv(void* arg);

// 处理发送的消息
unsigned HandleSend(void* arg);

int main(int argc, char* argv[])
{

	HANDLE hSendThread, hRecvThread;
	if (argc != 2) {
		MessageBox(0, "命令行启动,请添加启动参数!\n例如:./ChatRoomClient.exe [name]\n其中name表示你的名称\n", "警告", MB_ICONWARNING);
		return 1; // 退出并返回错误码
	}

	// 获取聊天室昵称
	errno_t err = strcpy_s(name, MAX_NAME_SIZE, argv[1]);
	if (err != 0)
	{
		fputs("strcpy_s error\n", stdout);
	}
	// 初始化socker网络库
	InitSocketNet();
	// 3、建立socket连接,AF_INET:表示ipv4协议
	// SOCK_STREAM:表示TCP连接
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
		printf("socket errorno = %d\n", GetLastError());
		return -1;
	}
	// 4、配置要连接的服务器
	SOCKADDR_IN addrSrv;
	memset(&addrSrv, 0, sizeof(addrSrv));
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 假设我们有一个 IP 地址的字符串表示
	const char* ip_str = "192.168.1.9";
	if (inet_pton(AF_INET, ip_str, &(addrSrv.sin_addr.s_addr)) <= 0) {
		// 错误处理
		printf("inet_pton errorno = %d\n", GetLastError());
		return -1;
	}

	// 5、连接服务器
	if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		printf("connect errorno = %d\n", GetLastError());
		return -1;
	}
	// 创建线程监听服务器消息
	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &HandleRecv, &sockCli, 0, NULL);
	// 创建线程监听用户输入信息,并发送给服务器
	hSendThread = (HANDLE)_beginthreadex(NULL, 0, &HandleSend, &sockCli, 0, NULL);

	// 等待线程结束后,才结束主线程
	WaitForSingleObject(hRecvThread, INFINITE);
	WaitForSingleObject(hSendThread, INFINITE);

	// 7、关闭套接字
	closesocket(sockCli);
	WSACleanup();
	system("pause");
	return 0;
}

unsigned HandleRecv(void* arg)
{
	// 获取连接
	SOCKET socket = *(SOCKET*)arg;
	// 获取数据
	char* msg = new char[MAX_NAME_SIZE + MAX_MSG_SIZE];
	while (TRUE)
	{
		// 将消息置为0
		memset(msg, 0, MAX_NAME_SIZE + MAX_MSG_SIZE);
		// 获取消息
		if (recv(socket, msg, MAX_NAME_SIZE + MAX_MSG_SIZE, 0) > 0)
		{
			// 将消息打印到控制台
			fputs(msg, stdout);
		}
		else // 断开连接,跳出while循环
		{
			break;
		}

	}
	return 0;
}

unsigned HandleSend(void* arg)
{
	// 获取连接
	SOCKET socket = *(SOCKET*)arg;
	while (TRUE)
	{
		fgets(msg, MAX_NAME_SIZE + MAX_MSG_SIZE, stdin);
		if (strlen(msg) == 2 && tolower(msg[0]) == 'q') {
			fputs("退出聊天室!\n", stdout);
			exit(0);
		}
		else
		{
			sprintf_s(newMsg, MAX_NAME_SIZE + MAX_MSG_SIZE, "[%s]:%s", name, msg);
			SocketSend(socket, newMsg, strlen(newMsg));
		}
	}
	return 0;
}

三、静态库gyklib

#include <WinSock2.h>
#include <stdio.h>
#include "gyklib.h"
#include <Windows.h>
#include <io.h>

// 确保在包含WinSock2.h之前定义这些宏
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

int SocketRecv(int sock, char* buf, int dataSize)
{
	// 目前收到的数据量
	int numsRecvSoFar = 0;
	// 未收到的数据量
	int numsRemainingToRecv = dataSize;
	printf("enter SocketRecv\n");
	// 循环接收数据
	while (true)
	{
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d, numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);

		if (bytesRead == numsRemainingToRecv)
		{
			return 0;
		}
		else if (bytesRead > 0)
		{
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if (bytesRead < 0 && errno == EAGAIN)
		{
			continue;
		}
		else
		{
			return -1;
		}
	}
}

int SocketSend(int sock, char* buf, int dataSize)
{
	// 目前发送的数据量
	int numsSendSoFar = 0;
	// 未发送的数据量
	int numsRemainingToSend = dataSize;
	printf("enter SocketSend\n");
	// 循环接收数据
	while (true)
	{
		int bytesSend = send(sock, &buf[numsSendSoFar], numsRemainingToSend, 0);
		printf("###bytesSend = %d, numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesSend, numsSendSoFar, numsRemainingToSend);

		if (bytesSend == numsRemainingToSend)
		{
			return 0;
		}
		else if (bytesSend > 0)
		{
			numsSendSoFar += bytesSend;
			numsRemainingToSend -= bytesSend;
			continue;
		}
		else if (bytesSend < 0 && errno == EAGAIN)
		{
			continue;
		}
		else
		{
			return -1;
		}
	}
}

void AddToSystem(const char* appName)
{
	HKEY hKEY;
	char CurrentPath[MAX_PATH];
	char SysPath[MAX_PATH];
	long ret = 0;
	LPSTR FileNewName;
	LPSTR FileCurrentName;
	DWORD type = REG_SZ;
	DWORD size = MAX_PATH;
	// regedit win + R
	LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
	// 获取系统目录
	GetSystemDirectory(SysPath, size);
	// 获取当前模块的完整路径
	GetModuleFileName(NULL, CurrentPath, size);
	// 复制文件
	FileCurrentName = CurrentPath;
	FileNewName = lstrcat(lstrcat(lstrcat(SysPath, "\\"), appName), ".exe");
	struct _finddata_t Steal;
	if (_findfirst(FileNewName, &Steal) != -1)
	{
		MessageBox(0, "该程序已经安装过了!", "消息提示", NULL);
		return;
	}
	int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n继续运行该程序将使这台机器处于被监控的状态!\n如果您不想这样,请按“取消”按钮退出。\n按下“是”按钮该程序将被复制到您的机器上,并随系统启动自动 运行。\n按下“否”按钮,程序只运行一次,不会在您的系统内留下任何东西。", "警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);

	if (ihow == IDCANCEL) exit(0);

	if (ihow == IDNO) return;

	// 复制文件到系统目录,并添加到注册表中
	printf("FileCurrentName: %s\nFileNewName: %s\n", FileCurrentName, FileNewName);
	ret = CopyFile(FileCurrentName, FileNewName, TRUE);
	if (!ret)
	{
		MessageBox(0, "复制程序失败,请重试!", "错误", NULL);
		return;
	}
	// 加入注册表
	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
	if (ret != ERROR_SUCCESS)
	{
		RegCloseKey(hKEY);
		return;
	}
	// 设置Key
	ret = RegSetValueEx(hKEY, appName, NULL, type, (const unsigned char*)FileNewName, size);
	if (ret != ERROR_SUCCESS)
	{
		RegCloseKey(hKEY);
		return;
	}
	RegCloseKey(hKEY);
}

void HideMyself()
{
	HWND hwnd = GetForegroundWindow();
	ShowWindow(hwnd, SW_HIDE);
}

int InitSocketNet() {
	// 1、初始化网络库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	// 2、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_42789698/article/details/140576499