本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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
枚举类的优势:
-
降低命名空间的污染
以下代码在同一个作用域下
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; //正确 -
避免发生隐式转换
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型别 } -
可以提前声明(强制声明)
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 ®
}
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…