栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ select模型聊天室 - 进阶

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ select模型聊天室 - 进阶

C++ select模型聊天室 - 进阶

初级版链接:https://blog.csdn.net/qq135595696/article/details/124156875

新增内容

TIPS:实现功能部分没有增加,功能与初级版本效果展示部分相同。

  1. 服务端代码进一步封装TcpConnecter,将客户端socket句柄进行封装,不暴露给TcpServer类。
  2. 服务端构建发送缓冲区,与接收缓冲区相呼应。同时封装send()函数,使用select的写方法进行监听是否可写,结合发送缓冲区进行使用。
  3. 客户端同上第2点,封装send()函数,使用select的写方法进行监听是否可写以及构建发送缓冲区。
数据包规范 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.hpp
// CMakeClient0331.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。

#pragma once


// TODO: 在此处引用程序需要的其他标头。
#include 
#include 
#include 
#include 
#include 
#include 
#include "NetHeader.h"

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

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 ®
	}

	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] = '';
					inputLen = 0;
					left = false;
				}
			}
			else
			{
				_login.password[inputLen++] = c;
				if (c == 'r')
				{
					_login.password[inputLen - 1] = '';
					inputLen = 0;
					break;
				}
			}
		}
	}


	SLoginC2S GetUerMsg() { return _login; }
};


class TcpClient
{
private:
	SOCKET _sock;
	//接收缓冲区
	char* _buffer;
	int _bufferLen;
	int _bufferMaxLen;

	//发送缓冲区
	char _sendBuf[1024];
	int _sendLen;
	int _sendMaxLen;
public:
	std::vector msg;
public:
	bool isLoginSuccess;

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

		_sendLen = 0;
		_sendMaxLen = 1024;
	}

	~TcpClient()
	{
		delete[] _buffer;
		closesocket(_sock);
		_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;
			msg.push_back(serverMsg->msg);
			break;
		}
		default:
			break;
		}
		return len;
	}

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

		OnNetMsg(nullptr, 0);

		closesocket(_sock);
		_sock = INVALID_SOCKET;
	}

	bool Send(const char* buf, int len)
	{
		FD_SET writes;
		FD_ZERO(&writes);
		FD_SET(_sock, &writes);

		bool bStorage = false;//表示是否可以存放
		//如果发送缓冲区可以存放buf数据
		if (_sendLen + len <= _sendMaxLen)
		{
			memmove(_sendBuf + _sendLen, buf, len);
			_sendLen += len;
			bStorage = true;
		}

		if (0 == _sendLen)return false;//数据包如果过大
		struct timeval timer { 0, 1000 };//1000微秒
		int nRet = select(0, nullptr, &writes, nullptr, &timer);
		if (nRet <= 0)
			return false;

		int nRetSend = send(_sock, _sendBuf, _sendLen, 0);
		_sendLen -= nRetSend;

		//如果发送缓冲区不可以存放buf数据
		if (!bStorage)
		{
			memmove(_sendBuf + _sendLen, buf, len);
			_sendLen += len;
		}
		return true;
	}
};

//聊天信息显示
class ChatRoom : public TcpClient
{
private:
	COORD _coord;
public:
	void ChatRoomDemo()
	{
		_coord.X = 1;
		_coord.Y = 1;
		if (msg.size() == 11)
		{
			msg.erase(msg.begin());
			ClsMsg();
			return;
		}
		for (auto&& msgItem : msg)
		{
			SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
			std::cout << Register::GetInstance()->GetUerMsg().userName << ":" << msgItem;
			_coord.Y++;
		}
	}

	void ClsMsg()
	{
		_coord.X = 1;
		_coord.Y = 1;
		for (int i = 0; i < 11; i++)
		{
			SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), _coord);
			std::cout << "                                                              ";
			_coord.Y++;
		}
	}
};
Client.cpp
// CMakeClient0331.cpp: 定义应用程序的入口点。
//

#include "Client.hpp"

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


	ChatRoom 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] = '';
					SMsgC2S clientMsg;
					strcpy(clientMsg.msg, inputBuf);
					int a = sizeof(SMsgC2S);
					client.Send((char*)&clientMsg, sizeof(SMsgC2S));
					IBLen = 0;
				}
			}
		}
		client.Update();
		client.ChatRoomDemo();
	}

	WSACleanup();
	return 0;
}
服务端源码 ChatRoom.hpp
// ChatRoom.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。

#pragma once

// TODO: 在此处引用程序需要的其他标头。
#include 
#include 
#include "NetHeader.h"
#include 
#include 
#include 

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

using namespace std;

class TcpConnecter
{
private:
	SOCKET _sock;
	//接收缓冲区
	char _recvBuf[1024];
	int _recvLen;
	int _recvMaxLen;

