首先,基于CServiceBase类,派生一个CNetService类
CNetService新增了一个成员变量,用于保存监听端口;增加了一个CreateUser方法,用于业务层创建对应的连接对象,其参数为nOrigin,即来源编号,由业务层约定。
NetService在重写基类函数OnStart时,启动一个服务线程,处理服务的监听事件。
#pragma once
#include "ServiceBase.h"
class CLinkUser;
class CNetService :
public CServiceBase
{
public:
CNetService();
virtual ~CNetService();
public:
virtual void ServerListenThread();
virtual void OnStart();
public:
virtual CLinkUser* CreateUser(int nOrigin);
protected:
int m_nPort;
};
#include "NetService.h"
#include <thread>
#include <utility>
#include "AsioServer.h"
CNetService::CNetService()
: m_nPort(20001)
{
}
CNetService::~CNetService()
{
}
void CNetService::ServerListenThread()
{
asio::io_context io_context;
CAsioServer svr(io_context, m_nPort);
io_context.run();
}
void CNetService::OnStart()
{
std::thread th(&CNetService::ServerListenThread, this);
th.detach();
}
CLinkUser* CNetService::CreateUser(int nOrigin)
{
return nullptr;
}
在ServerListenThread函数中,我们使用了Asio的封装类AsioServer,io_context作为参数,传入到CAsioServer的构造函数,后面执行io_context.run(),启动了服务监听。
下面我们看,AsioServer的实现
#pragma once
#define ASIO_STANDALONE
//https://think-async.com/Asio/
#include "asio.hpp"
#include "TCPSession.h"
#include <iostream>
class CAsioServer
{
public:
CAsioServer(asio::io_context& io_context, int nPort)
: m_acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), nPort))
{
OnAccept();
}
private:
void OnAccept()
{
m_acceptor.async_accept([&](std::error_code ec, asio::ip::tcp::socket socket) {
if (!m_acceptor.is_open())
{
return;
}
if (!ec)
{
// todo something OnAccept by TCPSession
}
OnAccept();
});
}
private:
asio::ip::tcp::acceptor m_acceptor;
};
AsioServer对Asio模块做了简单封装,成员变量m_acceptor在构造函数中接收了传入参数io_context和端口号,服务启动后会监听这个端口号。
svr在构造函数里注册了OnAccept函数,当客户端与服务的TCP连接建立完成后,会触发这个函数,执行acceptor的异步接入,同时启动新的OnAccept函数,等待下一个客户端接入。
此时在m_acceptor.async_accept注册的回调函数中,我们可以建立一个TCPSession,用于管理这个用户和服务端的交互信息。
完善此处的处理逻辑
if (!ec)
{
CTCPSession* pSession = new CTCPSession(socket);
pSession->SetCreateUserFunc([this](int nOrigine)->CLinkUser*{
if(m_CreateUserFunc){
return m_CreateUserFunc(nOrigine);
}
return nullptr;
});
pSession->Start();
}
TCPSession需要在接收客户端消息后,才知道消息发送者的身份,所以,需要将创建LinkUser的函数传递到TCPSession内部,在内部接收到消息后,创建对应的LinkUser。
同样AsioServer本身也不复杂创建,需要由NetService将函数传递进来,因此AsioServer需要增加相应变量和逻辑,完整的AsioServer.h为
#pragma once
#define ASIO_STANDALONE
//https://think-async.com/Asio/
#include "asio.hpp"
#include "TCPSession.h"
#include <iostream>
class CAsioServer
{
public:
CAsioServer(asio::io_context& io_context, int nPort)
: m_acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), nPort))
{
OnAccept();
}
void SetCreateUserFunc(CreateFunc func){m_CreateUserFunc = func;}
private:
void OnAccept()
{
m_acceptor.async_accept([&](std::error_code ec, asio::ip::tcp::socket socket) {
if (!m_acceptor.is_open())
{
return;
}
if (!ec)
{
CTCPSession* pSession = new CTCPSession(socket);
pSession->SetCreateUserFunc([this](int nOrigine)->CLinkUser*{
if(m_CreateUserFunc){
return m_CreateUserFunc(nOrigine);
}
return nullptr;
});
pSession->Start();
}
OnAccept();
});
}
private:
asio::ip::tcp::acceptor m_acceptor;
CreateFunc m_CreateUserFunc;
};
NetService在创建Svr时传入
void CNetService::ServerListenThread()
{
asio::io_context io_context;
CAsioServer svr(io_context, m_nPort);
svr.SetCreateUserFunc([this](int nOrigine)->CLinkUser*{
return CreateUser(nOrigine);
});
io_context.run();
}
至此,服务框架具有了监听端口的能力,接收客户端的TCP连接请求,并创建相应的会话,在下一节中,我们仔细观察TCPSession所做的处理。