c++ - Boost ASIO 网络服务器/客户端

Posted

技术标签:

【中文标题】c++ - Boost ASIO 网络服务器/客户端【英文标题】:c++ - Boost ASIO networking server/client 【发布时间】:2017-01-04 23:14:54 【问题描述】:

我为客户端和服务器编写了一个程序。该程序当前执行以下操作:

    服务器侦听连接的端点 客户端连接到服务器 服务器在接受连接时发送消息 客户端收到消息

我正在异步执行此操作。但是,问题是他们只能发送/接收一条消息。之后,他们就终止了。以下是我的代码:

#include <iostream>
#include <vector>
#include<boost/asio.hpp>

std::vector<char> buff(256);

void SendHandler(boost::system::error_code ex)
    std::cout << " do something here" << std::endl;


void ReadHandler(boost::system::error_code ex)
    std::cout << " print the buffer data..." << std::endl;
    std::cout << buff.data() << std::endl;



void Server()
    boost::asio::io_service service;
    using namespace boost::asio::ip;
    tcp::endpoint endpoint(tcp::v4(), 4000);
    tcp::acceptor acceptor(service, endpoint); 
    tcp::socket socket(service);
    std::cout << "[Server] Waiting for connection" << std::endl;


    acceptor.accept(socket);
    std::cout << "[Server] Accepted a connection from client" << std::endl;

    std::string msg = "Message from server";
    socket.async_send(boost::asio::buffer(msg), SendHandler);
    service.run();



void Client()
    boost::asio::io_service service;
    using namespace boost::asio::ip;
    tcp::endpoint endpoint(address::from_string("127.0.0.1"), 4000);
    tcp::socket socket(service);
    std::cout << "[Client] Connecting to server..." << std::endl;
    socket.connect(endpoint);
    std::cout << "[Client] Connection successful" << std::endl;

    socket.async_read_some(boost::asio::buffer(buff), ReadHandler);
    service.run();


int main(int argc, char **argv) 
    if(argc == 1)
        std::cout << "Please specify s for server or c for client" << std::endl;
        return -1;
    
    if(argv[1][0] == 's')
        Server();
    
    else
        Client();
    
    return 0;

我想扩展这个程序,以便:

    服务器可以监听,客户端可以无限期地发送请求。更像是一种单向聊天系统。 服务器能够连接到多个客户端。

async_send()service.run() 置于无限循环中并没有帮助。它只是在客户端一遍又一遍地打印消息,直到客户端终止。

我对@9​​87654324@ 甚至对network programming 还是很陌生。请告诉我应该在我的代码中修改的位置和内容?

【问题讨论】:

【参考方案1】:

从学习一些基础知识开始。该库提供了出色的教程和示例,将引导您了解同步和异步服务器的概念和示例。

http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio.html

从头开始。使用计时器和回调完成基本概念。继续本教程进入网络。在几个小时的过程中,通过编辑教程程序来构建核心想法,以自己尝试想法。

然后继续阅读示例部分。 “聊天”示例非常接近您要构建的内容。此特定示例显示如何通过在读取处理程序中重新发出 async_reads 来打开连接。只要服务线程继续使用分配的 async_accept 任务运行,异步 asio 服务器就会为多个客户端提供服务。

http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/examples/cpp11_examples.html http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp11/chat/chat_server.cpp

【讨论】:

【参考方案2】:

实现一个异步 TCP 服务器

异步 ​​TCP 服务器是满足以下条件的分布式应用程序的一部分:

在客户端-服务器通信模型中充当服务器 通过 TCP 协议与客户端应用程序通信 使用异步 I/O 和控制操作 可以同时处理多个客户端

典型的异步 TCP 服务器按照以下算法工作:

分配一个接受器套接字并将其绑定到特定的 TCP 端口。 启动异步接受操作。 产生一个或多个控制线程并将它们添加到运行 Boost.Asio 事件循环的线程池中。 当异步接受操作完成后,发起一个新的接受下一个连接请求。 启动异步读取操作,读取已连接客户端的请求。 异步读取操作完成后,处理请求并准备响应消息。 发起异步写入操作,向客户端发送响应消息。 当异步写入操作完成后,关闭连接并释放套接字。
//responsible for handling a single client by reading the request message, processing it, and then sending back the response message.
//Each instance of the Service class is intended to handle one connected client
//by reading the request message, processing it, and then sending the response message back.
class Service

public:
    //The class's constructor accepts a shared pointer to an object representing a socket connected to a particular client as an argument
    // and caches this pointer. This socket will be used later to communicate with the client application.
    Service(std::shared_ptr<asio::ip::tcp::socket> sock) : m_sock(sock)

    //This method starts handling the client by initiating the asynchronous reading operation
    //to read the request message from the client specifying the onRequestReceived() method as a callback.
    void StartHandling()

