2016-04-11 4 views
3

Я написал базовое клиент-серверное приложение на C++ с использованием библиотеки asio. Клиент отправляет сообщения с консоли на сервер.Websockets with C++ asio library weird behavior

Если я запускаю его на локальном хосте на Linux или Windows, он отлично работает. Однако, когда я запускаю его на своем реальном сервере, я получаю странное поведение. Каждый раз, когда я отправляю сообщение, сразу после отправки другого сообщения, которое содержит мусор или пусто. Иногда это случается, иногда нет. Но это происходит в большинстве случаев. Я пробовал использовать другой порт.

Например, если я посылаю сообщения 1, 2 и 3 это то, что я вижу в консоли сервера:

1

Что я могу делать неправильно?

server.cpp - почти тот же код, как видно here

#define ASIO_STANDALONE 
#include <iostream> 
#include <asio.hpp> 

using asio::ip::tcp; 

const std::size_t max_length = 2048; 
const unsigned short PORT  = 15562; 


class Session 
    : public std::enable_shared_from_this<Session> 
{ 
public: 
    Session(tcp::socket server_socket) 
     : _session_socket(std::move(server_socket)) 
    { 
    } 

    void start() 
    { 
     do_read(); 
    } 

private: 
    void do_read() 
    { 
     auto self(shared_from_this()); // shared_ptr instance to this 

     // Start an asynchronous read. 
     // This function is used to asynchronously read data from the stream socket. 
     _session_socket.async_read_some(asio::buffer(_data, max_length), 
             [this, self](std::error_code error, std::size_t length) 
             { 
              if (!error) 
              { 
               std::cout << "Data RECEIVED: " << std::endl; 
               std::cout << _data << std::endl; 
               do_write(length); 
              } 
             }); 
    } 

    void do_write(std::size_t length) 
    { 
     auto self(shared_from_this()); // shared_ptr instance to this 

     // Start an asynchronous write. 
     // This function is used to asynchronously write data to the stream socket. 
     strncpy(_data, "Hi, from the server", max_length); 
     asio::async_write(_session_socket, asio::buffer(_data, length), 
          [this, self](std::error_code error, std::size_t /*length*/) 
          { 
           if (!error) 
           { 
            do_read(); 
           } 
          }); 
    } 

    tcp::socket _session_socket; 
    char  _data[max_length]; 
}; 


class server 
{ 
public: 
    server(asio::io_service &io_service, const tcp::endpoint &endpoint) 
     : _server_socket(io_service), 
      _server_acceptor(io_service, endpoint) 
    { 
    } 

    void do_accept() 
    { 
     // Start an asynchronous accept. 
     // This function is used to asynchronously accept a new connection into a socket. 
     _server_acceptor.async_accept(_server_socket, 
             [this](std::error_code error) 
             { 
              // Accept succeeded 
              if (!error) 
              { 
               // Create a session 
               auto session = std::make_shared<Session>(
                std::move(_server_socket)); 
               session->start(); 
              } 

              // Continue to accept more connections 
              do_accept(); 
             }); 
    } 

private: 
    tcp::acceptor _server_acceptor; 
    tcp::socket _server_socket; 
}; 


int main() 
{ 
    try 
    { 
     asio::io_service io_service;     // io_service provides functionality for sockets, connectors, etc 
     tcp::endpoint endpoint(tcp::v4(), PORT); // create an endpoint using a IP='any' and the specified PORT 
     server   server(io_service, endpoint); // create server on PORT 
     server.do_accept(); 
     std::cout << "Server started on port: " << PORT << std::endl; 
     io_service.run(); 
    } 
    catch (std::exception &e) 
    { 
     std::cerr << "Exception: " << e.what() << "\n"; // Print error 
    } 

    return 0; 
} 

client.cpp - почти такой же код, как показано here

#define ASIO_STANDALONE 
#include <iostream> 
#include <asio.hpp> 

using asio::ip::tcp; 


int main(int argc, char *argv[]) 
{ 
    asio::io_service io_service; 
    tcp::socket  socket(io_service); 
    tcp::resolver resolver(io_service); 
    // Connect 
    asio::connect(socket, resolver.resolve({"localhost", "15562"})); 

    for (int i = 0; i < 10; ++i) 
    { 
     std::cout << "Enter message to sent to server:" << std::endl; 
     char client_message[2048]; 
     std::cin.getline(client_message, 2048); 
     // Send message to server 
     asio::write(socket, asio::buffer(client_message, 2048)); 

     char server_message[2048]; 
     // Read message from server 
     asio::read(socket, asio::buffer(server_message, 2048)); 
     std::cout << "Reply is: " << std::endl; 
     std::cout << server_message << std::endl; 
    } 

    return 0; 
} 
+0

