c++编写聊天室(客户端)Windows

 项目涉及Windows网络编程开发

 服务器使用Linux系统

 互斥锁的使用

 多线程开发

 windows 控制台的设置开发

        

项目效果预览

c++聊天室


编写思路

  •  导入所需的头文件:
  • #include <iostream>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    
  • 创建套接字:

  • int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        std::cout << "Failed to create socket" << std::endl;
        return -1;
    }
    
  • 设置服务器地址和端口

  • sockaddr_in serverAddress{};
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(port); // 设置端口号 用htons转为网络字节序
    if (inet_pton(AF_INET, ipAddress, &serverAddress.sin_addr) <= 0) {
        std::cout << "Invalid address/ Address not supported" << std::endl;
        return -1;
    }
    
  • 连接到服务器:

  • if (connect(sockfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
        std::cout << "Connection Failed" << std::endl;
        return -1;
    }
    
  • 发送数据到服务器:

     

  • char message[] = "Hello Server!";
    if (send(sockfd, message, strlen(message), 0) < 0) {
        std::cout << "Send failed" << std::endl;
        return -1;
    }
    
  • 接收服务器返回的数据:

  • char buffer[1024] {0};
    if (recv(sockfd, buffer, sizeof(buffer), 0) < 0) {
        std::cout << "Receive failed" << std::endl;
        return -1;
    }
    std::cout << "Server response: " << buffer << std::endl;
    
  • 关闭套接字:

    close(sockfd);
    
     接下来就是再此基础上优化(由于客户端和服务端的编码格式不同,所以相互之间要进行转码)

    头文件

 
#include <stdio.h>
#include <string>
 
#include <conio.h>
#include <WinSock2.h>//网络相关服务头文件
#pragma comment (lib,"WS2_32.lib")//网络相关库文件
 
using namespace std;
HANDLE hMutex;//互斥锁(用于线程之间的互斥)
#define SERVER_IP		"118.126.117.125"//服务器IP 字符串
#define QUN_LIAO_PROT  2022
char line1[111];//up
char line2[111];//空白字符分割线
char nickName[32];//昵称
SOCKET serverSocket;//网络套接字
sockaddr_in sockAddr;//网络地址
void GBKToUTF8(string& strGBK);
void gotoxy(int x, int y);
string UTF8ToGBK(const char* strUTF8);

main.cpp

#include "main.h"


//转码
void GBKToUTF8(string& strGBK)
{
	int len = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
	wchar_t* wszUtf8 = new wchar_t[len];
	memset(wszUtf8, 0, len);
	MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, wszUtf8, len);
	len = WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, NULL, 0, NULL, NULL);
	char* szUtf8 = new char[len + 1];
	memset(szUtf8, 0, len + 1);
	WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, szUtf8, len, NULL, NULL);
	strGBK = szUtf8; 
	delete[] szUtf8;
	delete[] wszUtf8;
}

void gotoxy(int x,int y)
{
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//获取当前窗口句柄
	COORD pos = { x,y };
	SetConsoleCursorPosition(hOut,pos);

}

void uiInit() 
{
	system("mode con lines=36 cols=110");
	system("cls");
	gotoxy(0,33);

	for (int i = 0; i < 110; i++)line1[i] = '-';
	line1[110] = 0;
	for (int i = 0; i < 110; i++)line2[i] = ' ';
	line2[110] = 0;
	printf("%s\n\n", line1);
	 

}
/*
* 
*   mciSendString("open ./images/小情歌.mp3 alias bgm ", NULL, 0, NULL);
	mciSendString("play bgm", NULL, 0, NULL);
*/

void login() 
{
	system("mode con lines=5 cols=30 \n ");
	printf("   欢迎进入内部聊天室 \n \n");
	printf("       昵称:");
	scanf_s("%s", nickName, sizeof(nickName));

	while (getchar() != '\n');//清空输入缓冲区
	string name = nickName;
	GBKToUTF8(name );
	send(serverSocket,name.c_str()/*c++字符串 转成c语言*/, strlen(name.c_str()) + 1, 0);//send niskname to server 如果中文,可能回出问题
	//做send检查
}