private:
    void onRequestReceived(const boost::system::error_code &ec,
                           std::size_t bytes_transferred)


    void onResponseSent(const boost::system::error_code &ec,
                        std::size_t bytes_transferred)

    // Here we perform the cleanup.
    void onFinish()
    
        delete this;
    

    //To keep things simple,  we implement a dummy service which only emulates the execution of certain operations
    //The request processing emulation consists of performing many increment operations to emulate operations
    //that intensively consume CPU and then putting the thread of control to sleep for some time to emulate I/O operations
    std::string ProcessRequest(asio::streambuf &request)

private:
    std::shared_ptr<asio::ip::tcp::socket> m_sock;
    std::string m_response;
    asio::streambuf m_request;
;

//responsible for accepting the connection requests arriving from clients and instantiating the objects of the Service class,
// which will provide the service to connected clients.
class Acceptor

public:
    //Its constructor accepts a port number on which it will listen for the incoming connection requests as its input argument. 
    Acceptor(asio::io_service &ios, unsigned short port_num) : m_ios(ios),
                                                               //The object of this class contains an instance of the asio::ip::tcp::acceptor class as its member named m_acceptor,
                                                               //which is constructed in the Acceptor class's constructor.
                                                               m_acceptor(m_ios,
                                                                          asio::ip::tcp::endpoint(
                                                                              asio::ip::address_v4::any(),
                                                                              port_num)),
                                                               m_isStopped(false)

    //The Start() method is intended to instruct an object of the Acceptor class to start listening and accepting incoming connection requests.
    void Start()

    // Stop accepting incoming connection requests.
    void Stop()


private:
    void InitAccept()

    void onAccept(const boost::system::error_code &ec,
                  std::shared_ptr<asio::ip::tcp::socket> sock)

private:
    asio::io_service &m_ios;
    //used to asynchronously accept the incoming connection requests.
    asio::ip::tcp::acceptor m_acceptor;
    std::atomic<bool> m_isStopped;
;

//represents the server itself
class Server

public:
    Server()

    // Start the server.
    // Accepts a protocol port number on which the server should listen for the incoming connection requests
    // and the number of threads to add to the pool as input arguments and starts the server
    // Nonblocking Method
    void Start(unsigned short port_num,
               unsigned int thread_pool_size)

    // Stop the server.
    // Blocks the caller thread until the server is stopped and all the threads running the event loop exit.
    void Stop()

private:
    asio::io_service m_ios;
    std::unique_ptr<asio::io_service::work> m_work;
    std::unique_ptr<Acceptor> acc;
    std::vector<std::unique_ptr<std::thread>> m_thread_pool;
;

int main()

    unsigned short port_num = 3333;

    try
    
        //it instantiates an object of the Server class named srv.
        Server srv;

        //before starting the server, the optimal size of the pool is calculated.
        // The general formula often used in parallel applications to find the optimal number of threads is the number of processors the computer has multiplied by 2.
        // We use the std::thread::hardware_concurrency() static method to obtain the number of processors. 
        unsigned int thread_pool_size =
            std::thread::hardware_concurrency() * 2;

        //because this method may fail to do its job returning 0,
        // we fall back to default value represented by the constant DEFAULT_THREAD_POOL_SIZE, which is equal to 2 in our case.
        if (thread_pool_size == 0)
            thread_pool_size = DEFAULT_THREAD_POOL_SIZE;

        srv.Start(port_num, thread_pool_size);

        std::this_thread::sleep_for(std::chrono::seconds(60));

        srv.Stop();
    
    catch (system::system_error &e)
    
        std::cout << "Error occured! Error code = "
                  << e.code() << ". Message: "
                  << e.what();
    

    return 0;

实现一个异步 TCP 客户端

支持异步执行请求和请求取消功能的异步 TCP 客户端应用程序:

来自用户的输入应在单独的线程(用户界面线程)中处理。此线程不应被阻塞很长时间。 用户应该能够向不同的服务器发出多个请求。 用户应该能够在之前发出的请求完成之前发出新的请求。 用户应该能够在之前发出的请求完成之前取消它们。
// Function pointer type that points to the callback
// function which is called when a request is complete.
// Based on the values of the parameters passed to it, it outputs information about the finished request.
typedef void (*Callback)(unsigned int request_id,        // unique identifier of the request is assigned to the request when it was initiated.
                         const std::string &response,    // the response data
                         const system::error_code &ec);  // error information

