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等协议,功能还是比较齐全的。
测试的图片如下,服务器接收和发送的消息的功能都是正常的
参考链接
本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:C++ – 基于no-boost Asio实现一个异步TCP服务器
原文链接:https://www.stubbornhuang.com/2730/
发布于:2023年07月19日 13:37:09
修改于:2023年07月19日 13:47:35
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论
50