string UTF8ToGBK(const char* strUTF8)
{ 
	int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
	wchar_t* wszGBK = new wchar_t[len + 1];
	memset(wszGBK, 0, len * 2 + 2); 
	MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
	len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
	char* szGBK = new char[len + 1]; 
	memset(szGBK, 0, len + 1);
	WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
	std::string strTemp(szGBK); 
	if (wszGBK) delete[] wszGBK;
	if (szGBK) delete[] szGBK; 
	return strTemp; 
}

void  printMsg(const char* msg)
{
	//上锁(申请互斥锁)
	WaitForSingleObject(hMutex, INFINITE/*等待时间 永远等*/);
	//没有申请到 就一直等待 直到等到为止
	 static POINT pos = { 0,0 };
	 gotoxy(pos.x, pos.y);
	 //printf("%s \n", msg);
	  static int color = 31;

	 printf("\033[0;%d;40m%s\033[0m\n",color++,msg);
	 if (color > 36)
	 {
		 color = 31;
	 }
	
	 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	 CONSOLE_SCREEN_BUFFER_INFO info;
	 GetConsoleScreenBufferInfo(hOut,&info);
	 pos.x = info.dwCursorPosition.X;
	 pos.y = info.dwCursorPosition.Y;
	 if (pos.y >= 33)
	 {
		 printf("%s\n", line2);
		 printf("\n\n");
		 gotoxy(0, 33);
		 printf("%s\n ",line1);
		 pos.y -= 1;
	 }
	 gotoxy(1,34);
	//释放锁
	ReleaseMutex(hMutex);
}

void editPrint(int col,int ch)
{
	WaitForSingleObject(hMutex, INFINITE);

	gotoxy(col, 34);
	printf("%c",ch);
	ReleaseMutex(hMutex);
 }

void editPrint(int col,const char * str)
{
	WaitForSingleObject(hMutex, INFINITE);

	gotoxy(col, 34);
	printf("%s", str);
	ReleaseMutex(hMutex);
}

bool init()
{
	WSADATA data;//初始化结果
	//1.网络服务初始化
	int ret = WSAStartup(MAKEWORD(1, 1), &data);
	if (ret != 0)
	{
		return false;
	}
	//2.网络套接字socket
			
	serverSocket = socket(PF_INET/*网络 数据流 数据报 类型*/, SOCK_STREAM/*数据流*/,IPPROTO_TCP/*TCP协议*/);

	//3.配置物理地址
	sockAddr.sin_family = PF_INET;//网络地址
	sockAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);//IP地址
	//端口号 最大为65535  转字节序
	sockAddr.sin_port = htons(QUN_LIAO_PROT);

	CreateMutex(0, 0, L"console");

	return true;
}

//创建线程
DWORD WINAPI threadFuncRecy(LPVOID pram) {

	char buff[4096];//接收信息
	while (1) {
		int ret = recv(serverSocket, buff, sizeof(buff),0);
		if (ret <= 0) {
			printf("服务器关闭或故障\n");
			break;
		}
		
		//打印接收到的信息
		
		printMsg(UTF8ToGBK(buff).c_str());

	}
	return NULL;
}

bool isHZ(char str[], int index) {
	//一个汉字占两个字节 第一个字节< 0; 第二个字节 有可能小于零 也可能大于0
	//一个英文字符,只有一个字节, 》0 

	int i = 0;
	while (i < index) {
		if (str[i] > 0) {
			i++;
		}
		else {
			i += 2;
		}

	}
	if (i == index)
	{
		return false;
	}
	else {
		return true;
	}
}

