以下思路来源于部分网络库
以select为核心的基础事件循环句柄实现
#ifndef _EVENTLOOP_HPP_
#define _EVENTLOOP_HPP_
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <Winsock2.h>
#include <functional>
#include <unordered_map>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
namespace X
{
typedef struct sNetPoint
{
SOCKET sock;
SOCKADDR_IN addr;
} NetPoint;
class EventLoop
{
public:
typedef struct sHandle
{
SOCKET sock;
std::function<void()> readEvent;
std::function<void()> closeEvent;
} Handle;
private:
std::unordered_map<SOCKET, Handle *> _events;
std::list<Handle *> _removeEvents;
public:
EventLoop()
{
}
~EventLoop()
{
}
public:
void AddEvent(Handle *handle)
{
#ifdef _DEBUG
if (_events.find(handle->sock) != _events.end())
{
std::cout << "AddEvent 失败, handle->sock = " << handle->sock << " 没有移除" << std::endl;
return;
}
#endif
_events.insert(std::pair<SOCKET, Handle *>(handle->sock, handle));
// _events[handle->sock] = handle;
}
void RemoveEvent(Handle *handle)
{
_removeEvents.push_back(handle);
}
public:
void LoopOnce(int ms = 1)
{
fd_set reads;
// 清空或者初始化reads
FD_ZERO(&reads);
// 将所有的socket加入reads
for (auto iter = _events.begin(); iter != _events.end(); iter++)
{
FD_SET(iter->first, &reads);
}
int sRet = select(0, &reads, 0, 0, 0);
// 表示select超时或者出错
if (sRet <= 0) return;
for (auto iter = _events.begin(); iter != _events.end(); iter++)
{
if (FD_ISSET(iter->first, &reads))
{
iter->second->readEvent();
}
}
for (auto iter = _removeEvents.begin(); iter != _removeEvents.end(); iter++)
{
_events.erase((*iter)->sock);
(*iter)->closeEvent();
}
_removeEvents.clear();
}
};
}
#endif
创建一个 TCP 服务端->处理TCP 连接请求
#ifndef _TCPACCEPT_H_
#define _TCPACCEPT_H_
#include "EventLoop.hpp"
namespace X
{
class TcpAccept
{
private:
EventLoop::Handle _handle;
EventLoop *_loop;
std::function<void(NetPoint *point)> _cb;
public:
TcpAccept(EventLoop *loop, std::function<void(NetPoint *point)> cb)
{
_handle.readEvent = std::bind(&TcpAccept::OnRead, this);
_loop = loop;
_cb = cb;
}
~TcpAccept()
{
}
bool Listen(int port, const char *const ip = "0.0.0.0")
{
SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sockServer)
{
printf("创建服务端句柄失败\n");
return false;
}
SOCKADDR_IN addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port); // 端口号 host to net short
addr.sin_addr.S_un.S_addr = inet_addr(ip); // IP地址
int ret = bind(sockServer, (sockaddr *) &addr, sizeof(SOCKADDR_IN));
if (SOCKET_ERROR == ret)
{
printf("绑定端口号失败\n");
return false;
}
ret = listen(sockServer, 5);
if (SOCKET_ERROR == ret)
{
printf("监听端口号失败\n");
return false;
}
std::cout << "IP:" << ip << " Port:" << port << std::endl;
_handle.sock = sockServer;
_loop->AddEvent(&_handle);
return true;
}
private:
void OnRead()
{
std::cout << "OnRead" << std::endl;
// 当 accept 可读时表示 有新的连接
SOCKADDR_IN clientAddr = {};
int nAddrLen = sizeof(SOCKADDR_IN);
SOCKET sockClient = accept(_handle.sock, (sockaddr *) &clientAddr, &nAddrLen);
if (INVALID_SOCKET == sockClient)
{
printf("接收客户端连接失败\n");
// return -1;
}
std::cout << "client IP:" << inet_ntoa(clientAddr.sin_addr) << " Port:" << ntohs(clientAddr.sin_port)
<< std::endl;
NetPoint point;
point.sock = sockClient;
memcpy(&point.addr, &clientAddr, sizeof(clientAddr));
// closesocket(sockClient);
_cb(&point);
}
};
}
#endif
处理TCP链接的数据收发,提供基本的连接管理和数据处理框架
#ifndef _TCPSOCKET_HPP
#define _TCPSOCKET_HPP
#include "EventLoop.hpp"
namespace X
{
class TcpSocket
{
private:
EventLoop *_loop;
NetPoint _point;
EventLoop::Handle _handle;
char *_recvBuf;
int _recvMaxSize;
int _recvCurrSize;
bool _bClose;
public:
TcpSocket() : _point()
{
_bClose = false;
_loop = nullptr;
_handle.sock = INVALID_SOCKET;
_handle.readEvent = std::bind(&TcpSocket::OnRead, this);
_handle.closeEvent = std::bind(&TcpSocket::OnClose, this);
_recvMaxSize = 4096;
_recvBuf = new char[_recvMaxSize];
_recvCurrSize = 0;
}
virtual ~TcpSocket()
{
delete[] _recvBuf;
}
EventLoop::Handle GetHandle()
{
return _handle;
}
public:
void OnAccept(EventLoop *loop, NetPoint *netPoint)
{
_loop = loop;
_point = *netPoint;
_handle.sock = netPoint->sock;
_loop->AddEvent(&_handle);
}
void Close()
{
if (_bClose)
return;
_bClose = true;
_loop->RemoveEvent(&_handle);
}
private:
void OnRead()
{
int nLen = recv(_point.sock, _recvBuf + _recvCurrSize, _recvMaxSize - _recvCurrSize, 0);
if (nLen <= 0)
{
Close();
return;
}
_recvCurrSize += nLen;
while (_recvCurrSize > 0)
{
int nRet = OnNetMsg(_recvBuf, _recvCurrSize);
if (nRet <= 0)
break;
_recvCurrSize -= nRet;
if (_recvCurrSize > 0)
memcpy(_recvBuf, _recvBuf + nRet, _recvCurrSize);
}
}
void OnClose()
{
closesocket(_point.sock);
OnDisconnect();
}
public:
virtual int OnNetMsg(const char *const buf, int len) = 0;
virtual void OnDisconnect() = 0;
};
}
#endif
TcpSocket的具体实现
一个简单的聊天服务端,只实现了登陆和群聊
通过#号分割信息
#include <iostream>
#include "EventLoop.hpp"
#include "TcpAccept.hpp"
#include "TcpSocket.hpp"
#include <map>
namespace
{
std::map<int, class TcpSocket*> g_clients;
class TcpSocket : public X::TcpSocket
{
private:
bool _bLogin = false;
int _userId = 0;
std::string _userName; //客户端的用户名
public:
TcpSocket() : X::TcpSocket()
{
static int userId = 0;
_userId = ++userId;
g_clients[_userId] = this;
}
virtual ~TcpSocket()
{
}
// 分割字符串函数
// INPUIT: const std::string& s 待分割字符串, char delimiter 分割符号
// RETURN: std::vector<std::string> 存储分割的字符串的数组
std::vector<std::string> splitString(std::string_view s, char delimiter)
{
std::vector<std::string> result;
std::string path;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != delimiter)
{
path.push_back(s[i]);
}
else if (!path.empty()) // 确保path非空时才push_back
{
result.push_back(path);
path.clear();
}
}
if (!path.empty()) // 处理字符串以分隔符结尾的情况
{
result.push_back(path);
}
return result;
}
void SendUserList()
{
std::cout << "发送用户列表" << std::endl;
std::string UserList = "UserList";
for (auto iter = g_clients.begin(); iter != g_clients.end(); iter++)
{
UserList.append("|");
UserList.append(iter->second->_userName);
}
SendMsg(_userId, UserList.c_str());
}
// 对特定客户端发送信息函数
void SendMsg(int uid, const char* msg)
{
int msglen = strlen(msg);
if (g_clients.find(uid) != g_clients.end())
{
send(g_clients[uid]->GetHandle().sock, msg, msglen, 0);
}
}
//广播给除了特定客户端以外的端点函数
void BoardCastMsg(int uid, const char* msg)
{
int msglen = strlen(msg);
for (auto iter = g_clients.begin(); iter != g_clients.end(); iter++)
{
if (iter->first != uid)
{
send(iter->second->GetHandle().sock, msg, msglen, 0);
}
}
}
int OnNetMsg(const char* buf, int len) override
{
// std::cout << std::string(buf, len) << std::endl;
int index = -1;
for (int i = 0; i < len; ++i)
{
//再接受到#号前会一直将消息存储起来
if (buf[i] == '#')
{
index = i;
break;
}
}
if (index == -1)return 0;
//拿到缓冲区的消息
std::string comm(buf, index + 1);
std::cout << comm << std::endl;
//清除#号
comm.pop_back();
std::vector<std::string> cmdStr = splitString(comm, '|');
if (cmdStr[0] == "Login")
{
// 验证重复登录
if (this->_userName == cmdStr[1] || cmdStr.size() < 2)
{
std::string loginFailedMsg = "Error|LoginFailed";
SendMsg(_userId, loginFailedMsg.c_str());
return index + 1;
}
_userName = cmdStr[1];
std::string UserLoginOK = "LoginOK";
SendMsg(_userId, UserLoginOK.c_str());
std::cout << "用户[" << _userName << "]登录" << std::endl;
}
else if (cmdStr[0] == "Group")
{
if (cmdStr.size() < 2)
{
std::string chatFailedMsg = "Error|ChatFailed";
SendMsg(_userId, chatFailedMsg.c_str());
return index + 1;
}
std::cout << "群发:" << cmdStr[1] << std::endl;
std::string chatMsg = "Group|" + _userName + ":" + cmdStr[1];
// 发送消息
BoardCastMsg(-1, chatMsg.data());
}
else if (cmdStr[0] == "其他命令")
{
//其它命令相关
}
//Login|UserName|PassWord#
//Group|chatMessage#
return index + 1;
}
void OnDisconnect() override
{
g_clients.erase(_userId);
std::cout << "客户端关闭" << std::endl;
delete this;
}
public:
bool IsLogin() const
{
return _bLogin;
}
};
}
int main()
{
// 0. 初始化网络环境
WSADATA wsaData = {};
WSAStartup(MAKEWORD(2, 2), &wsaData);
X::EventLoop loop;
X::TcpAccept accept(&loop, [&loop](X::NetPoint* point)
{
TcpSocket* socket = new TcpSocket();
socket->OnAccept(&loop, point);
});
accept.Listen(9870);
while (true)
{
loop.LoopOnce();
}
return 0;
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。