使用async_accept处理多个客户端

时间:2016-10-02 09:41:01

标签: c++ boost-asio

我正在编写一个带有增强ASIO和协程的安全SSL echo服务器。我希望这台服务器能够为多个并发客户端提供服务,这是我的代码

 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线,是否会接受另一个连接?

1 个答案:

答案 0 :(得分:1)

您的问题具体细节有点难以理解,因为代码不完整(例如,您的区块中有return,但目前还不清楚是什么阻止部分)。

尽管如此,该文档还包含example of a TCP echo server using coroutines。您似乎基本上需要为其添加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多处理回显客户端,改编自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"
...

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

现在为echo服务器。我稍微调整了example from the docs

#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";
    }
}

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

请注意,不是协程版本。虽然我曾经使用过coroutine版本,但我根本无法在我当前的盒子上进行构建(同样,正如下面评论中的注释,你现在可能更喜欢这个更主流的版本)。 / p>

但是,这不是根本区别,w.r.t。你的问题。非协程版本具有回调,显式地启动提供下一个回调的新操作;协程版使用更顺序的范例。每个调用都返回到两个版本中的asio控制循环,监视所有当前可以继续的操作。

来自asio coroutine docs

  

Coroutines允许您创建一个镜像实际程序逻辑的结构。异步操作不分割函数,因为没有处理程序来定义异步操作完成时应该发生的事情。程序可以使用顺序结构,而不是让处理程序相互调用。

并非顺序结构使所有操作顺序完成 - 这将消除对asio的全部需求。