使用async_accept处理多个客户端

Handling multiple clients with async_accept

本文关键字:客户端 处理 accept async 使用      更新时间:2023-10-16

我正在用boost ASIO和协程编写一个安全的SSL回声服务器。我希望这个服务器能够服务多个并发客户端,这是我的代码

 try {
    boost::asio::io_service io_service;
    boost::asio::spawn(io_service, [&io_service](boost::asio::yield_context yield) {
      auto ctx = boost::asio::ssl::context{ boost::asio::ssl::context::sslv23 };
      ctx.set_options(
        boost::asio::ssl::context::default_workarounds
        | boost::asio::ssl::context::no_sslv2
        | boost::asio::ssl::context::single_dh_use);
      ctx.use_private_key_file(..); // My data setup
      ctx.use_certificate_chain_file(...); // My data setup
      boost::asio::ip::tcp::acceptor acceptor(io_service,
        boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
      for (;;) {
        boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sock{ io_service, ctx };
        acceptor.async_accept(sock.next_layer(), yield);
        sock.async_handshake(boost::asio::ssl::stream_base::server, yield);
        auto ec = boost::system::error_code{};
        char data_[1024];
        auto nread = sock.async_read_some(boost::asio::buffer(data_, 1024), yield[ec]);
        if (ec == boost::asio::error::eof)
          return; //connection closed cleanly by peer
        else if (ec)
          throw boost::system::system_error(ec); //some other error, is this desirable?
        sock.async_write_some(boost::asio::buffer(data_, nread), yield[ec]);
        if (ec == boost::asio::error::eof)
          return; //connection closed cleanly by peer
        else if (ec)
          throw boost::system::system_error(ec); //some other error
        // Shutdown gracefully
        sock.async_shutdown(yield[ec]);
        if (ec && (ec.category() == boost::asio::error::get_ssl_category())
          && (SSL_R_PROTOCOL_IS_SHUTDOWN == ERR_GET_REASON(ec.value())))
        {
          sock.lowest_layer().close();
        }
      }
    });
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "n";
  }

无论如何,我不确定上面的代码是否可以:理论上调用async_accept将把控制权返回给io_service管理器。

如果一个连接已经被接受,即它已经超过了async_accept线,另一个连接会被接受吗?

要理解你的问题的细节有点困难,因为代码是不完整的(例如,在你的块中有一个return,但不清楚块的部分是什么)。

尽管如此,文档中还是包含了一个使用协程的TCP回显服务器示例。看来你基本上需要添加SSL支持,以适应您的需求。

如果您查看main,它有以下块:

boost::asio::spawn(io_service,
    [&](boost::asio::yield_context yield)
    {
      tcp::acceptor acceptor(io_service,
        tcp::endpoint(tcp::v4(), std::atoi(argv[1])));
      for (;;)
      {
        boost::system::error_code ec;
        tcp::socket socket(io_service);
        acceptor.async_accept(socket, yield[ec]);
        if (!ec) std::make_shared<session>(std::move(socket))->go();
      }
    });

这个循环是无止境的,并且,在每次(成功)调用async_accept之后,处理接受下一个连接(当这个连接和其他连接可能仍然是活动的)。

同样,我不确定你的代码,但它包含退出循环,如
return; //connection closed cleanly by peer

为了说明这一点,这里有两个应用程序。

第一个是Python多处理echo客户端,改编自PMOTW:

import socket
import sys
import multiprocessing
def session(i):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_address = ('localhost', 5000)
    print 'connecting to %s port %s' % server_address
    sock.connect(server_address)
    print 'connected'
    for _ in range(300):
        try:
            # Send data
            message = 'client ' + str(i) + ' message'
            print 'sending "%s"' % message
            sock.sendall(message)
            # Look for the response
            amount_received = 0
            amount_expected = len(message)
            while amount_received < amount_expected:
                data = sock.recv(16)
                amount_received += len(data)
                print 'received "%s"' % data
        except:
            print >>sys.stderr, 'closing socket'
            sock.close()
if __name__ == '__main__':
    pool = multiprocessing.Pool(8)
    pool.map(session, range(8))

细节不是那么重要(尽管它是Python,因此很容易阅读),但关键是它打开了8个进程,每个进程都使用相同的asio echo服务器(见下图)和300条消息。

运行时输出

...
received "client 1 message"
sending "client 1 message"
received "client 2 message"
sending "client 2 message"
received "client 3 message"
received "client 0 message"
sending "client 3 message"
sending "client 0 message"
...

显示回显会话确实是交错的。

现在是回显服务器。我稍微调整了文档中的示例:

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
using boost::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(
            boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                 if(!ec)
                     do_write(length);
            });
    }
    void do_write(std::size_t length) {
        auto self(shared_from_this());
        socket_.async_write_some(
            boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec)
                    do_read();
            });
    }
private:
    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};
class server {
public:
    server(boost::asio::io_service& io_service, short port) :
            acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
            socket_(io_service) {
        do_accept();
    }
private:
    void do_accept() {
        acceptor_.async_accept(
            socket_,
            [this](boost::system::error_code ec) {
                if(!ec)
                    std::make_shared<session>(std::move(socket_))->start();
                do_accept();
            });
    }
    tcp::acceptor acceptor_;
    tcp::socket socket_;
};
int main(int argc, char* argv[]) {
    const int port = 5000;
    try {
        boost::asio::io_service io_service;
        server s{io_service, port};
        io_service.run();
    }
    catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "n";
    }
}

这表明这个服务器确实是交错的。

注意,这是而不是协程版本。虽然我曾经尝试过协程版本,但我无法在我目前的机器上构建它(而且,正如她在下面的评论中指出的那样,你可能无论如何都更喜欢现在这个更主流的版本)。

然而,这不是根本的区别,请原谅你的问题。非协程版本有显式回调,显式地启动新操作,提供下一个回调;协程版本使用了更顺序的范式。在两个版本中,每个调用都返回到asio的控制循环,该循环监视所有当前可以进行的操作。

asio协程文档:

协程允许您创建反映实际程序逻辑的结构。异步操作不拆分函数,因为没有处理程序来定义异步操作完成时应该发生什么。程序可以使用顺序结构,而不是让处理程序相互调用。

并不是说顺序结构使所有的操作都是顺序的——那样就完全不需要asio了。