int main(void)
{
	 
	if (init() == false) {
		printf("Initialization failed\n");
		getchar();
		return -1;
	}
	else {
		printf("Initialization succeeded \n");
		 
	}

	 
	// 连接服务器(发起网络连接)
	int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
	if (ret != 0) {
		printf("Initialization failed Failed to connect to the server. Please check the network \n");
		 
		return 1;
	}
	else 
	{
		printf("connect succeeded \n");
	 
	}
 
	//登录聊天室
	login();
	
	uiInit();//初始化界面

	//创建线程回返回一个线程句柄
	HANDLE	hThread =  CreateThread(0, 0, threadFuncRecy,0,0,0);
	CloseHandle(hThread);//关闭句柄 没关闭线程
	char buff[1024];//保存用户输入的字符串
	//内容设置为零
	memset(buff, 0,sizeof(buff));
	//编辑信息
	while (1)
	{
		editPrint(0,'>');
		int len = 0;
		while (1){
			if (_kbhit())
			{
				char c = getch();	
				if (c == '\r')
				{
					//按下回车
					break;
				}
				else if(c == 8)//退格键
				{
					if (len == 0)
					{
						continue;
					}
					//删除  
					if (isHZ(buff, len - 1)  ) {
						//printf("\b\b \b\b");
						editPrint(len + 1, "\b\b  \b\b");
						buff[len - 1] = 0;
						buff[len - 2] = 0;
						len -= 2;

					}
					else {
						editPrint(len + 1, "\b \b");
						buff[len - 1] = 0;
						len -= 1;
					}

					continue;
				}

				WaitForSingleObject(hMutex, INFINITE);
				do {
					printf("%c",c);
					buff[len++] = c;
				} while (_kbhit()&& (c=getch()));

				ReleaseMutex(hMutex);

				//editPrint(len + 1,c);
				//buff[len++] = c ;
			}
		}
		if (len == 0)
		{
			continue;
		}

		//清楚编辑区的信息
		char buff2[1024];
		sprintf_s(buff2,sizeof(buff2),"%s\n",line2);
		editPrint(0, buff2);

		//把用户自己说的话,输出到聊天室
		sprintf_s(buff2, sizeof(buff2), "【localHost@%s】%s", nickName, buff);
		
		printMsg(buff2);
		//发送编辑好的字符串

		send(serverSocket,buff, strlen(buff)+1,0);
	}

	getchar();

	return 0;
}

 

    GBKToUTF8:将GBK编码的字符串转换为UTF-8编码的字符串。
    gotoxy:在控制台上设置光标的位置。
    UTF8ToGBK:将UTF-8编码的字符串转换为GBK编码的字符串。
    uiInit:初始化控制台界面,设置控制台的大小和清空屏幕。
    login:用户登录函数,用户输入昵称并发送到服务器。
    printMsg:打印接收到的消息到控制台界面。
    editPrint:在控制台界面上打印编辑区的信息。
    init:初始化网络服务,包括启动WinSock和创建网络套接字。
    threadFuncRecy:接收消息的线程函数,负责接收服务器发送的消息并打印到控制台界面。
    isHZ:判断一个字符是否是汉字。
    main:程序的入口函数,包括初始化网络连接、登录、界面初始化、创建接收线程和处理用户输入等功能。

下面是一些优化建议:

  使用更现代化的网络库:WinSock2是一个较旧的网络库,可以考虑使用更现代化的网络库,如Boost.Asio或C++11/14中引入的网络功能,以提供更简洁和高效的网络通信代码。

    错误处理和异常处理:代码中需要更好的错误处理和异常处理机制。例如,在连接服务器、发送和接收数据时,应检查返回值并处理可能出现的错误情况,而不仅仅是简单地打印错误消息。可以使用异常处理来更好地处理错误和异常情况。

    优化界面显示:目前的界面显示使用了控制台打印,可以考虑使用图形界面或更高级的用户界面库来提升用户体验。

    代码结构和模块化:代码可以更好地组织和模块化,将不同的功能模块分离开来,以提高代码的可读性和维护性。可以将网络通信部分、界面显示部分和其他功能部分分别封装成不同的函数或类。

    代码注释和文档:为了方便他人阅读和理解代码,可以添加更多的注释来解释代码的逻辑和功能。此外,编写适当的文档和说明文件,以便其他开发人员理解和使用这段代码。

    使用现代C++特性:代码中使用了一些较旧的C++特性,可以考虑使用现代C++特性来简化代码并提高性能。例如,可以使用智能指针、lambda表达式和更好的字符串处理函数来改进代码。

错误异常处理

#include <stdexcept> // 引入标准异常类头文件

// ...

