C++ select模型聊天室初版

139 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

C++ select模型聊天室初版

需要了解知识点

memmove与memcpy的区别

  memmove可以防止内存重叠。

  比如:abcdef,目前需要删除ab,然后将cdef往前移动,有些系统的内存移动机制可能存在从后往前移动,比如先把f移动到d所在位置,然后将e移动到c所在位置,但是当我们要把d移动到b所在位置的时候,我们发现d已经是f了,也就变成了把f移动到b,同理本来是把c移动到a所在位置,却变成了把e移动到a所在位置,最终的数据移动操作变成了efef。

  而memmove可以强制要求从前往后移动数据。

C++中的控制字符'\n'和'\r'

  • ASCII码不同,\n为10,而\r是13。
  • 在Windows系统中,\r只回车不换行,\n是换行。在有些系统中,单独的\n是不换行的。

C++11 enum class与enum

参考文章:https://blog.csdn.net/weixin_42817477/article/details/109029172

  枚举类的优势:

  1. 降低命名空间的污染

    以下代码在同一个作用域下

    enum Color{black,white,red};	//black、white、red作用域和color作用域相同
    
    auto white = false;	//错误,white已经被声明过了
    
    enum class Color{black,white,red}; //black、white、red作用域仅在大括号内生效
    
    auto white = false;		//正确,这个white并不是Color中的white
    
    Color c = white;	//错误,在作用域范围内没有white这个枚举量
    
    Color c = Color::white;	//正确
    
    auto c = Color::white;	//正确
    
  2. 避免发生隐式转换

    enum Color{black,white,red};
    std::vector<std::size_t> primeFactors(std::size_t x);	//函数返回x的质因数
    
    Color c = red;
    
    if(c < 14.5)	//将color型别和double型别比较,发生隐式转换
    {
    	auto factors = primeFactors(c);  //计算一个color型别的质因数,发生隐式转换
    }
    
    enum class Color{black,white,red};
    Color c = Color::red;
    
    if(c < 14.5)	//错误,不能将枚举类和double进行比较,需要使用static_cast进行强制转换
    {
    	auto factors = primeFactors(c); //错误,Color不能转化为size_t型别
    }
    
  3. 可以提前声明(强制声明)

    enum Color;			//错误
    enum class Color;	//正确
    

C++ map使用[ ]进行查询操作

参考文章:https://blog.csdn.net/albertsh/article/details/103529462

  map的[ ]操作符会有副作用,当查找的键不存在时,会在对应键位置插入默认值。

效果展示

TIPS:以下代码初步完成,未进行优化,仅供参照。

数据包规范

NetHeader.h

#pragma once


enum class ENetHeader
{
	LOGIN_C2S,
	LOGIN_S2C,
	MSG_C2S,
	MSG_S2C,

};


struct SNetHeader
{
	int len;//发送数据包的大小
	ENetHeader type;//数据包的类型
};


struct SLoginC2S
{
	SNetHeader header{ sizeof(SLoginC2S),ENetHeader::LOGIN_C2S };
	char userName[128];
	char password[128];
};


struct SLoginS2C
{
	SNetHeader header{ sizeof(SLoginS2C),ENetHeader::LOGIN_S2C };
	bool bLogin;
};


struct SMsgC2S
{
	//总计1024字节,与服务端和客户端的缓存容量相同
	SNetHeader header{ sizeof(SMsgC2S),ENetHeader::MSG_C2S };
	char msg[1016];
};


struct SMsgS2C
{
	//总计1024字节,与服务端和客户端的缓存容量相同
	SNetHeader header{ sizeof(SMsgS2C),ENetHeader::MSG_S2C };
	char msg[1016];
};

客户端源码

Client.h

// Client.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。

#pragma once

// TODO: 在此处引用程序需要的其他标头。
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <conio.h>
#include <vector>
#include <string>
#include "NetHeader.h"

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


class CharRoom
{
private:
	COORD _coord;
public:
	void CharRoomDemo(const char* userName, const std::vector<std::string>& msg)
	{
		_coord.X = 1;
		_coord.Y = 1;
		for (auto&& msgItem : msg)
		{
			SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
			std::cout << userName << ":" << msgItem;
			_coord.Y++;
		}
	}

	void ClsMsg()
	{
		_coord.X = 1;
		_coord.Y = 1;
		for (int i = 0; i < 10; i++)
		{
			SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
			std::cout << "                                                              ";
			_coord.Y++;
		}
	}
};


