简易版“异步多线程服务器”


1.引用 和复制(不用&做参数) 的区别
(1)第一个:肯定是一个原参数操作,另外一个是对原参数 复制一个副本 去操作(开销问题)
(2)第二个:如果是公共参数,比如io_context,他始终只有一个(与内核相关),只能由&


简介

本文基于c++,重点是来掌握c++的异步概念.也是基于boost的asio库

服务端

对话设计

因为是io复用的服务器设计,我们的服务器只有一个端口和ip地址,只就要产生多个 多个会话区 即是 客户端

以防内存泄漏,本设计采用智能指针来控制对象的释放.

参考资料

using namespace boost::asio::ip;

class session:public std::enable_shared_from_this<session> {
    
    
public:
session(boost::asio::io_context &io_context):socket_(io_context){
    
    };
void start() {
    
    
    auto self(shared_from_this());
    boost::asio::async_read_until(socket_,buffer,'\n',[self](boost::system::error_code ec,std::size_t len) {
    
    
        if(!ec) {
    
    
            std::istream is(&self->buffer);
            std::string line;
            std::getline(is,line);
            std::cout<<"Received: "<<line<<std::endl;
            boost::asio::async_write(self->socket_,boost::asio::buffer("Receive is finished"),[self](boost::system::error_code ec) {
    
    
                if(!ec) {
    
    
                    self->start();
                }
            });
        }
        else
            std::cout<<ec.failed()<<std::endl;
    });
}
private:
    tcp::socket socket_;
    boost::asio::streambuf buffer;
};

这个采用递归一直读写

  • boost::asio::async_read_until(socket, buffer, delimiter, read_handler);
  • socket:
    类型:boost::asio::basic_stream_socket 或其他支持异步读取的套接字类型。
    作用:指定从哪个套接字读取数据。
  • buffer:
    类型:boost::asio::streambuf 或其他动态缓冲区类型。
    作用:存储读取到的数据。async_read_until 会将读取到的数据存储到这个缓冲区中。
  • delimiter:
    类型:char 或 std::string。
    作用:指定读取操作的结束条件。当读取到的数据中包含这个分隔符时,读取操作将停止。例如,如果你指定分隔符为 \n,则读取操作会在遇到换行符时停止。
  • read_handler:
    类型:回调函数或 lambda 表达式。
    作用:在读取操作完成时被调用。回调函数的签名通常为:void read_handler(const boost::system::error_code& ec, std::size_t bytes_transferred);
  • boost::asio::async_write(socket, buffers, write_handler);
  • socket:
    类型:boost::asio::basic_stream_socket 或其他支持异步写入的流类型。
    作用:指定将数据写入到哪个套接字或流中。
  • buffers:
    类型:boost::asio::const_buffer 或 std::vectorboost::asio::const_buffer。
    作用:指定要写入的数据。const_buffer 是一个表示常量缓冲区的类,可以包含指向数据的指针和数据的大小。你可以使用 - - - —boost::asio::buffer 函数来创建 const_buffer 对象。
  • write_handler:
    类型:回调函数或 lambda 表达式。
    作用:在写入操作完成时被调用。回调函数的签名通常为:

服务端

class server {
    
    
public:
    server(boost::asio::io_context &io_context,short port):acceptor_(io_context,tcp::endpoint(tcp::v4(),port)),io_context_(io_context) {
    
    
        std::cout<<"Server is running on port: "<<port<<std::endl;
        start_accept();
    }
    void start_accept() {
    
    
        auto new_session=std::make_shared<session>(io_context_);
        acceptor_.async_accept(new_session->socket(),[this,new_session](boost::system::error_code ec) {
    
    
            if(!ec) {
    
    
                new_session->start();
            }

            start_accept();//采用递归来不断连接
        });

    }
private:
    boost::asio::io_context &io_context_;
    tcp::acceptor acceptor_;
};

acceptor_.async_accept

参数为 socket:
类型:boost::asio::basic_stream_socket 或其他支持异步写入的流类型。(其实是客户端的socekt)
回调函数

io_context为服务器的socket

运行

