TCP网络事件模型的封装2.0
最近学习了TCP网络模型的封装,其中运用的封装技术个人感觉有点绕
在反复读代码、做思维导图下初步理解了这套封装模型,不禁感叹原来代码还能这样写?神奇!
为此将源码分享出来并将流程图画出,方便理解和复习
PS:下列思维导图仅代表个人理解,如有误恳请指出纠正
2023/03/30:在原有事件模型基础上加入了发送模块,并将发送接受设置为了非阻塞状态,这样可避免出现客户端恶意拒收的情况,俗称黑客攻击
模型继承关系图
要理解该模型,我认为首先要画图理解类的继承关系,下面是我画的一个简单关系图
模型流程图
下面是我画的该模型的大致流程图,仅供参考,有误恳请指出
具体代码实现
EventLoop.hpp
#ifndef _EVENTLOOP_H_
#define _EVENTLOOP_H_
#include <list>
#include <WinSock2.h>
enum class E_Event_Type
{
Recv,
Send,
};
class IEventCallback
{
public:
virtual void OnNetEvent(E_Event_Type e) = 0;
virtual void OnClose() = 0;
virtual bool NeedWrite() = 0;
};
struct sSelectEvent
{
SOCKET sock;
IEventCallback* event;
};
class EventLoop
{
private:
std::list<sSelectEvent*> _events;
std::list<sSelectEvent*> _delEventCaches;
public:
void LoopOnce();
void AddEvent(sSelectEvent* e)
{
_events.push_back(e);
}
void DelEvent(sSelectEvent* e);
};
#endif
EventLoop.cpp
#include "EventLoop.hpp"
void EventLoop::LoopOnce()
{
fd_set reads;
fd_set writes;
FD_ZERO(&reads);
FD_ZERO(&writes);
do
{
auto begin = _events.begin();
auto end = _events.end();
for (; begin != end; ++begin)
{
FD_SET((*begin)->sock, &reads);
if ((*begin)->event->NeedWrite())
{
FD_SET((*begin)->sock, &writes);
}
}
} while (false);
int nSeclect = select(0, &reads, &writes, nullptr, nullptr);
if (0 == nSeclect)return;
if (nSeclect < 0)
return;
do
{
auto begin = _events.begin();
auto end = _events.end();
for (; begin != end; ++begin)
{
if (FD_ISSET((*begin)->sock, &reads))
{
(*begin)->event->OnNetEvent(E_Event_Type::Recv);
}
}
} while (false);
do
{
auto begin = _delEventCaches.begin();
auto end = _delEventCaches.end();
for (; begin != end; ++begin)
{
sSelectEvent* p = *begin;
_events.remove_if([p](sSelectEvent* a)
{
return a == p;
});
p->event->OnClose();
}
_delEventCaches.clear();
} while (false);
do
{
auto begin = _events.begin();
auto end = _events.end();
for (; begin != end; ++begin)
{
if (FD_ISSET((*begin)->sock, &writes))
{
(*begin)->event->OnNetEvent(E_Event_Type::Send);
}
}
} while (false);
}
void EventLoop::DelEvent(sSelectEvent* e)
{
_delEventCaches.push_back(e);
}
TcpListen.hpp
#ifndef _TCPLISTEN_H_
#define _TCPLISTEN_H_
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include "EventLoop.hpp"
#include <WinSock2.h>
#include <iostream>
#include <functional>
class TcpSocket;
class TcpListen :public IEventCallback
{
protected:
SOCKET _sock;
sSelectEvent _event;
EventLoop* _loop;
std::function<TcpSocket* ()> _sockCB;
public:
TcpListen();
void Init(EventLoop* loop, std::function<TcpSocket* ()> sockCB)
{
_loop = loop; _sockCB = sockCB;
}
bool Listen(unsigned short port, const char* const ip = "0.0.0.0");
// 通过 IEventCallback 继承
virtual void OnNetEvent(E_Event_Type e) override;
virtual void OnClose() override;
virtual bool NeedWrite() override { return false; }
public:
virtual void OnAccpet(TcpSocket* sock) = 0;
};
#endif
TcpListen.cpp
#include "TcpListen.h"
#include "TcpSocket.h"
TcpListen::TcpListen()
{
_event.event = this;
_sock = _event.sock = INVALID_SOCKET;
_loop = nullptr;
}
bool TcpListen::Listen(unsigned short port, const char* const ip)
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == _sock)
{
std::cout << "create SOCKET fail!\n" << std::endl;
return false;
}
std::cout << "1.create SOCKET OK!\n" << std::endl;
// 2.绑定IP和端口
// bind(SOCKET,绑定的IP端口结构体,结构体大小)
SOCKADDR_IN serverAddr;
serverAddr.sin_family = AF_INET;
//SOCKADDR_IN6*
serverAddr.sin_port = htons(port);
// 127.0.0.1 本机回环地址
// 0.0.0.0 绑定所有IP
serverAddr.sin_addr.s_addr = inet_addr(ip);
//SOCKADDR_IN6 serverAddr6;
if (SOCKET_ERROR
== bind(_sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)))
{
std::cout << "bind SOCKET fail!\n" << std::endl;
return false;
}
std::cout << "2.bind SOCKET OK!\n" << std::endl;
listen(_sock, 5);
std::cout << "3.listen SOCKET OK!\n" << std::endl;
_event.sock = _sock;
_loop->AddEvent(&_event);
return true;
}
void TcpListen::OnNetEvent(E_Event_Type e)
{
SOCKADDR_IN clientAddr;
int addrlen = sizeof(SOCKADDR_IN);
SOCKET clientSock = accept(_sock, (SOCKADDR*)&clientAddr, &addrlen);
if (INVALID_SOCKET == clientSock)
{
std::cout << "4.ACCEPT ERROR!!\n" << std::endl;
return;
}
std::cout << "4.ACCEPT ip:" << inet_ntoa(clientAddr.sin_addr)
<< " Port:" << ntohs(clientAddr.sin_port) << " " << clientSock
<< std::endl;
TcpSocket* sock = _sockCB();
sock->OnAccept(clientSock, &clientAddr);
OnAccpet(sock);
}
void TcpListen::OnClose()
{
}
TcpSocket.hpp
#ifndef _TCPSOCKET_H_
#define _TCPSOCKET_H_
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include "EventLoop.hpp"
const unsigned int RECV_MAX_BUF = 4096 * 2;
class TcpSocket :public IEventCallback
{
private:
SOCKET _sock;
SOCKADDR_IN _addr;
sSelectEvent _event;
EventLoop* _loop;
char _recvBuff[RECV_MAX_BUF];
int _recvLen;
bool _bClose;
struct sPack
{
char* msg;
int len;
int size;
sPack(const char* const msg, int len)
{
this->size = len;
this->len = 0;
this->msg = (char*)malloc(len);
memcpy(this->msg, msg, len);
}
~sPack()
{
free(this->msg);
}
};
std::list<sPack*> _sends;
public:
TcpSocket();
virtual ~TcpSocket();
void Init(EventLoop* loop);
void OnAccept(SOCKET sock, SOCKADDR_IN* addr);
void Close();
// TODO 连接服务端
public:
// 通过 IEventCallback 继承
virtual void OnNetEvent(E_Event_Type e) override;
virtual void OnClose() override;
public:
virtual int OnNetMsg(const char* const msg, int msgLen) = 0;
virtual void Disconnect() = 0;
virtual bool NeedWrite() override;
void Send(const char* msg, int msgLen);
};
#endif
TcpSocket.cpp
#include "TcpSocket.h"
TcpSocket::TcpSocket()
{
_sock = _event.sock = INVALID_SOCKET;
_event.event = this;
_recvLen = 0;
_bClose = false;
}
TcpSocket::~TcpSocket()
{
}
void TcpSocket::Init(EventLoop* loop)
{
unsigned long ul = 1;
int ret = ioctlsocket(_sock, FIONBIO, (unsigned long*)&ul);
//设置成非阻塞模式。
_loop = loop;
_loop->AddEvent(&_event);
}
void TcpSocket::OnAccept(SOCKET sock, SOCKADDR_IN* addr)
{
_sock = sock;
_event.sock = sock;
memcpy(&_addr, addr, sizeof(addr));
}
void TcpSocket::Close()
{
if (_bClose) return;
_bClose = true;
if (_loop != NULL)
_loop->DelEvent(&_event);
else
OnClose();
}
void TcpSocket::OnNetEvent(E_Event_Type e)
{
if (E_Event_Type::Recv == e)
{
int nRecv = recv(_sock, _recvBuff + _recvLen, RECV_MAX_BUF - _recvLen, 0);
if (nRecv <= 0)
{
Close();
return;
}
_recvLen += nRecv;
while (_recvLen != 0)
{
int nRet = OnNetMsg(_recvBuff, _recvLen);
if (nRet <= 0)break;
_recvLen -= nRet;
for (int i = 0; i < _recvLen; ++i)
{
_recvBuff[i] = _recvBuff[i + nRet];
}
}
if (_recvLen == RECV_MAX_BUF) Close();
}
if (E_Event_Type::Send == e)
{
sPack* p = _sends.front();
int nRet = send(_sock, p->msg + p->len, p->size - p->len, 0);
if (nRet <= 0)
{
Close();
return;
}
p->len += nRet;
if (p->len == p->size)
{
_sends.pop_front();
delete p;
}
}
}
void TcpSocket::OnClose()
{
do
{
auto begin = _sends.begin();
auto end = _sends.end();
for (; begin != end; ++begin)
{
delete (*begin);
}
_sends.clear();
} while (false);
Disconnect();
closesocket(_sock);
delete this;
}
bool TcpSocket::NeedWrite()
{
return !_sends.empty();
}
void TcpSocket::Send(const char* msg, int msgLen)
{
//send(_sock, msg, msgLen, 0);
sPack* p = new sPack(msg, msgLen);
_sends.push_back(p);
}
EasyTcpServer.cpp
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32")
#include <vector>
#include "TcpListen.h"
#include "TcpSocket.h"
class EasyTcpClient :public TcpSocket
{
public:
// 通过 TcpSocket 继承
virtual int OnNetMsg(const char* const msg, int msgLen) override
{
//std::cout << msgLen << "接受客户端数据:" << msg << std::endl;
Send(msg, msgLen);
return msgLen;
}
// 通过 TcpSocket 继承
virtual void Disconnect() override
{
std::cout << "断开连接" << std::endl;
}
};
class EasyTcpServer :public TcpListen
{
public:
void OnAccpet(TcpSocket* sock) override
{
//sock->Init(_loop)
sock->Init(_loop);
std::cout << "客户端连接" << std::endl;
}
};
int main()
{
// 加载网络环境
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
EventLoop loop;
EasyTcpServer listen;
listen.Init(&loop, []()->TcpSocket*
{
return new EasyTcpClient;
});
listen.Listen(7890);
while (true)
{
loop.LoopOnce();
}
return 0;
}
最后
{
std::cout << "断开连接" << std::endl;
}
};
class EasyTcpServer :public TcpListen
{
public:
void OnAccpet(TcpSocket* sock) override
{
//sock->Init(_loop)
sock->Init(_loop);
std::cout << "客户端连接" << std::endl;
}
};
int main()
{
// 加载网络环境
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
EventLoop loop;
EasyTcpServer listen;
listen.Init(&loop, []()->TcpSocket*
{
return new EasyTcpClient;
});
listen.Listen(7890);
while (true)
{
loop.LoopOnce();
}
return 0;
}
# 最后
代码经供参考,如有疑问或者代码有问题欢迎提出