Очень возможно, что сервер не получает всего сообщения за один выстрел, а в результате '_data' не завершает нуль. То же самое для 'server_message' в клиенте. – user4581301

+0

@ user4581301 Каков правильный способ обработки этой ситуации в коде? Как я могу узнать, когда получил все сообщение? Я просто распечатываю '_data'. Если у вас есть знания о веб-сайтах, взгляните на мой [другой вопрос] (http://stackoverflow.com/questions/36557865/websockets-using-asio-c-library-for-the-server-and-javascript-as -клиент) тоже, если вы можете – dimitris93

ответ

1
std::cin.getline(client_message, 2048);  

Gets a line of input from the user. В этом случае «1». Это будет вежливо завершено NULL, но, не глядя, вы не представляете, сколько данных было фактически предоставлено пользователем.

asio::write(socket, asio::buffer(client_message, 2048)) 

Записывает все 2048 байт client_message в гнездо. Таким образом, в идет '1', NULL и еще 2046 байтов неизвестного содержимого. Все это будет прочитано сервером.

Как это вызывает, по крайней мере некоторые из девиантного поведения ФП в:

Некоторые из того, что 2048 байт данных наматывать в одном пакете. Остальное завершается в другом пакете. Сервер считывает первый пакет и обрабатывает его. Через несколько миллисекунд поступит второй пакет. Первый пакет как 1 и нуль в нем, поэтому cout печатает 1 и отбрасывает остальное, потому что это то, что cout делает с char *. Второй пакет имеет бог-знает - что в нем. cout будет пытаться интерпретировать его так, как если бы он имел любую нулевую завершающую строку. Он будет печатать случайный мусор, пока не найдет нуль, корова придет домой или программа выйдет из строя.

Это необходимо исправить. Быстрое исправление взлома:

std::cin.getline(client_message, 2048);  
size_t len = strlen(client_message) 
asio::write(socket, asio::buffer(client_message, len+1)) 

Теперь будет отправлена ​​только строка ввода пользователя и нуль. Рассмотрите возможность использования std::string и std::getline вместо char массива и iostream::getline

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

Типичные растворы

  1. чтения-а-байт для чтения а-байт для чтения а-байт байт байт до тех пор, protpcol определенный терминатор не будет достигнута. Медленное и болезненное, но иногда лучшее решение. Буферизация пакетов в std::stringstream в ожидании терминатора, который, возможно, еще не прибыл, может облегчить эту боль.

  2. Я предпочитаю добавлять длину сообщения к сообщению в виде данных фиксированного размера. Приемник считывает размер длины, затем считывает длину байтов. Скажем, вы отправляете неподписанное 32-битное поле длины. Приемник считывает 32 бита для получения длины, затем считывает длину байта для сообщения. При отправке двоичных чисел по сети watch out for different endian among receivers. Чтобы избежать различий в настройках, убедитесь, что ваш протокол указывает, какой endian использовать. Промышленный стандарт заключается в том, чтобы всегда отправлять большие цифры, но большинство процессоров, с которыми вы, вероятно, столкнетесь в наши дни, маловероятны. Вы делаете звонок.

Я нечеткий по специфике asio :: buffer. Вы хотите получить длину (как uint32_t) и сообщение (как std::string) в выходной поток. Это может быть столь же просто, как

std::getline(cin, client_message);  
uint32_t len = client_message.length(); 
asio::write(socket, asio::buffer(len, sizeof(len))) 
asio::write(socket, asio::buffer(client_message.c_str(), len+1)) 

Там может быть лучше встраивается в ASIO, и выше, может быть полным craptastic нонсенс. Проконсультируйтесь с экспертом asio о том, как оптимизировать это.

Приемник считывает сообщение что-то вроде:

uint32_t len; 
asio::read(socket, asio::buffer(len, sizeof(len))); 
asio::read(socket, asio::buffer(server_message, len)); 
std::cout << "Reply is: " << std::endl; 
std::cout << server_message << std::endl; 

версия асинхронный должна быть несколько похожа.

+0

отличный ответ, спасибо большое – dimitris93