class Register
{
private:
	COORD _coord;
	SLoginC2S _login;

	Register(int x = 15, int y = 5)
	{
		_coord.X = x;
		_coord.Y = y;
	}

	~Register() {}
public:
	static Register* GetInstance()
	{
		static Register reg;
		return &reg;
	}

	void RegisterDemo()
	{
		_coord.X = 15;
		_coord.Y = 5;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "*************登录*************";
		_coord.Y++;
		_coord.X = 18;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "用户名:";
		_coord.Y++;
		_coord.X = 18;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "密码:";
		_coord.Y++;
		_coord.X = 15;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "******************************";
		_coord.Y = 6;
		_coord.X = 26;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
	}

	void PrintCls()
	{
		_coord.X = 15;
		_coord.Y = 5;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "                                                         ";
		_coord.Y++;
		_coord.X = 18;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "                                                         ";
		_coord.Y++;
		_coord.X = 18;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "                                                         ";
		_coord.Y++;
		_coord.X = 15;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "                                                         ";
		_coord.X = 1;
		_coord.Y = 14;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		std::cout << "输入发送信息:";
		_coord.Y++;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
	}

	void UserRegister()
	{
		_coord.Y = 6;
		_coord.X = 26;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
		int inputLen = 0;
		bool left = true;
		while (1)
		{
			if (false == left && inputLen == 0)
			{
				_coord.Y = 7;
				_coord.X = 24;
				SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
			}
			char c = getch();
			std::cout << c;
			if (left == true)
			{
				_login.userName[inputLen++] = c;
				if (c == '\r')
				{
					_login.userName[inputLen - 1] = '\0';
					inputLen = 0;
					left = false;
				}
			}
			else
			{
				_login.password[inputLen++] = c;
				if (c == '\r')
				{
					_login.password[inputLen - 1] = '\0';
					inputLen = 0;
					break;
				}
			}
		}
	}


	SLoginC2S GetUerMsg() { return _login; }
};


class TcpClient :public CharRoom
{
private:
	SOCKET _sock;
	char* _buffer;
	int _bufferLen;
	int _bufferMaxLen;

	std::vector<std::string> msg;
public:
	bool isLoginSuccess;

public:
	TcpClient(int bufMaxLen = 1024)
	{
		_buffer = new char[bufMaxLen];
		_bufferLen = 0;
		_bufferMaxLen = bufMaxLen;
		isLoginSuccess = false;
		_sock = INVALID_SOCKET;
	}

	bool Connect(const char* ip, int port)
	{
		//判断socket是否合法,即socket如果已经创建过一次,那么不能再次被创建
		if (_sock != INVALID_SOCKET)
			return false;
		_sock = socket(AF_INET, SOCK_STREAM, 0);
		if (INVALID_SOCKET == _sock)
		{
			closesocket(_sock);
			_sock = INVALID_SOCKET;
			return false;
		}

		SOCKADDR_IN serverAddr;
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.s_addr = inet_addr(ip);
		serverAddr.sin_port = htons(port);
		if (INVALID_SOCKET == connect(_sock, (sockaddr*)&serverAddr, sizeof(SOCKADDR_IN)))
		{
			closesocket(_sock);
			_sock = INVALID_SOCKET;
			return false;
		}
		//std::cout << "连接服务端成功" << std::endl;
		return true;
	}


	void Update()
	{
		//判断socket是否合法,如果socket没有被创建,那么不能使用update功能
		if (_sock == INVALID_SOCKET)
			return;
		FD_SET reads;
		FD_ZERO(&reads);
		FD_SET(_sock, &reads);
		timeval timer{ 0,1000 };//1000微妙,即1毫秒
		int nRet = select(0, &reads, nullptr, nullptr, &timer);
		if (nRet <= 0)
			return;
		int nRecvLen = recv(_sock, _buffer + _bufferLen, _bufferMaxLen - _bufferLen, 0);
		if (nRecvLen <= 0)
		{
			//断开连接
			Close();
			return;
		}
		_bufferLen += nRecvLen;
		int nRetLen = OnNetMsg(_buffer, _bufferLen);
		if (nRetLen < 0)
		{
			//数据数据包错误,即发送的数据包无法经过检验
			Close();
			return;
		}
		//没有处理数据包
		if (nRetLen == 0)
			return;
		memmove(_buffer, _buffer + nRetLen, _bufferLen - nRetLen);
		_bufferLen -= nRetLen;

	}