bool init()
{
    WSADATA data;
    int ret = WSAStartup(MAKEWORD(2, 2), &data);
    if (ret != 0)
    {
        throw std::runtime_error("Failed to initialize WinSock");
    }

    serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        throw std::runtime_error("Failed to create socket");
    }

    // ...

    return true;
}

void login()
{
    system("mode con lines=5 cols=30 \n ");
    printf("   欢迎进入内部聊天室 \n \n");
    printf("       昵称:");
    scanf_s("%s", nickName, sizeof(nickName));

    while (getchar() != '\n');

    string name = nickName;
    GBKToUTF8(name);
    int ret = send(serverSocket, name.c_str(), strlen(name.c_str()) + 1, 0);
    if (ret == SOCKET_ERROR)
    {
        closesocket(serverSocket);
        WSACleanup();
        throw std::runtime_error("Failed to send nickname to server");
    }
}

// ...

DWORD WINAPI threadFuncRecy(LPVOID pram)
{
    char buff[4096];
    while (1)
    {
        int ret = recv(serverSocket, buff, sizeof(buff), 0);
        if (ret <= 0)
        {
            closesocket(serverSocket);
            WSACleanup();
            throw std::runtime_error("Server closed or encountered an error");
        }

        printMsg(UTF8ToGBK(buff).c_str());
    }
    return NULL;
}

// ...

int main(void)
{
    try
    {
        if (init() == false)
        {
            throw std::runtime_error("Initialization failed");
        }

        int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
        if (ret != 0)
        {
            closesocket(serverSocket);
            WSACleanup();
            throw std::runtime_error("Failed to connect to the server");
        }

        // ...
    }
    catch (const std::exception& ex)
    {
        printf("An error occurred: %s\n", ex.what());
        return 1;
    }

    return 0;
}

 代码结构模块化

#include <stdio.h>
#include <string>
#include <conio.h>
#include <WinSock2.h>
#include <stdexcept>

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

using namespace std;

// 定义常量
#define SERVER_IP       "118.126.117.125"
#define QUN_LIAO_PROT   2022

// 函数声明
void GBKToUTF8(string& strGBK);
void gotoxy(int x, int y);
string UTF8ToGBK(const char* strUTF8);
void uiInit();
void login();
void printMsg(const char* msg);
void editPrint(int col, int ch);
void editPrint(int col, const char* str);
bool init();
DWORD WINAPI threadFuncRecy(LPVOID pram);
bool isHZ(char str[], int index);

// 全局变量
HANDLE hMutex; // 互斥锁
char line1[111]; // 分隔线
char line2[111]; // 空白字符分割线
char nickName[32]; // 昵称
SOCKET serverSocket; // 网络套接字
sockaddr_in sockAddr; // 网络地址

void GBKToUTF8(string& strGBK)
{
    // 转码实现
}

void gotoxy(int x, int y)
{
    // 设置光标位置实现
}

string UTF8ToGBK(const char* strUTF8)
{
    // 转码实现
}

void uiInit()
{
    // 界面初始化实现
}

void login()
{
    // 用户登录实现
}

void printMsg(const char* msg)
{
    // 打印消息实现
}

void editPrint(int col, int ch)
{
    // 编辑区打印字符实现
}

void editPrint(int col, const char* str)
{
    // 编辑区打印字符串实现
}

bool init()
{
    // 初始化网络服务实现
}

DWORD WINAPI threadFuncRecy(LPVOID pram)
{
    // 接收消息的线程函数实现
}

bool isHZ(char str[], int index)
{
    // 判断字符是否是汉字实现
}

int main(void)
{
    try
    {
        if (init() == false)
        {
            throw std::runtime_error("Initialization failed");
        }

        int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
        if (ret != 0)
        {
            throw std::runtime_error("Failed to connect to the server");
        }

        login();

        uiInit();

        HANDLE hThread = CreateThread(0, 0, threadFuncRecy, 0, 0, 0);
        CloseHandle(hThread);

        char buff[1024];
        memset(buff, 0, sizeof(buff));

        while (1)
        {
            // 编辑信息实现
        }
    }
    catch (const std::exception& ex)
    {
        printf("An error occurred: %s\n", ex.what());
        return 1;
    }

    return 0;
}