// data structure whose purpose is to keep the data related to a particular request while it is being executed
struct Session

    Session(asio::io_service &ios,
            const std::string &raw_ip_address,
            unsigned short port_num,
            const std::string &request,
            unsigned int id,
            Callback callback) : m_sock(ios),
                                 m_ep(asio::ip::address::from_string(raw_ip_address),
                                      port_num),
                                 m_request(request),
                                 m_id(id),
                                 m_callback(callback),
                                 m_was_cancelled(false) 

    asio::ip::tcp::socket m_sock; // Socket used for communication
    asio::ip::tcp::endpoint m_ep; // Remote endpoint.
    std::string m_request;        // Request string.

    // streambuf where the response will be stored.
    asio::streambuf m_response_buf;
    std::string m_response; // Response represented as a string.

    // Contains the description of an error if one occurs during
    // the request lifecycle.
    system::error_code m_ec;

    unsigned int m_id; // Unique ID assigned to the request.

    // Pointer to the function to be called when the request
    // completes.
    Callback m_callback;

    bool m_was_cancelled;
    std::mutex m_cancel_guard;
;

// class that provides the asynchronous communication functionality.
class AsyncTCPClient : public boost::noncopyable

public:
    AsyncTCPClient(unsigned char num_of_threads)

    // initiates a request to the server
    void emulateLongComputationOp(
        unsigned int duration_sec,          //represents the request parameter according to the application layer protocol
        const std::string &raw_ip_address,  //specify the server to which the request should be sent.
        unsigned short port_num,            //specify the server to which the request should be sent.
        Callback callback,                  //callback function, which will be called when the request is complete.
        unsigned int request_id)    // unique identifier of the request


    // cancels the previously initiated request designated by the request_id argument
    void cancelRequest(unsigned int request_id) //accepts an identifier of the request to be canceled as an argument.


    // blocks the calling thread until all the currently running requests complete and deinitializes the client.
    void close()


private:
    // method is called whenever the request completes with any result.
    void onRequestComplete(std::shared_ptr<Session> session)

private:
    asio::io_service m_ios;
    std::map<int, std::shared_ptr<Session>> m_active_sessions;
    std::mutex m_active_sessions_guard;
    std::unique_ptr<boost::asio::io_service::work> m_work;
    std::list<std::unique_ptr<std::thread>> m_threads;
;

// a function that will serve as a callback, which we'll pass to the AsyncTCPClient::emulateLongComputationOp() method
// It outputs the result of the request execution and the response message to the standard output stream if the request is completed successfully
void handler(unsigned int request_id,
             const std::string &response,
             const system::error_code &ec)

int main()

    try
    
        AsyncTCPClient client(4);

        // Here we emulate the user's behavior.

        // creates an instance of the AsyncTCPClient class and then calls its emulateLongComputationOp() method to initiate three asynchronous requests
        // User initiates a request with id 1.
        client.emulateLongComputationOp(10, "127.0.0.1", 3333, handler, 1);

        // Decides to exit the application.
        client.close();
    
    catch (system::system_error &e)
    
        std::cout << "Error occured! Error code = " << e.code()
                  << ". Message: " << e.what();

        return e.code().value();
    

    return 0;
;

设置环境

1.安装 CMake

cd ~
wget https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5.tar.gz
tar xf cmake-3.14.5.tar.gz
cd cmake-3.14.5
./bootstrap --parallel=10
make -j4
sudo make -j4 install

2。安装 Boost

cd ~
wget https://boostorg.jfrog.io/artifactory/main/release/1.69.0/source/boost_1_69_0.tar.gz
tar xf boost_1_69_0.tar.gz
cd boost_1_69_0
./bootstrap.sh
./b2 ... cxxflags="-std=c++0x -stdlib=libc++" linkflags="-stdlib=libc++" ...
sudo ./b2 toolset=gcc -j4 install

如何构建

mkdir build
cd build
cmake ..
cmake --build .

如何运行

运行服务器

./bin/server

我们可以检查服务器是否运行

netstat -tulpn | grep LISTEN

tcp        0      0 0.0.0.0:445             0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:3333            0.0.0.0:*               LISTEN      6866/./bin/server   <===============
tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:5433          0.0.0.0:*               LISTEN      -
tcp6       0      0 :::445                  :::*                    LISTEN      -
tcp6       0      0 :::5000                 :::*                    LISTEN      -
tcp6       0      0 :::5001                 :::*                    LISTEN      -
tcp6       0      0 :::139                  :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0 ::1:631                 :::*                    LISTEN      -

运行客户端

./bin/client

Request #1 has completed. Response: Response from server

您可以在Boost Asio C++ Network Programming Client Server找到该项目

【讨论】:

以上是关于c++ - Boost ASIO 网络服务器/客户端的主要内容,如果未能解决你的问题,请参考以下文章

使用 boost-asio 的 C++ 和 Python 程序之间的客户端服务器

使用 C++ 和 Boost::Asio 的 N 方客户端到客户端通信

C++ Boost Asio 简单聊天教程

Boost.Asio c++ 网络编程翻译

boost::asio基本使用

boost:asio网络库初学之echo服务器客户端实现