	//发送缓冲区
	char _sendBuf[1024];
	int _sendLen;
	int _sendMaxLen;
public:
	TcpConnecter(SOCKET sock_)
	{
		_sock = sock_;
		_recvLen = 0;
		_recvMaxLen = 1024;

		_sendLen = 0;
		_sendMaxLen = 1024;
	}

	//获取当前客户端发送过来的数据容器首地址
	char* GetBuf() { return _recvBuf; }

	//获取当前数据容器的已有数据的长度
	int GetLen() { return _recvLen; }

	//接收数据
	bool OnRecv()
	{
		int nRecv = recv(_sock, _recvBuf + _recvLen,
			_recvMaxLen - _recvLen, 0);
		if (nRecv <= 0)
			return false;
		_recvLen += nRecv;
		return true;
	}

	//去除目前已经使用的长度
	void UseBuf(int len)
	{
		memmove(_recvBuf, _recvBuf + len, _recvLen - len);
		_recvLen -= len;
	}

	//发送数据
	bool OnSend(const char* buf, int len)
	{
		FD_SET writes;
		FD_ZERO(&writes);
		FD_SET(_sock, &writes);

		bool bStorage = false;//表示是否可以存放
		//如果发送缓冲区可以存放buf数据
		if (_sendLen + len <= _sendMaxLen)
		{
			memmove(_sendBuf + _sendLen, buf, len);
			_sendLen += len;
			bStorage = true;
		}

		if (0 == _sendLen)return false;//数据包如果过大
		struct timeval timer { 0, 1000 };//1000微秒
		int nRet = select(0, nullptr, &writes, nullptr, &timer);
		if (nRet <= 0)
			return false;

		int nRetSend = send(_sock, _sendBuf, _sendLen, 0);
		_sendLen -= nRetSend;

		//如果发送缓冲区不可以存放buf数据
		if (!bStorage)
		{
			memmove(_sendBuf + _sendLen, buf, len);
			_sendLen += len;
		}
		return true;
	}

	//关闭当前客户端
	void CLose()
	{
		if (_sock == INVALID_SOCKET)
			return;
		closesocket(_sock);
	}
};


class TcpServer
{
private:
	SOCKET _serverSock;
	map _map;
	map _tcs;
public:
	TcpServer()
	{
		_map.insert(make_pair("xxx", "123"));
		_map.insert(make_pair("xhh", "123"));
		_serverSock = INVALID_SOCKET;
	}

	~TcpServer()
	{
		closesocket(_serverSock);
		_serverSock = INVALID_SOCKET;
		_map.clear();
		auto begin = _tcs.begin();
		auto end = _tcs.end();
		for (; begin != end; begin++)
			delete begin->second;
		_tcs.clear();
	}

	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&& clientSockItem : _tcs)
			FD_SET(clientSockItem.first, &reads);

		int nRet = select(0, &reads, nullptr, nullptr, nullptr);
		if (nRet > 0)
		{
			if (FD_ISSET(_serverSock, &reads))
				Accept();
			else
			{
				auto begin = _tcs.begin();
				auto end = _tcs.end();
				for (; begin != end;)
				{
					if (FD_ISSET(begin->first, &reads))
					{
						TcpConnecter* p = begin->second;
						if (!p->OnRecv())
						{
							p->CLose();
							OnDisConnect(p);
							delete p;
							begin = _tcs.erase(begin);
							continue;
						}

						int nRet = OnNetMsg(p);
						if (nRet <= 0)
							return;
						p->UseBuf(nRet);
					}
					++begin;
				}
			}
		}
	}

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

	void OnConnect(TcpConnecter* p)
	{
		printf("客户端连接n");
	}

	int OnNetMsg(TcpConnecter* client)
	{
		if (client->GetLen() < sizeof(struct SNetHeader))
			return 0;
		struct SNetHeader* header = (SNetHeader*)client->GetBuf();

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

		switch (header->type)
		{
		case ENetHeader::LOGIN_C2S:
		{
			struct SLoginC2S* login = (SLoginC2S*)client->GetBuf();
			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;
			client->OnSend((char*)&repurclient, sizeof(SLoginS2C));
			break;
		}
		case ENetHeader::MSG_C2S:
		{
			struct SMsgC2S* clientMsg = (SMsgC2S*)client->GetBuf();
			struct SMsgS2C serverMsg;
			strcpy(serverMsg.msg, clientMsg->msg);
			for (auto&& clientItem : _tcs)
				clientItem.second->OnSend((char*)&serverMsg, sizeof(SMsgS2C));
			break;
		}
		default:
			break;
		}
		return client->GetLen();
	}

	void OnDisConnect(TcpConnecter* client)
	{
		printf("==客户端断开连接n");
	}

	void Send(TcpConnecter* client)
	{
		client->OnSend(client->GetBuf(), client->GetLen());
	}
};
ChatRoom.cpp
// ChatRoom.cpp: 定义应用程序的入口点。
//

#include "ChatRoom.hpp"

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;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/976800.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号