代码 注释和文档

#include <stdio.h>
#include <string>
#include <conio.h>
#include <WinSock2.h>
#include <stdexcept>

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

using namespace std;

// 定义常量
#define SERVER_IP       "118.126.117.125" // 服务器IP
#define QUN_LIAO_PROT   2022 // 聊天室端口号

// 函数声明

// 将GBK编码的字符串转换为UTF-8编码的字符串
void GBKToUTF8(string& strGBK);

// 在控制台上设置光标的位置
void gotoxy(int x, int y);

// 将UTF-8编码的字符串转换为GBK编码的字符串
string UTF8ToGBK(const char* strUTF8);

// 初始化控制台界面
void uiInit();

// 用户登录
void login();

// 打印接收到的消息到控制台界面
void printMsg(const char* msg);

// 在控制台界面上打印编辑区的字符
void editPrint(int col, int ch);

// 在控制台界面上打印编辑区的字符串
void editPrint(int col, const char* str);

// 初始化网络服务
bool init();

// 接收消息的线程函数
DWORD WINAPI threadFuncRecy(LPVOID pram);

// 判断一个字符是否是汉字
bool isHZ(char str[], int index);

int main(void)
{
    try
    {
        if (init() == false)
        {
            throw std::runtime_error("Initialization failed");
        }

        int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
        if (ret != 0)
        {
            throw std::runtime_error("Failed to connect to the server");
        }

        login();

        uiInit();

        HANDLE hThread = CreateThread(0, 0, threadFuncRecy, 0, 0, 0);
        CloseHandle(hThread);

        char buff[1024];
        memset(buff, 0, sizeof(buff));

        while (1)
        {
            // 编辑信息实现
        }
    }
    catch (const std::exception& ex)
    {
        printf("An error occurred: %s\n", ex.what());
        return 1;
    }

    return 0;
}

        

#include <stdio.h>
#include <string>
#include <conio.h>
#include <WinSock2.h>
#include <stdexcept>

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

using namespace std;

// 定义常量
#define SERVER_IP       "118.126.117.125" // 服务器IP
#define QUN_LIAO_PROT   2022 // 聊天室端口号

// 函数声明
void GBKToUTF8(string& strGBK);
void gotoxy(int x, int y);
string UTF8ToGBK(const char* strUTF8);
void uiInit();
void login();
void printMsg(const char* msg);
void editPrint(int col, int ch);
void editPrint(int col, const char* str);
bool init();
DWORD WINAPI threadFuncRecy(LPVOID pram);
bool isHZ(char str[], int index);

// 全局变量
HANDLE hMutex; // 互斥锁
char line1[111]; // 分隔线
char line2[111]; // 空白字符分割线
char nickName[32]; // 昵称
SOCKET serverSocket; // 网络套接字
sockaddr_in sockAddr; // 网络地址

void GBKToUTF8(string& strGBK)
{
    int len = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
    wchar_t* wszUtf8 = new wchar_t[len];
    memset(wszUtf8, 0, len);
    MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, wszUtf8, len);
    len = WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, NULL, 0, NULL, NULL);
    char* szUtf8 = new char[len + 1];
    memset(szUtf8, 0, len + 1);
    WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, szUtf8, len, NULL, NULL);
    strGBK = szUtf8;
    delete[] szUtf8;
    delete[] wszUtf8;
}

void gotoxy(int x, int y)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { x, y };
    SetConsoleCursorPosition(hOut, pos);
}

string UTF8ToGBK(const char* strUTF8)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
    wchar_t* wszGBK = new wchar_t[len + 1];
    memset(wszGBK, 0, len * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
    len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
    char* szGBK = new char[len + 1];
    memset(szGBK, 0, len + 1);
    WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
    string strTemp(szGBK);
    if (wszGBK) delete[] wszGBK;
    if (szGBK) delete[] szGBK;
    return strTemp;
}