	int OnNetMsg(const char* buf, int len)
	{
		if (len < sizeof(struct SNetHeader))
			return 0;
		struct SNetHeader* header = (struct SNetHeader*)buf;

		//解决连包问题
		if (len < header->len)
			return 0;

		switch (header->type)
		{
		case ENetHeader::LOGIN_S2C:
		{
			struct SLoginS2C* login = (struct SLoginS2C*)header;
			//std::cout << login->bLogin << std::endl;
			if (login->bLogin == true)
				isLoginSuccess = true;
			break;
		}
		case ENetHeader::MSG_S2C:
		{
			struct SMsgS2C* serverMsg = (struct SMsgS2C*)header;
			if (msg.size() == 10)
			{
				msg.erase(msg.begin());
				ClsMsg();
			}
			msg.push_back(serverMsg->msg);
			CharRoomDemo(Register::GetInstance()->GetUerMsg().userName, msg);
			break;
		}
		default:
			break;
		}


		return len;
	}

	void Close()
	{
		if (_sock == INVALID_SOCKET)
			return;

		OnNetMsg(nullptr, 0);

		closesocket(_sock);
		_sock = INVALID_SOCKET;
	}

	void Send(const char* buf, int len)
	{
		int nSendLen = send(_sock, buf, len, 0);
	}
};

Client.cpp

// Client.cpp: 定义应用程序的入口点。
//
#include "Client.h"

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);


	TcpClient client;
	client.Connect("127.0.0.1", 7890);

	Register::GetInstance()->RegisterDemo();
	char inputBuf[1024]{ 0 };
	int IBLen = 0;
	bool isInput = false;
	COORD coord;
	while (1)
	{
		if (client.isLoginSuccess)
		{
			if (!isInput)
			{
				isInput = true;
				Register::GetInstance()->PrintCls();
				coord.X = 1;
				coord.Y = 0;
				SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
				coord.X = 1;
				coord.Y = 15;
				std::cout << "接收服务端信息:";
				CONSOLE_CURSOR_INFO cursor;
				cursor.bVisible = FALSE;
				cursor.dwSize = sizeof(cursor);
				SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor);
			}
		}

		if (_kbhit())
		{
			if (!client.isLoginSuccess)
			{
				Register::GetInstance()->UserRegister();
				client.Send((char*)(&Register::GetInstance()->GetUerMsg()), sizeof(SLoginC2S));
			}
			else
			{
				SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
				char c = getch();
				std::cout << c;
				coord.X++;
				inputBuf[IBLen++] = c;
				if (c == '\r')
				{
					coord.Y++;
					coord.X = 1;
					SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
					inputBuf[IBLen - 1] = '\0';
					SMsgC2S clientMsg;
					strcpy(clientMsg.msg, inputBuf);
					client.Send((char*)&clientMsg, sizeof(SMsgC2S));
					IBLen = 0;
				}
			}
		}
		client.Update();
	}

	WSACleanup();
	return 0;
}

服务端源码

ChatRoom.h

// ChatRoom.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。

#pragma once
// TODO: 在此处引用程序需要的其他标头。
#include <iostream>
#include <WinSock2.h>
#include "NetHeader.h"
#include <map>
#include <string>
#include <vector>

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

using namespace std;

class TcpConnecter
{
public:
	char _recvBuf[1024];
	int _recvLen;
	int _recvMaxLen;

public:
	TcpConnecter()
	{
		_recvLen = 0;
		_recvMaxLen = 1024;
	}
};


class TcpServer
{
public:
	TcpServer()
	{
		_map.insert(make_pair("xxx", "123"));
		_map.insert(make_pair("xhh", "123"));
	}

	bool Listen(const char* ip, int port)
	{
		//创建socket
		_serverSock = socket(AF_INET, SOCK_STREAM, 0);
		if (INVALID_SOCKET == _serverSock)
			goto Exit;
		//绑定IP和端口号
		SOCKADDR_IN addr;
		addr.sin_addr.s_addr = inet_addr(ip);
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		if (INVALID_SOCKET == bind(_serverSock, (sockaddr*)&addr, sizeof(SOCKADDR_IN)))
			goto Exit;

		//监听端口
		if (INVALID_SOCKET == listen(_serverSock, 255))
			goto Exit;

		return true;
	Exit:
		closesocket(_serverSock);
		_serverSock = INVALID_SOCKET;
		return false;
	}