int main() {
    
    
    try {
    
    
        boost::asio::io_context io_context;
        server s(io_context,8080);
        std::vector<std::thread>threads(5);
        for(int i=0;i<threads.size();i++) {
    
    
            threads[i]=std::thread([&io_context]() {
    
    
                io_context.run();
            });
        }
        for(auto &thread: threads) {
    
    
            thread.join();
        }


    } catch (std::exception ec) {
    
    
        std::cout<<ec.what()<<std::endl;
    }
}

使用了多个进程来运行io_context.run();

客户端

tcp::resolver

在 Boost.Asio 中,boost::asio::ip::tcp::resolver 是一个用于解析主机名和端口名称的类。它将主机名和端口名称转换为一个或多个 tcp::endpoint 对象,这些对象表示可以连接到的网络地址和端口。

tcp::resolver 的作用

tcp::resolver 的主要作用是将主机名和端口名称解析为一个或多个 tcp::endpoint 对象。这个过程通常涉及以下步骤:

  • DNS 查询:将主机名解析为一个或多个 IP 地址。
  • 端口解析:将端口名称解析为一个端口号。
  • 生成 tcp::endpoint:将解析得到的 IP 地址和端口号组合成一个或多个 tcp::endpoint 对象。

方法

  • tcp::resolver::query()

query(const std::string& host, const std::string& service);

  • void async_resolve(const query& q,ResolveHandler handler);

q:一个 tcp::resolver::query 对象,包含主机名和端口名称。
handler:解析完成后的回调函数。回调函数的签名通常为:void handler(const boost::system::error_code& ec, tcp::resolver::results_type endpoints);
返回一个或多个endpoint对象

  • boost::asio::async_connect(socket_, endpoints, [this, self](boost::system::error_code ec, tcp::endpoint)
  • 使用 async_connect 异步连接到解析得到的 tcp::endpoint。
#include <iostream>
#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>

using boost::asio::ip::tcp;

class client : public std::enable_shared_from_this<client>
{
    
    
public:
    client(boost::asio::io_context& io_context)
        : socket_(io_context), resolver_(io_context)
    {
    
    
    }

    void start(const std::string& server, const std::string& port)
    {
    
    
        auto self(shared_from_this());
        tcp::resolver::query query(server, port);
        resolver_.async_resolve(query,
            [this, self](boost::system::error_code ec, tcp::resolver::results_type endpoints)
            {
    
    
                if (!ec)
                {
    
    
                    boost::asio::async_connect(socket_, endpoints,
                        [this, self](boost::system::error_code ec, tcp::endpoint)
                        {
    
    
                            if (!ec)
                            {
    
    
                                do_write();

                            }
                            else
                            {
    
    
                                std::cerr << "Connection failed: " << ec.message() << std::endl;
                            }
                        });
                }
                else
                {
    
    
                    std::cerr << "Resolve failed: " << ec.message() << std::endl;
                }
            });
    }

private:
    void do_write()
    {
    
    
        auto self(shared_from_this());
        std::string message = "Hello, server!\n";
        boost::asio::async_write(socket_, boost::asio::buffer(message),
            [this, self](boost::system::error_code ec, std::size_t /*length*/)
            {
    
    
                if (!ec)
                {
    
    
                    // 继续读取数据
                    do_read();
                }
                else
                {
    
    
                    std::cerr << "Write failed: " << ec.message() << std::endl;
                }
            });
    }

    void do_read()
    {
    
    
        auto self(shared_from_this());
        boost::asio::async_read_until(socket_, buffer_, '\n',
            [this, self](boost::system::error_code ec, std::size_t length)
            {
    
    
                if (!ec)
                {
    
    
                    std::istream is(&buffer_);
                    std::string line;
                    std::getline(is, line);
                    std::cout << "Received from server: " << line << std::endl;

                    // 清空缓冲区
                    buffer_.consume(length);

                    // 继续读取数据
                    do_read();
                }
                else
                {
    
    
                    std::cerr << "Read failed: " << ec.message() << std::endl;
                }
            });
    }

    tcp::socket socket_;
    tcp::resolver resolver_;
    boost::asio::streambuf buffer_;
};

int main()
{
    
    
    try
    {
    
    
        boost::asio::io_context io_context;
        auto c = std::make_shared<client>(io_context);
        c->start("127.0.0.1", "8080");
        io_context.run();
    }
    catch (std::exception& e)
    {
    
    
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}