void uiInit()
{
    system("mode con lines=36 cols=110");
    system("cls");
    gotoxy(0, 33);

    for (int i = 0; i < 110; i++) line1[i] = '-';
    line1[110] = 0;
    for (int i = 0; i < 110; i++) line2[i] = ' ';
    line2[110] = 0;
    printf("%s\n\n", line1);
}

void login()
{
    system("mode con lines=5 cols=30 \n ");
    printf("   欢迎进入内部聊天室 \n \n");
    printf("       昵称:");
    scanf_s("%s", nickName, sizeof(nickName));

    while (getchar() != '\n');

    string name = nickName;
    GBKToUTF8(name);
    send(serverSocket, name.c_str(), strlen(name.c_str()) + 1, 0);
}

void printMsg(const char* msg)
{
    WaitForSingleObject(hMutex, INFINITE);
    static POINT pos = { 0,0 };
    gotoxy(pos.x, pos.y);
    static int color = 31;

    printf("\033[0;%d;40m%s\033[0m\n", color++, msg);
    if (color > 36)
    {
        color = 31;
    }

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hOut, &info);
    pos.x = info.dwCursorPosition.X;
    pos.y = info.dwCursorPosition.Y;
    if (pos.y >= 33)
    {
        printf("%s\n", line2);
        printf("\n\n");
        gotoxy(0, 33);
        printf("%s\n ", line1);
        pos.y -= 1;
    }
    gotoxy(1, 34);
    ReleaseMutex(hMutex);
}

void editPrint(int col, int ch)
{
    WaitForSingleObject(hMutex, INFINITE);
    gotoxy(col, 34);
    printf("%c", ch);
    ReleaseMutex(hMutex);
}

void editPrint(int col, const char* str)
{
    WaitForSingleObject(hMutex, INFINITE);
    gotoxy(col, 34);
    printf("%s", str);
    ReleaseMutex(hMutex);
}

bool init()
{
    WSADATA data;
    int ret = WSAStartup(MAKEWORD(2, 2), &data);
    if (ret != 0)
    {
        return false;
    }

    serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        return false;
    }

    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
    sockAddr.sin_port = htons(QUN_LIAO_PROT);

    CreateMutex(0, 0, L"console");

    return true;
}

DWORD WINAPI threadFuncRecy(LPVOID pram)
{
    char buff[4096];
    while (1)
    {
        int ret = recv(serverSocket, buff, sizeof(buff), 0);
        if (ret <= 0)
        {
            printf("Server closed or encountered an error\n");
            break;
        }

        printMsg(UTF8ToGBK(buff).c_str());
    }
    return NULL;
}

bool isHZ(char str[], int index)
{
    int i = 0;
    while (i < index)
    {
        if (str[i] > 0)
        {
            i++;
        }
        else
        {
            i += 2;
        }
    }
    if (i == index)
    {
        return false;
    }
    else
    {
        return true;
    }
}

int main(void)
{
    try
    {
        if (init() == false)
        {
            throw std::runtime_error("Initialization failed");
        }

        int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
        if (ret != 0)
        {
            throw std::runtime_error("Failed to connect to the server");
        }

        login();

        uiInit();

        HANDLE hThread = CreateThread(0, 0, threadFuncRecy, 0, 0, 0);
        CloseHandle(hThread);

        char buff[1024];
        memset(buff, 0, sizeof(buff));

        while (1)
        {
            editPrint(0, '>');
            int len = 0;
            while (1)
            {
                if (_kbhit())
                {
                    char c = getch();
                    if (c == '\r')
                    {
                        break;
                    }
                    else if (c == 8)
                    {
                        if (len == 0)
                        {
                            continue;
                        }
                        if (isHZ(buff, len - 1))
                        {
                            editPrint(len + 1, "\b\b  \b\b");
                            buff[len - 1] = 0;
                            buff[len - 2] = 0;
                            len -= 2;
                        }
                        else
                        {
                            editPrint(len + 1, "\b \b");
                            buff[len - 1] = 0;
                            len -= 1;
                        }
                        continue;
                    }
                    WaitForSingleObject(hMutex, INFINITE);
                    do
                    {
                        printf("%c", c);
                        buff[len++] = c;
                    } while (_kbhit() && (c = getch()));
                    ReleaseMutex(hMutex);
                }
            }
            if (len == 0)
            {
                continue;
            }
            char buff2[1024];
            sprintf_s(buff2, sizeof(buff2), "%s\n", line2);
            editPrint(0, buff2);
            sprintf_s(buff2, sizeof(buff2), "【localHost@%s】%s", nickName, buff);
            printMsg(buff2);
            send(serverSocket, buff, strlen(buff) + 1, 0);
        }
    }
    catch (const std::exception& ex)
    {
        printf("An error occurred: %s\n", ex.what());
        return 1;
    }

    return 0;
}
#include <stdio.h>
#include <string>
#include <conio.h>
#include <WinSock2.h>
#include <stdexcept>

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

