1 什么是no-boost Asio

no-boost Asio是指不依赖于boost的Asio版本,在使用时不需要依赖boost环境,只需要包含Asio的相关头文件即可。

关于no-boost Asio和boost Asio的区别可以查看我的另外一篇文章:C++ – Asio和Boost.Asio的区别 ,本文在这里就不再赘述了。

2 基于Asio实现一个异步TCP服务器

最近需要写一个跨平台的TCP Server,在自己复习了linux/windows的socket之后,才想起基于Asio可以完美实现一个TCP Server。

使用的Asio为最新版本1.28.0,在Asio的官网提供了一个C++11标准的异步TCP服务器示例,其主要代码如下:

//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include "asio.hpp"

using asio::ip::tcp;

class session
  : public std::enable_shared_from_this<session>
{
public:
  session(tcp::socket socket)
    : socket_(std::move(socket))
  {
  }

  void start()
  {
    do_read();
  }

private:
  void do_read()
  {
    auto self(shared_from_this());
    socket_.async_read_some(asio::buffer(data_, max_length),
        [this, self](std::error_code ec, std::size_t length)
        {
          if (!ec)
          {
            do_write(length);
          }
        });
  }

  void do_write(std::size_t length)
  {
    auto self(shared_from_this());
    asio::async_write(socket_, asio::buffer(data_, length),
        [this, self](std::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            do_read();
          }
        });
  }

  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(asio::io_context& io_context, short port)
    : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
  {
    do_accept();
  }

private:
  void do_accept()
  {
    acceptor_.async_accept(
        [this](std::error_code ec, tcp::socket socket)
        {
          if (!ec)
          {
            std::make_shared<session>(std::move(socket))->start();
          }

          do_accept();
        });
  }

  tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    asio::io_context io_context;

    server s(io_context, std::atoi(argv[1]));

    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

其实上述代码也是可以使用的,不过不符合我的要求,我是需要在一个主程序中的一个子线程启动一个TCP Server,所以我就需要将上述代码转封装一下,

修改过后包括3个类:

  • AsioTCPServer类,主要功能与上述示例代码中的server类相似
  • AsioTCPSession类,主要功能与上述示例代码中的session类类似
  • TCPServer类,最终的封装类,声明asio::io_contenxt变量,控制服务器生命周期

AsioTCPServer.h 代码如下

#ifndef _ASIO_TCP_SERVER_H_
#define _ASIO_TCP_SERVER_H_

#include <iostream>
#include "asio.hpp"

#include "asio_tcp_session.h"

using asio::ip::tcp;

class AsioTCPServer
{
public:
    AsioTCPServer() = delete;
    AsioTCPServer(asio::io_context& io_context, short port);
    virtual~AsioTCPServer();

public:
    void Listen();

private:
    void async_accept_one();

private:
    short m_port;
    tcp::acceptor m_acceptor;
};

#endif // !_ASIO_TCP_SERVER_H_

AsioTCPServer.cpp 代码如下

#include "asio_tcp_server.h"

AsioTCPServer::AsioTCPServer(asio::io_context& io_context, short port)
    :m_port(port),
    m_acceptor(io_context, tcp::endpoint(tcp::v4(), m_port))
{

}

AsioTCPServer::~AsioTCPServer()
{
}

void AsioTCPServer::Listen()
{
    async_accept_one();
}

void AsioTCPServer::async_accept_one()
{
    m_acceptor.async_accept(
        [this](std::error_code ec, tcp::socket socket)
        {
            if (!ec)
            {
               //std::cout << "New connection from " << socket.remote_endpoint().address() << std::endl;
               std::make_shared<AsioTCPSession>(std::move(socket))->Start();
            }

            async_accept_one();
        });
}

AsioTCPSession.h 代码如下

#ifndef _ASIO_TCP_SESSION_H_
#define _ASIO_TCP_SESSION_H_

#include <iostream>
#include <memory>

#include "asio.hpp"

using asio::ip::tcp;

class AsioTCPSession : public std::enable_shared_from_this<AsioTCPSession>
{
public:
    AsioTCPSession() = delete;
    AsioTCPSession(tcp::socket socket);
    virtual~AsioTCPSession();

public:
    void Start();

private:
    void do_read();
    void send_message(const std::string& message);

private:
    tcp::socket m_socket;
    enum { max_length = 102400 };
    char m_data[max_length];
};

#endif // !_ASIO_TCP_SESSION_H_

