Сокет с асинхронного сервера пишет - но не читает - PullRequest
0 голосов
/ 03 февраля 2019

Хорошо, я довольно новичок в работе с сокетами и пытаюсь создать асинхронный сервер с несколькими синхронными клиентами.Проблема в том, что мой сервер не может прочитать какие-либо данные с клиента, а также без ошибок!

Вот мой класс сервера:

#define READ_BUF_SIZE 512

struct Connection {
    boost::asio::ip::tcp::socket socket;
    boost::asio::streambuf read_buffer;
    Connection(boost::asio::io_service & io_service) : socket(io_service), read_buffer() { }
    Connection(boost::asio::io_service & io_service, size_t max_buffer_size) : socket(io_service), read_buffer(max_buffer_size) { }
};

class CServer {
    boost::asio::io_service m_ioservice;
    boost::asio::ip::tcp::acceptor m_acceptor;
    std::list<Connection> m_connections;
    using con_handle_t = std::list<Connection>::iterator;

public:

    CServer() : m_ioservice(), m_acceptor(m_ioservice), m_connections() { }

    void handle_read(con_handle_t con_handle, boost::system::error_code const & err, size_t bytes_transfered) {
        if (bytes_transfered > 0) {
            std::istream is(&con_handle->read_buffer);
            std::string line;
            std::getline(is, line);
            std::cout << "Message Received: " << line << std::endl;
        }

        if (!err) {
            do_async_read(con_handle);
        }
        else {
            std::cerr << "Error on read: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
    }

    void do_async_read(con_handle_t con_handle) {
        auto handler = boost::bind(&CServer::handle_read, this, con_handle, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred);
        boost::asio::async_read(con_handle->socket, con_handle->read_buffer, boost::asio::transfer_exactly(READ_BUF_SIZE), handler);
    }

    void handle_write(con_handle_t con_handle, std::shared_ptr<std::string> msg_buffer, boost::system::error_code const & err) {
        if (!err) {
            std::cout << "Finished sending message\n";
            if (con_handle->socket.is_open()) {
                // Write completed successfully and connection is open
            }
        }
        else {
            std::cerr << "Error on write: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
    }

    void handle_accept(con_handle_t con_handle, boost::system::error_code const & err) {
        if (!err) {
            std::cout << "Connection from: " << con_handle->socket.remote_endpoint().address().to_string() << "\n";
            std::cout << "Sending message\n";
            auto buff = std::make_shared<std::string>("Hello World!\r\n\r\n");
            auto handler = boost::bind(&CServer::handle_write, this, con_handle, buff, boost::asio::placeholders::error);
            boost::asio::async_write(con_handle->socket, boost::asio::buffer(*buff), handler);
            do_async_read(con_handle);
        }
        else {
            std::cerr << "We had an error: " << err.message() << std::endl;
            m_connections.erase(con_handle);
        }
        start_accept();
    }

    void start_accept() {
        auto con_handle = m_connections.emplace(m_connections.begin(), m_ioservice);
        auto handler = boost::bind(&CServer::handle_accept, this, con_handle, boost::asio::placeholders::error);
        m_acceptor.async_accept(con_handle->socket, handler);
    }

    void listen(uint16_t port) {
        auto endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port);
        m_acceptor.open(endpoint.protocol());
        m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        m_acceptor.bind(endpoint);
        m_acceptor.listen();
        start_accept();
    }

    void run() {
        m_ioservice.run();
    }
};

Этот класс сначала создается с помощьюконструктор, а затем вызывается сначала с помощью функции listen, а затем с помощью инструкции run.

Пока "Hello World!"тестовое сообщение отправлено , сервер не получает никакой информации от клиента (handle_read() не вызывается)

Client::Client() : io_context(), resolver(io_context), endpoints(resolver.resolve("localhost", "daytime")), socket(io_context)
{
    try
    {
        boost::asio::connect(socket, endpoints);

        boost::array<unsigned char, PACKET_LENGTH> buf;
        boost::system::error_code error;

        socket.read_some(boost::asio::buffer(buf), error);
        std::cout << "Got message." << std::endl;

        boost::asio::write(socket, boost::asio::buffer("test message"), error);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

Наконец, это мой класс клиента.Он содержит объект io_context, сокет, распознаватель и объект типа tcp::resolver::results_type с именем endpoints.

Запись отладки «Got message» фактически выводится на консоль, поэтому сервер можетвыполнить запись клиенту, в то время как запись «тестового сообщения» фактически никогда не видна на сервере, возможно потому, что он не может ее прочитать.

В чем здесь проблема?Заранее спасибо.

1 Ответ

0 голосов
/ 03 февраля 2019

Проблема здесь

boost::asio::async_read(con_handle->socket, con_handle->read_buffer,
      boost::asio::transfer_exactly(READ_BUF_SIZE), handler);
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ where READ_BUF_SIZE is 512

ваш обработчик вызывается, когда было прочитано ровно 512 байт, или сокет на стороне клиента был разрушен или его операции отправки были отключены socket.shutdown().

Таким образом, точка жизни - это время жизни Client объекта в клиентском коде.Если ваши клиентские коды выглядят так:

int main() {
  Client c; // send data, delete object, socket is destroyed
  return 0;
}

на сервере handle_read вызывается с error_code == boost::asio::error::eof (end of file or stream), что означает, что сокет в клиенте был уничтожен.Но вы можете проверить содержимое streambuf, оно содержит «тестовое сообщение».

Но если код вашего клиента выглядит следующим образом:

int main() {
  Client c;
  // wait here for X seconds
  return 0;
}

на стороне сервера вы не увидите никакого вывода,до истечения X секунд.Вы можете использовать

boost::asio::write(socket, boost::asio::buffer("test message"), error);
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send);  // <---

shutdown делает end_of_file отправляется на сервер, операция чтения прервана, вызывается обработчик для handle_read, вы можете проверить, если код ошибки равенerror::eof, если это так, вы можете распечатать содержимое strambuf. * ​​1027 *

Если вы не знаете, сколько отправлено байтов, вы можете использовать async_read_until() fuction с разделителем, чтобы указать конец сообщения (в этом примере это символ новой строки):

// server side
boost::asio::async_read_until(con_handle->socket, con_handle->read_buffer, "\n", handler);
                                                                           ^^^^ delimiter
// client side
boost::asio::write(socket, boost::asio::buffer("test message\n"), error);
                                                         ^^^^ added delimiter
...