IOCP模型C++服务端-接收客户端数据并响应客户端,同时管理所有客户端句柄
效果展示
源码展示
WSASend
在连接上的socket上发送数据。
int WSAAPI WSASend(
_In_ SOCKET s, 客户端句柄
_In_reads_(dwBufferCount) LPWSABUF lpBuffers, 发送缓冲区
_In_ DWORD dwBufferCount, 发送缓冲区的数量
_Out_opt_ LPDWORD lpNumberOfBytesSent, (如果发送操作立即完成)指向发送字节数
_In_ DWORD dwFlags, 标记位
_Inout_opt_ LPWSAOVERLAPPED lpOverlapped, 重叠I/O结构
_In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 指向例程的指针
);
参数:
- s,指向已连接的套接字(即客户端句柄)。
- lpBuffers,一个指向WSABUF结构数组的指针(也可以是WASBUF结构指针)。每一个WSABUF结构包含一个缓冲区的指针和缓冲区的长度。
- dwBufferCount,WSABUF结构数组中WSABUF结构的数量(如果lpBuffers为WSABUF结构指针,则该参数可以填1)。
- lpNumberOfBytesSent,如果接受数据的操作立即结束,那么接收的数据字节长度会返回给lpNumberOfBytesSent指针所指向的对象。(如果lpOverlapped结构不是NULL,那么应该使用NULL来填充此参数,以避免潜在的错误结果。即只要lpOverlapped不为NULL,那么这个参数必须为NULL或0)。
- dwFlags,用于更改WSASend函数的行为的标记。(即用来控制套接字的行为。例如,指出当前套接字是面向流(连接)的还是面向消息(无连接)的。通常设置为0。)
- lpOverlapped,一个指向重叠I/O结构的指针。
- lpCompletionRoutine,一个指向完成例程的指针。在发送操作完成后,会调用指针所指向的例程函数。在重叠I/O结构中该参数可设置为NULL。
shutdown
shutdown函数禁止一个socket发送数据或者接收数据。
int WSAAPI shutdown(
_In_ SOCKET s, 所要操作的句柄
_In_ int how 描述socket不允许哪种操作类型
);
参数:
-
s,所要操作的句柄(文件描述符)。
-
how,描述socket不允许哪种操作类型,如下:
值 含义 SD_RECEIVE 0 关闭接收操作 SD_SEND 1 关闭发送操作 SD_BOTH 2 关闭发送和接收操作
返回值:
如果执行成功,则返回0。否则,返回套接字错误的值,并通过调用WSAGetLastError()函数来检测错误代码。
参考链接:https://blog.csdn.net/weixin_40179091/article/details/113669146
因为socket是双向的,client和发server都可以进行input和output,但是有时候我们需要数据在socekt上进行单向传输,即数据往一个方向传输。单向的socket为半开放的socket。而要实现半开放式,需要用到shutdown()函数。
shutdown()和close()/closesocket()的区别
在多进程的环境中,close()/closesocket()破坏当前的进程所引用的socket标识符,但是socket还是完好存在的,其它的进程还是可以照样使用(就像shared_ptr智能指针一样),当引用数降为0的时候,socket被释放销毁。
shutdown()用来关闭连接,而不是套接字。不管调用多少次shutdown(),套接字依然存在,直到调用close()/closesocket()将套接字从内存中消除为止。而shutdown()的杀伤面积比较广,假设有一对父子进程共用一个socket,如果父进程对此socket的标识符进行shutdown(fd,SHUT_RD)操作,则此socket的所有引用操作符均无法接收数据,只能发送数据。
调用close()/closesocket()关闭套接字时,或者调用shutdown()关闭输出流时,都会向对方发送FIN包(三握四挥中有提到)。FIN包表示数据传输完毕,对方收到FIN包就知道对方不会再有数据传输过来。
默认情况下,close()/closesocket()会立即向网络中发送FIN包,不管输出缓冲区和接收缓冲区是否还有数据,而shutdown()会等输出缓冲区的数据传输完毕再发送FIN包。也就意味着,调用close()/closesocket()将丢失输出缓冲区和接收缓冲区的数据,而调用shutdown()则不会丢失。
shutdown函数应用场景
因为shutdown()函数可以让socket实现单向传输,而半开放socket适用于以下场景:
- 当你想要确保所有写好的数据已经发送成功时。如果在发送数据的过程中,网络出现断开或者异常,系统不一定会返回异常,这时候要怎么确定对端已经收到数据了呢?可以使用shutdown()函数来确定数据是否发送成功,因为调用shutdown()函数时只有在缓存中的数据全部发送成功后才返回。
- 当不能往有些socket上写数据时,或者不能从有些socket上读数据时,可以使用shutdown()让其断开,后续对其进行读写的操作会返回异常。
- 当多线程时,想防止其它线程或者进程访问该资源时,或者你想彻底关闭这个socket,让别的线程或者进程没法使用该socke可以调用shutdown()。
PostQueuedCompletionStatus
发送一个I/O完成数据包到I/O完成端口中。
BOOL WINAPI PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort, I/O完成端口
_In_ DWORD dwNumberOfBytesTransferred, 传输的字节数
_In_ ULONG_PTR dwCompletionKey, I/O完成密钥
_In_opt_ LPOVERLAPPED lpOverlapped 重叠I/O结构体
);
参数:
- CompletionPort,I/O完成端口。
- dwNumberOfBytesTransferred,通过lpNumberOfBytesTransferred字段返回的字节大小,即GetQueuedCompletionStatus函数返回中的lpNumberOfBytesTransferred字段。
- dwCompletionKey,通过 lpCompletionKey 字段返回的I/O完成密钥,即GetQueuedCompletionStatus函数返回中的lpCompletionKey字段。
- lpOverlapped,通过lpOverlapped 字段返回的I/O完成端口,即GetQueuedCompletionStatus函数返回中的lpOverlapped字段。
返回值:
如果函数成功,则返回值为非0。
如果函数失败,则返回值为0,如果想要获取额外的错误信息,可以使用GetLastError函数。
注意点:
PostQueuedCompletionStatus函数可以向I/O完成端口发送一个特殊的数据包,用于通知在I/O完成端口中运行的文件描述符(句柄)。其中第一个参数务必是I/O完成端口对象,至于另外三个参数:dwNumberOfBytesTransferred,dwCompletionKey和lpOverlapped,我们可以自己指定其中的值。这样一来,正在工作的文件描述符(句柄)可以在收到对应的参数之后处理相应的事件。比如我们给dwNumberOfBytesTransferred参数设置为0,表示终止指令。一旦所有的正在工作的文件描述符(句柄)收到这条指令,那么我们就可以关闭相应的文件描述符(句柄)。
即PostQueuedCompletionStatus函数提供了一种方式,用于跟I/O完成端口中的文件描述符(句柄)进行通信。
XNetCore.h
#ifndef _X_NET_CORE_H_
#define _X_NET_CORE_H_
//#define X_SELECT_NETCORE
#define X_IOCP_NETCORE
#include <functional>
namespace X {
class ILoopEvent
{
public:
virtual void Init() = 0;
virtual void LoopOnce() = 0;
virtual void UnInit() = 0;
virtual ~ILoopEvent() {}
};
class ITcpSocketCB
{
public:
virtual bool OnRecv(int len) = 0;
virtual bool OnSend(int len) = 0;
//客户端断开后的回调
virtual void OnClose() = 0;
virtual ~ITcpSocketCB() {}
};
class ITcpSocket
{
public:
virtual void Init(ILoopEvent* loop, ITcpSocketCB* cb) = 0;
virtual bool DoSend(const char* buff, int len) = 0;
virtual bool DoRecv(char const* buff, int len) = 0;
//主动关闭客户端
virtual bool Close() = 0;
virtual ~ITcpSocket() {}
};
typedef std::function<void(ITcpSocket*)> FTcpServerCB;
class ITcpServer
{
public:
// 和 循环事件关联
/// <summary>
/// 初始化TCP服务
/// </summary>
/// <param name="loop">循环事件</param>
/// <param name="cb">当用户连接成功后 回调的函数</param>
/// <returns></returns>
virtual bool Init(ILoopEvent* loop, FTcpServerCB cb) = 0;
virtual bool Listen(const char* ip, unsigned short port) = 0;
virtual ~ITcpServer() {}
};
void InitNetCore();
ILoopEvent* CreateLoopEvent();
ITcpServer* CreateTcpServer();
}
#endif // !_X_NET_CORE_H_
IOCPLoopEvent.h
#include "XNetCore.h"
#ifdef X_IOCP_NETCORE
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <unordered_map>
#pragma comment(lib,"ws2_32")
#pragma comment(lib, "Mswsock")
#pragma comment(lib, "shlwapi")
#pragma comment(lib, "psapi")
#include <iostream>
#ifndef X_LOPEVENT_H_
#define X_LOPEVENT_H_
namespace X {
struct sEvent
{
enum class Type
{
E_TCPSERVER,
E_TCPSOCKET,
PCK_ACCEPT_CLOSE,
PCK_SOCKET_CLOSE,
PCK_USER_DATA, //用户投递的数据,调用DoPost让用户自己消耗
};
Type type;
SOCKET sock;
union
{
class TcpServer* tcpServer;
class TcpSocket* tcpSocket;
void* ptr;
};
};
struct sReqHandle
{
OVERLAPPED overlapped;
enum class sReqHandleE
{
HANDLE_ACCEPT = 0,
HANDLE_RECV,
HANDLE_SEND,
HANDLE_CONNECT,
HANDLE_RECVFROM,
HANDLE_SENDTO,
};
sReqHandleE eType;//投递的类型
void* ptr;
};
class LoopEvent :public ILoopEvent
{
private:
std::unordered_map<SOCKET, sEvent*> _events;
HANDLE _iocp;
public:
void Init() override;
void LoopOnce() override;
void UnInit() override;
public:
void AddEvent(sEvent* event);
// 关联完成端口
bool AssioIOCP(SOCKET sock, void* ptr);
//消息处理
void PostMsg(sEvent::Type eKey, void* pData);
};
}
#endif // !_ILOOPEVENT_H_
#endif // !X_SELECT_NETCORE
IOCPLoopEvent.cpp
#include "IOCPLoopEvent.h"
#ifdef X_IOCP_NETCORE
#include "IOCPTcpServer.h"
#include "IOCPTcpSocket.h"
namespace X {
void InitNetCore()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// TODO 添加异常导出库 程序崩溃生成崩溃信息
}
ILoopEvent* CreateLoopEvent()
{
return new LoopEvent();
}
}
void X::LoopEvent::Init()
{
_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
if (_iocp == INVALID_HANDLE_VALUE)
{
std::cout << "创建完成端口失败" << std::endl;
return;
}
std::cout << "创建完成端口成功" << std::endl;
}
void X::LoopEvent::LoopOnce()
{
DWORD lpNumberOfBytesTransferred;
void* lpCompletionKey = nullptr;
OVERLAPPED* lpOverlapped;
DWORD dwMilliseconds;
BOOL bRet = GetQueuedCompletionStatus(_iocp, &lpNumberOfBytesTransferred
, (PULONG_PTR)&lpCompletionKey, &lpOverlapped, 0);
if (!bRet && lpOverlapped)
{
return;
}
if (lpCompletionKey == nullptr) return;
sEvent* event = (sEvent*)lpCompletionKey;
switch (event->type)
{
case sEvent::Type::E_TCPSERVER:
event->tcpServer->OnAccept();
break;
case sEvent::Type::E_TCPSOCKET:
event->tcpSocket->OnNetMsg((sReqHandle*)lpOverlapped, bRet, lpNumberOfBytesTransferred);
break;
case sEvent::Type::PCK_SOCKET_CLOSE:
event->tcpSocket->OnClose();
break;
default:
break;
}
}
void X::LoopEvent::UnInit()
{
}
void X::LoopEvent::AddEvent(sEvent* event)
{
std::cout << "添加 事件" << event->sock
<< " type:" << (int)event->type
<< " ptr:" << event->tcpServer
<< std::endl;
_events.insert(std::pair<SOCKET, sEvent*>(event->sock, event));
}
bool X::LoopEvent::AssioIOCP(SOCKET sock, void* ptr)
{
return _iocp == CreateIoCompletionPort((HANDLE)sock, _iocp, (ULONG_PTR)ptr, 0);
}
void X::LoopEvent::PostMsg(sEvent::Type eKey, void* pData)
{
sEvent* event = new sEvent();
event->type = eKey;
event->ptr = pData;
if (PostQueuedCompletionStatus(_iocp, 0, (ULONG_PTR)event, (LPOVERLAPPED)pData) != 0)
std::cout << "消息发布成功" << std::endl;
}
#endif // !X_SELECT_NETCORE
IOCPTcpServer.h
#include "XNetCore.h"
#include "IOCPLoopEvent.h"
#ifdef X_IOCP_NETCORE
#ifndef X_TCP_SERVER_H_
#define X_TCP_SERVER_H_
namespace X {
class TcpServer :public ITcpServer
{
private:
LoopEvent* _loop;
FTcpServerCB _cb;
SOCKET _sock;
sEvent _event;
sReqHandle _handle;
SOCKET _socket;
char _recvBuf[1024];
DWORD _recvLen;
public:
bool Init(ILoopEvent* loop, FTcpServerCB cb) override;
bool Listen(const char* ip, unsigned short port) override;
public:
TcpServer();
void OnAccept();
private:
void PostAccept();
};
}
#endif // !_ILOOPEVENT_H_
#endif // !X_SELECT_NETCORE
IOCPTcpServer.cpp
#include "IOCPTcpServer.h"
#ifdef X_IOCP_NETCORE
#include <ws2tcpip.h>
#include <mswsock.h>
#include "IOCPTcpSocket.h"
namespace X
{
ITcpServer* CreateTcpServer()
{
return new TcpServer();
}
}
bool X::TcpServer::Init(ILoopEvent* loop, FTcpServerCB cb)
{
_loop = (LoopEvent*)loop;
_cb = cb;
std::cout << "初始化 TCPServer " << std::endl;
return true;
}
bool X::TcpServer::Listen(const char* ip, unsigned short port)
{
std::cout << "Listen IP:" << ip << " port:" << port << std::endl;
// TODO 改行代码 以后完善 存在一些问题
if (_sock != INVALID_SOCKET)
closesocket(_sock);
//初始化socket
_sock = INVALID_SOCKET;
_sock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&addr, sizeof(addr)))
return false;
if (SOCKET_ERROR == listen(_sock, 5))
return false;
_event.sock = _sock;
if (_loop->AssioIOCP(_sock, &_event))
{
std::cout << "关联IOCP成功" << __FUNCTION__ << std::endl;
}
else
std::cout << "关联IOCP失败" << __FUNCTION__ << std::endl;
// 投递IOCP监听
PostAccept();
return true;
}
X::TcpServer::TcpServer()
{
_loop = nullptr;
_sock = INVALID_SOCKET;
_event.tcpServer = this;
_event.type = sEvent::Type::E_TCPSERVER;
_event.sock = INVALID_SOCKET;
_handle.eType = sReqHandle::sReqHandleE::HANDLE_ACCEPT;
memset(&_handle.overlapped, 0, sizeof(_handle.overlapped));
}
void X::TcpServer::OnAccept()
{
//SOCKADDR_IN addr;
//int addrLen = sizeof(SOCKADDR_IN);
//SOCKET sock = accept(_sock, (sockaddr*)&addr, &addrLen);
//// 连接成功 调用回调函数
//_cb(nullptr);
sockaddr* paddr1 = NULL;
sockaddr* paddr2 = NULL;
int tmp1 = 0;
int tmp2 = 0;
GetAcceptExSockaddrs(_recvBuf, _recvLen, sizeof(SOCKADDR_IN) + 16
, sizeof(SOCKADDR_IN) + 16, &paddr1, &tmp1, &paddr2, &tmp2);
TcpSocket* tcp = new TcpSocket();
tcp->OnAccept(_socket, paddr2);
_cb(tcp);
PostAccept();
}
void X::TcpServer::PostAccept()
{
_socket = socket(AF_INET, SOCK_STREAM, 0);
//WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (_socket == INVALID_SOCKET)
{
return;
}
_recvLen = 0;
if (FALSE == AcceptEx(_sock, _socket, _recvBuf, 0, sizeof(SOCKADDR_IN) + 16
, sizeof(SOCKADDR_IN) + 16, &_recvLen, &_handle.overlapped))
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
std::cout << "ERROR " << __FUNCTION__ << std::endl;
return;
}
}
std::cout << "OK " << __FUNCTION__ << std::endl;
return;
}
#endif // !X_SELECT_NETCORE
IOCPTcpSocket.h
#include "XNetCore.h"
#include "IOCPLoopEvent.h"
#ifdef X_IOCP_NETCORE
#ifndef X_TCP_SOCKET_H_
#define X_TCP_SOCKET_H_
namespace X {
//记录客户端当前的状态
enum class LINK_STATUS
{
LS_UNINITIALIZE = 0, // 无效状态
LS_ESTABLISHED, // 连接状态
LS_WAITCLOSE, // 等待关闭状态
LS_WAITCLEAR, // 等待清除状态
};
class TcpSocket :public ITcpSocket
{
private:
LoopEvent* _loop;
ITcpSocketCB* _cb;
SOCKET _sock;
sEvent _event;
//发送缓冲区
bool _bSend;
WSABUF _sendWsaBuf;
sReqHandle _sendHandle;
//接收缓冲区
bool _bRecv;
WSABUF _recvWSABuf;
sReqHandle _recvHandle;
//当前状态
LINK_STATUS _elinkStatus;
public:
TcpSocket();
~TcpSocket();
public:
bool OnAccept(SOCKET sock, SOCKADDR* in);
bool OnNetMsg(sReqHandle* handle, BOOL ret, int len);
//关闭客户端
bool OnClose();
//准备关闭客户端
bool DoClose();
public:
// 通过 ITcpSocket 继承
virtual void Init(ILoopEvent* loop, ITcpSocketCB* cb) override;
virtual bool DoSend(const char* buff, int len) override;
virtual bool DoRecv(char const* buff, int len) override;
//主动关闭客户端
virtual bool Close() override;
};
}
#endif // !_ILOOPEVENT_H_
#endif // !X_SELECT_NETCORE
IOCPTcpSocket.cpp
#include "IOCPTcpServer.h"
#ifdef X_IOCP_NETCORE
#include <ws2tcpip.h>
#include <mswsock.h>
#include "IOCPTcpSocket.h"
namespace X
{
TcpSocket::TcpSocket()
{
_cb = nullptr;
_loop = nullptr;
_sock = INVALID_SOCKET;
_bSend = false;
_sendHandle.eType = sReqHandle::sReqHandleE::HANDLE_SEND;
_sendHandle.ptr = this;
memset(&_sendHandle.overlapped, 0, sizeof(_sendHandle.overlapped));
_event.tcpSocket = this;
_event.type = sEvent::Type::E_TCPSOCKET;
_bRecv = false;
_recvHandle.eType = sReqHandle::sReqHandleE::HANDLE_RECV;
_recvHandle.ptr = this;
memset(&_recvHandle.overlapped, 0, sizeof(_recvHandle.overlapped));
_elinkStatus = LINK_STATUS::LS_UNINITIALIZE;
}
TcpSocket::~TcpSocket()
{
//std::cout << "释放客户端" << _sock << std::endl;
}
bool TcpSocket::Close()
{
return true;
}
bool TcpSocket::DoClose()
{
switch (_elinkStatus)
{
case X::LINK_STATUS::LS_UNINITIALIZE:
break;
case X::LINK_STATUS::LS_ESTABLISHED:
{
_elinkStatus = LINK_STATUS::LS_WAITCLOSE;
shutdown(_sock, SD_BOTH);
goto postClear;
break;
}
case X::LINK_STATUS::LS_WAITCLOSE:
goto postClear;
break;
case X::LINK_STATUS::LS_WAITCLEAR:
break;
}
postClear:
_elinkStatus = LINK_STATUS::LS_WAITCLEAR;
if (_loop) //如果有IO服务则投递到IO服务中关闭
{
_loop->PostMsg(sEvent::Type::PCK_SOCKET_CLOSE, this);
}
else //没有服务则直接关闭
{
OnClose();
}
return true;
}
bool TcpSocket::OnClose()
{
closesocket(_sock);
_sock = INVALID_SOCKET;
if (_cb)
_cb->OnClose();
_elinkStatus = LINK_STATUS::LS_UNINITIALIZE;
delete this;
return true;
}
void TcpSocket::Init(ILoopEvent* loop, ITcpSocketCB* cb)
{
_loop = (LoopEvent*)loop;
_cb = cb;
_event.sock = _sock;
_loop->AssioIOCP(_sock, &_event);
}
bool TcpSocket::OnAccept(SOCKET sock, SOCKADDR* in)
{
_sock = sock;
_elinkStatus = LINK_STATUS::LS_ESTABLISHED;
return true;
}
bool TcpSocket::OnNetMsg(sReqHandle* handle, BOOL ret, int len)
{
if (ret)
{
// TODO关闭
}
//if (len <= 0)
//{
// // 断开连接
// return 0;
//}
switch (handle->eType)
{
case sReqHandle::sReqHandleE::HANDLE_RECV:
{
_bRecv = false;
if (len <= 0)
{
// 断开连接
DoClose();
return 0;
}
_cb->OnRecv(len);
break;
}
case sReqHandle::sReqHandleE::HANDLE_SEND:
{
_bSend = false;
_cb->OnSend(len);
break;
}
case sReqHandle::sReqHandleE::HANDLE_CONNECT:
break;
case sReqHandle::sReqHandleE::HANDLE_RECVFROM:
break;
case sReqHandle::sReqHandleE::HANDLE_SENDTO:
break;
default:
break;
}
return true;
}
bool TcpSocket::DoSend(const char* buff, int len)
{
//TODO 判断一些错误
if (_bSend) return false;
_sendWsaBuf.buf = (CHAR*)buff;
_sendWsaBuf.len = len;
DWORD dwSend = 0;
if (WSASend(_sock, &_sendWsaBuf, 1, &dwSend, 0, &_sendHandle.overlapped, NULL) != 0)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
return false;
}
}
_bSend = true;
return false;
}
bool TcpSocket::DoRecv(char const* buf, int len)
{
if (_bRecv) return false;
// TODO判断一些失败条件
_bRecv = true;
_recvWSABuf.buf = (CHAR*)buf;
_recvWSABuf.len = len;
DWORD dwRecv = 0;
DWORD dwFlag = 0;
if (WSARecv(_sock, &_recvWSABuf, 1, &dwRecv, &dwFlag, &_recvHandle.overlapped, NULL) != 0)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
return false;
}
}
return true;
}
}
#endif // !X_SELECT_NETCORE
XServer.cpp
#include <iostream>
#include "XNetCore.h"
namespace {
class TcpSocket : public X::ITcpSocketCB
{
public:
char buff[1024]{ 0 };
char sendBuf[1024];
X::ITcpSocket* iTcpSocket;
public:
// 通过 ITcpSocketCB 继承
virtual bool OnRecv(int len) override
{
std::cout << buff << std::endl;
OnRecv();//重新投递
return true;
}
void OnRecv()
{
iTcpSocket->DoRecv(buff, 1024);
}
void OnSend()
{
for (int i = 0; i < 1024; i++)
sendBuf[i] = 'a';
iTcpSocket->DoSend(sendBuf, 1024);
}
virtual bool OnSend(int len) override
{
return true;
}
virtual void OnClose() override
{
std::cout << "客户端断开(断开回调)" << std::endl;
}
};
}
int main()
{
X::InitNetCore();
X::ILoopEvent* loop = X::CreateLoopEvent();
loop->Init();
X::ITcpServer* server = X::CreateTcpServer();
server->Init(loop, [loop](X::ITcpSocket* sock) {
std::cout << "客户端连接成功1" << std::endl;
TcpSocket* tcp = new TcpSocket();
sock->Init(loop, tcp);
tcp->iTcpSocket = sock;
tcp->OnRecv();
tcp->OnSend();
});
server->Listen("0.0.0.0", 7890);
while (true)
{
loop->LoopOnce();
}
return 0;
}
服务端事件处理顺序示意图
IOCP模型C++服务端-接收客户端数据并响应客户端,同时管理所有客户端句柄_iocp shutdown 返回_ufgnix0802的博客-CSDN博客
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天, 点击查看活动详情