	void Update()
	{
		FD_SET reads;
		FD_ZERO(&reads);
		FD_SET(_serverSock, &reads);

		for (auto&& clientSock : _clientSocks)
			FD_SET(clientSock, &reads);

		int nRet = select(0, &reads, nullptr, nullptr, nullptr);
		if (nRet > 0)
		{
			if (FD_ISSET(_serverSock, &reads))
				Accept();
			else
			{
				auto begin = _clientSocks.begin();
				auto end = _clientSocks.end();
				for (; begin != end;)
				{
					if (FD_ISSET(*begin, &reads))
					{
						auto iter = _tcs.find(*begin);
						TcpConnecter* p = iter->second;
						int nRecv = recv(*begin, p->_recvBuf + p->_recvLen,
							p->_recvMaxLen - p->_recvLen, 0);

						if (nRecv <= 0)
						{
							closesocket(*begin);
							OnDisConnect(*begin);
							delete p;
							_tcs.erase(*begin);
							begin = _clientSocks.erase(begin);
							end = _clientSocks.end();
							continue;
						}
						p->_recvLen += nRecv;
						int nRet = OnNetMsg(*begin, p->_recvBuf, p->_recvLen);
						if (nRet <= 0)
							return;
						memmove(p->_recvBuf, p->_recvBuf + nRet, p->_recvLen - nRet);
						p->_recvLen -= nRet;
					}
					++begin;
				}
			}
		}
	}

	void Accept()
	{
		SOCKADDR_IN clientAddr;
		int clientAddrLen = sizeof(clientAddr);
		SOCKET clientSock = accept(_serverSock, (sockaddr*)&clientAddr, &clientAddrLen);
		if (INVALID_SOCKET != clientSock)
		{
			_clientSocks.push_back(clientSock);
			OnConnect(clientSock);
			TcpConnecter* p = new TcpConnecter;
			_tcs.insert(make_pair(clientSock, p));
		}
	}

	void OnConnect(SOCKET clientSock)
	{
		printf("%d 客户端连接\n", clientSock);
	}

	int OnNetMsg(SOCKET clientSock, const char* buff, int len)
	{
		/*printf("%d == %s\n", clientSock, buff);
		for (auto&& clientSock : _clientSocks)
			send(clientSock, buff, len, 0);*/
		if (len < sizeof(struct SNetHeader))
			return 0;
		struct SNetHeader* header = (SNetHeader*)buff;

		//解决连包问题
		if (len < header->len)
			return 0;

		switch (header->type)
		{
		case ENetHeader::LOGIN_C2S:
		{
			struct SLoginC2S* login = (SLoginC2S*)buff;
			auto iter = _map.find(login->userName);
			struct SLoginS2C repurclient;
			if (iter != _map.end() && strcmp(iter->second.c_str(), login->password) == 0)
				repurclient.bLogin = true;
			else
				repurclient.bLogin = false;
			Send(clientSock, (char*)&repurclient, sizeof(SLoginS2C));
			break;
		}
		case ENetHeader::MSG_C2S:
		{
			struct SMsgC2S* clientMsg = (SMsgC2S*)buff;
			struct SMsgS2C serverMsg;
			strcpy(serverMsg.msg, clientMsg->msg);
			for (auto&& clientSock : _clientSocks)
				send(clientSock, (char*)&serverMsg, sizeof(SMsgS2C), 0);
			break;
		}
		default:
			break;
		}
		return len;
	}


	void OnDisConnect(SOCKET sock)
	{
		printf("%d==客户端断开连接\n", sock);
	}

	void Send(SOCKET sock, const char* buf, int len)
	{
		send(sock, buf, len, 0);
	}

private:
	SOCKET _serverSock;
	vector<SOCKET> _clientSocks;
	map<string, string> _map;
	map<SOCKET, TcpConnecter*> _tcs;
};

ChatRoom.cpp

// ChatRoom.cpp: 定义应用程序的入口点。
//
#include "ChatRoom.h"
int main()
{
	WSADATA wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);

	TcpServer server;
	server.Listen("0.0.0.0", 7890);
	while (true)
		server.Update();

	WSACleanup();
	return 0;
}

版权声明:本文为CSDN博主「ufgnix0802」的原创文章:
原文链接:blog.csdn.net/qq135595696…