using namespace std;

// 定义常量
#define SERVER_IP       "118.126.117.125" // 服务器IP
#define QUN_LIAO_PROT   2022 // 聊天室端口号

// 函数声明
void GBKToUTF8(string& strGBK);
void gotoxy(int x, int y);
string UTF8ToGBK(const char* strUTF8);
void uiInit();
void login();
void printMsg(const char* msg);
void editPrint(int col, int ch);
void editPrint(int col, const char* str);
bool init();
DWORD WINAPI threadFuncRecy(LPVOID pram);
bool isHZ(char str[], int index);

// 全局变量
HANDLE hMutex; // 互斥锁
char line1[111]; // 分隔线
char line2[111]; // 空白字符分割线
char nickName[32]; // 昵称
SOCKET serverSocket; // 网络套接字
sockaddr_in sockAddr; // 网络地址

void GBKToUTF8(string& strGBK)
{
    int len = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
    wchar_t* wszUtf8 = new wchar_t[len];
    memset(wszUtf8, 0, len);
    MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, wszUtf8, len);
    len = WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, NULL, 0, NULL, NULL);
    char* szUtf8 = new char[len + 1];
    memset(szUtf8, 0, len + 1);
    WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, szUtf8, len, NULL, NULL);
    strGBK = szUtf8;
    delete[] szUtf8;
    delete[] wszUtf8;
}

void gotoxy(int x, int y)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { x, y };
    SetConsoleCursorPosition(hOut, pos);
}

string UTF8ToGBK(const char* strUTF8)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
    wchar_t* wszGBK = new wchar_t[len + 1];
    memset(wszGBK, 0, len * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
    len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
    char* szGBK = new char[len + 1];
    memset(szGBK, 0, len + 1);
    WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
    string strTemp(szGBK);
    if (wszGBK) delete[] wszGBK;
    if (szGBK) delete[] szGBK;
    return strTemp;
}

void uiInit()
{
    system("mode con lines=36 cols=110");
    system("cls");
    gotoxy(0, 33);

    for (int i = 0; i < 110; i++) line1[i] = '-';
    line1[110] = 0;
    for (int i = 0; i < 110; i++) line2[i] = ' ';
    line2[110] = 0;
    printf("%s\n\n", line1);
}

void login()
{
    system("mode con lines=5 cols=30 \n ");
    printf("   欢迎进入内部聊天室 \n \n");
    printf("       昵称:");
    scanf_s("%s", nickName, sizeof(nickName));

    while (getchar() != '\n');

    string name = nickName;
    GBKToUTF8(name);
    send(serverSocket, name.c_str(), strlen(name.c_str()) + 1, 0);
}

void printMsg(const char* msg)
{
    WaitForSingleObject(hMutex, INFINITE);
    static POINT pos = { 0,0 };
    gotoxy(pos.x, pos.y);
    static int color = 31;

    printf("\033[0;%d;40m%s\033[0m\n", color++, msg);
    if (color > 36)
    {
        color = 31;
    }

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hOut, &info);
    pos.x = info.dwCursorPosition.X;
    pos.y = info.dwCursorPosition.Y;
    if (pos.y >= 33)
    {
        printf("%s\n", line2);
        printf("\n\n");
        gotoxy(0, 33);
        printf("%s\n ", line1);
        pos.y -= 1;
    }
    gotoxy(1, 34);
    ReleaseMutex(hMutex);
}

