[16 使用C++11开发一个简单的通信程序(Proactor模式)] 16.4 C++11结合asio实现一个简单的服务端程序

需求:服务端监听某个端口,允许多个客户端连接上来,打印客户端发来的数据。

(1)能接收多个客户端。

考虑用一个map来管理socket,每次有新连接时,服务器自动分配一个连接号给这个连接,以方便管理。socket不允许复制,不能直接将socket放到map里,需要外面封装一层。

(2)打印客户端的数据,需要异步读数据。

为简化操作,将socket封装到一个读/写事件处理器中。这时采用同步写,异步读。

读/写事件处理器的实现如下:

#include <array>
#include <functional>
#include <iostream>
using namespace std;

#include <boost/asio.hpp>
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost;

const int MAX_IP_PACK_SIZE = 65536;
const int HEAD_LEN = 4;

class RWHandler
{
public:
    RWHandler(io_service& ios) : m_sock(ios)
    {
    }
    
    ~RWHandler()
    {
    }

    void HandleRead()
    {
        // 异步读
        async_read(m_sock, buffer(m_buff), transfer_at_least(HEAD_LEN),
        [this](const boost::system::error_code& ec, size_t size) {
            if (ec != nullptr) {
                HandleError(ec);
                return;
            }
            // 打印客户端发来的数据
            cout << m_buff.data() + HEAD_LEN << endl;    
            // 循环发起异步读事件
            HandleRead();
        });
    }

    void HandleWrite(char* data, int len) {
        boost::system::error_code ec;
        // 同步写
        write(m_sock, buffer(data, len), ec);
        if (ec != nullptr) {
            HandleError(ec);
        }
    }

    tcp::socket& GetSocket()
    {
        return m_sock;
    }

    void CloseSocket()
    {
        boost::system::error_code ec;
        m_sock.shutdown(tcp::socket::shutdown_send, ec);
        m_sock.close(ec);
    }

    void SetConnId(int connId)
    {
        m_connId = connId;
    }

    int GetConnId() const
    {
        return m_connId;
    }

    void SetCallBackError(std::function<void(int)> f) 
    {
        m_callbackError = f;
    }
    

private:
    void HandlerError(const boost::system::error_code& ec)
    {
        CloseSocket();
        cout << ec.message << endl;
        if (m_callbackError) {
            m_callbackError(m_connId);
        }
    }

private:
    tcp::socket m_sock;
    //固定长度的读缓冲区
    std::array<char, MAX_IP_PACK_SIZE> m_buff;
    int m_connId;
    std::function<void(int)> m_callbackError;
};

异步操作对象m_socket的生命周期没有处理,socket可能在异步回调返回之前已经释放,这时需要通过shared_from_this来保证对象的生命周期。

有了RWHandler后,服务端接受新连接后的读/写操作就交给RWHandler,服务端Server的实现如下:

#include <boost/asio/buffer.hpp>
#include <unordered_map>
#include <numeric>
#include "Message.hpp"
#include "RWHandler.hpp"

const int MaxConnectionNum = 65536;
const int MaxRecvSize = 65536;

class Server
{
public:
    Server(io_service& ios, short port) : m_ios(ios), m_acceptor(ios, tcp::endpoint(tcp::v4(), port), m_cnnIdPool(MaxConnectionNum))
    {
        m_cnnIdPool.resize(MaxConnectionNum);
        // 用顺序递增的值赋值指定范围内的元素
        std::iota(m_cnnIdPool.begin(), m_cnnIdPool.end(), 1);
    }

    ~Server()
    {
    }
    
    void Accept()
    {
        cout << "Start Listening..." << endl;
        std::shared_ptr<RWHandler> handler = CreateHandler();
        m_acceptor.async_accept(handler->GetSocket(), [this, handler](const boost::system::error_code& error)
            if (error) 
            {
                cout << "error: " << error.message() << endl;
                HandleAcpError(handle, error);
                return ;
            }
            m_handlers.insert(std::make_pair(handler->GetConnId(), handler));
            cout << "current connect count:" << m_handlers.size() << endl;
            // 异步读
            handler->HandleRead();
            // 等待下一个连接
            Accept();
        });
    }

private:
    void HandlerAcpError(std::shared_ptr<RWHandler> eventHandler, const boost::system::error_code& error)
    {
        cout << "Error: " << error.message() << endl;
        // 关闭socket,移除读写事件处理器
        eventHandler->CloseSocket();
        StopAccept();
    }

    void StopAccept()
    {
        boost::system::error_code ec;
        m_acceptor.cancel(ec);
        m_acceptor.close(ec);
        m_ios.stop();
    }

    std::shared_ptr<RWHandler> CreateHandler() 
    {
        int connId = m_cnnIdPool.front();
        m_cnnIdPool.pop_front();
        std::shared_ptr<RWHandler> handler = std::make_shared<RWHandler>(m_ios);
        handler->SetConnId(connId);
        handler->SetCallBackError([this](int connId){
            RecyclConnid(connId);
        });
    }

    void RecyclConnid(int connId)
    {
        auto it = m_handlers.find(connId);
        if (it != m_handlers.end()) {
            m_handlers.erase();
        }
        cout << "current connect count:" << m_handlers.size() << endl;
        m_cnnIdPool.push_back(connId);
    }

private:
    io_service& m_ios;
    tcp::acceptor m_acceptor;
    std::unordered_map<int, std::shared_ptr<RWHandler>> m_handlers;
    list<int> m_cnnIdPool;
};

Server会管理所有连接的客户端。

测试程序如下:

void TestServer()
{
    io_service ios;
    Server server(ios, 9900); 
    server.Accept();
    ios.run();
}

循环发起异步读事件会保证io_service::run一直运行。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值