AsioTCPSession.cpp 代码如下

#include "asio_tcp_session.h"

AsioTCPSession::AsioTCPSession(tcp::socket socket)
    :m_socket(std::move(socket))
{
}

AsioTCPSession::~AsioTCPSession()
{
}

void AsioTCPSession::Start()
{
    do_read();
}

void AsioTCPSession::do_read()
{
    auto self(shared_from_this());
    m_socket.async_read_some(asio::buffer(m_data, max_length),
        [this, self](std::error_code ec, std::size_t length)
        {
            if (!ec)
            {          
                std::string buf(m_data, m_data + length);
                std::cout << buf << std::endl;

                send_message("reply message");
            }
            else
            {
                std::cerr << "error: " << ec.message() << std::endl;
            }
        });
}

void AsioTCPSession::send_message(const std::string& message)
{
    auto self(shared_from_this());
    asio::async_write(m_socket, asio::buffer(message.data(), message.length() * sizeof(char)),
        [this, self](std::error_code ec, std::size_t /*length*/)
        {
            if (!ec)
            {
                do_read();
            }
            else
            {
                std::cerr << "error: " << ec.message() << std::endl;
            }
        });
}

TCPServer.h 代码如下

#ifndef _TCP_SERVER_H_
#define _TCP_SERVER_H_

#include <thread>
#include <memory>
#include "asio.hpp"

#include "asio_tcp_server.h"

class TCPServer
{
public:
    TCPServer() = delete;
    TCPServer(short port = 19994);
    virtual~TCPServer();

public:
    void Run();
    void StopRun();

private:
    short m_port;
    asio::io_context m_io_context;
    std::unique_ptr<std::thread> m_ptr_tcp_server_thread;
};

#endif // !_TCP_SERVER_H_

TCPServer.cpp 代码如下

#include "tcp_server.h"

TCPServer::TCPServer(short port)
    : m_ptr_tcp_server_thread(nullptr),
    m_port(port)
{
}

TCPServer::~TCPServer()
{
    if (m_ptr_tcp_server_thread != nullptr)
    {
        if (m_ptr_tcp_server_thread->joinable())
        {
            m_ptr_tcp_server_thread->join();
        }

        m_ptr_tcp_server_thread.reset();
        m_ptr_tcp_server_thread = nullptr;
    }
}

void TCPServer::Run()
{
    if (m_ptr_tcp_server_thread != nullptr)
    {
        if (m_ptr_tcp_server_thread->joinable())
        {
            m_ptr_tcp_server_thread->join();
        }

        m_ptr_tcp_server_thread.reset();
        m_ptr_tcp_server_thread = nullptr;
    }

    m_ptr_tcp_server_thread = std::make_unique<std::thread>([&]() {
        auto ptr_tcp_server = std::make_unique<AsioTCPServer>(m_io_context, m_port);
        if (ptr_tcp_server != nullptr)
        {
            ptr_tcp_server->Listen();
        }
        m_io_context.run();
    });
}

void TCPServer::StopRun()
{
    if (!m_io_context.stopped())
    {
        m_io_context.stop();
    }

    if (m_ptr_tcp_server_thread != nullptr)
    {
        if (m_ptr_tcp_server_thread->joinable())
        {
            m_ptr_tcp_server_thread->join();
        }

        m_ptr_tcp_server_thread.reset();
        m_ptr_tcp_server_thread = nullptr;
    }
}

main.cpp 的示例代码如下

#include <iostream>
#include "asio_tcp_server.h"
#include "tcp_server.h"
#include "conio.h"

int main(int argc, char* argv[])
{
    TCPServer tcp_server(19994);
    tcp_server.Run();

    std::thread thread([&]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(10000));
        std::cout << "time" << std::endl;
        tcp_server.StopRun();
    });
    thread.join();

    return 0;
}

上述代码中,使用TCPServer这个类进行高度封装,在TCPServer中启动一个子线程用于启动AsioTCPServer,然后通过AsioTCPSession接收每一个TCP连接的所发送的消息,也可以使用AsioTCPSession::send_message向该连接回复消息。

测试我是使用的hercules这个软件,这个软件可以模拟TCP Client、TCP Server、UDP等协议,功能还是比较齐全的。

测试的图片如下,服务器接收和发送的消息的功能都是正常的

C++ – 基于no-boost Asio实现一个异步TCP服务器-StubbornHuang Blog

参考链接