void editPrint(int col, int ch)
{
    WaitForSingleObject(hMutex, INFINITE);
    gotoxy(col, 34);
    printf("%c", ch);
    ReleaseMutex(hMutex);
}

void editPrint(int col, const char* str)
{
    WaitForSingleObject(hMutex, INFINITE);
    gotoxy(col, 34);
    printf("%s", str);
    ReleaseMutex(hMutex);
}

bool init()
{
    WSADATA data;
    int ret = WSAStartup(MAKEWORD(2, 2), &data);
    if (ret != 0)
    {
        return false;
    }

    serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        return false;
    }

    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
    sockAddr.sin_port = htons(QUN_LIAO_PROT);

    CreateMutex(0, 0, L"console");

    return true;
}

DWORD WINAPI threadFuncRecy(LPVOID pram)
{
    char buff[4096];
    while (1)
    {
        int ret = recv(serverSocket, buff, sizeof(buff), 0);
        if (ret <= 0)
        {
            printf("Server closed or encountered an error\n");
            break;
        }

        printMsg(UTF8ToGBK(buff).c_str());
    }
    return NULL;
}

bool isHZ(char str[], int index)
{
    int i = 0;
    while (i < index)
    {
        if (str[i] > 0)
        {
            i++;
        }
        else
        {
            i += 2;
        }
    }
    if (i == index)
    {
        return false;
    }
    else
    {
        return true;
    }
}

int main(void)
{
    try
    {
        if (init() == false)
        {
            throw std::runtime_error("Initialization failed");
        }

        int ret = connect(serverSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
        if (ret != 0)
        {
            throw std::runtime_error("Failed to connect to the server");
        }

        login();

        uiInit();

        HANDLE hThread = CreateThread(0, 0, threadFuncRecy, 0, 0, 0);
        CloseHandle(hThread);

        char buff[1024];
        memset(buff, 0, sizeof(buff));

        while (1)
        {
            editPrint(0, '>');
            int len = 0;
            while (1)
            {
                if (_kbhit())
                {
                    char c = getch();
                    if (c == '\r')
                    {
                        break;
                    }
                    else if (c == 8)
                    {
                        if (len == 0)
                        {
                            continue;
                        }
                        if (isHZ(buff, len - 1))
                        {
                            editPrint(len + 1, "\b\b  \b\b");
                            buff[len - 1] = 0;
                            buff[len - 2] = 0;
                            len -= 2;
                        }
                        else
                        {
                            editPrint(len + 1, "\b \b");
                            buff[len - 1] = 0;
                            len -= 1;
                        }
                        continue;
                    }
                    WaitForSingleObject(hMutex, INFINITE);
                    do
                    {
                        printf("%c", c);
                        buff[len++] = c;
                    } while (_kbhit() && (c = getch()));
                    ReleaseMutex(hMutex);
                }
            }
            if (len == 0)
            {
                continue;
            }
            char buff2[1024];
            sprintf_s(buff2, sizeof(buff2), "%s\n", line2);
            editPrint(0, buff2);
            sprintf_s(buff2, sizeof(buff2), "【localHost@%s】%s", nickName, buff);
            printMsg(buff2);
            send(serverSocket, buff, strlen(buff) + 1, 0);
        }
    }
    catch (const std::exception& ex)
    {
        printf("An error occurred: %s\n", ex.what());
        return 1;
    }

    return 0;
}

程序的主要功能包括:

  1. 初始化:使用WSAStartup函数初始化WinSock库,并创建客户端的套接字。

  2. 连接服务器:使用connect函数与指定的服务器建立连接。

  3. 登录:通过输入昵称进行登录。

  4. 界面初始化:设置控制台的窗口大小和清屏操作。

  5. 消息打印:使用printMsg函数打印接收到的消息。

  6. 编辑区域打印:使用editPrint函数在控制台指定位置打印输入的消息。

  7. 接收线程:创建一个线程用于接收服务器发送的消息,并在接收到消息时调用printMsg函数打印消息。

  8. 消息发送:在主循环中,监听键盘输入,并将输入的消息发送给服务器。

 

猜你喜欢

转载自blog.csdn.net/m0_72703340/article/details/131606435