繁体   English   中英

Websockets与c ++ asio库奇怪的行为

[英]Websockets with c++ asio library weird behavior

我使用asio库在C ++中编写了一个基本的客户端 - 服务器应用程序。 客户端将消息从控制台发送到服务器。

如果我在linux或windows上的localhost上运行它,它的效果很好。 但是,当我在我的实际服务器上运行它时,我得到一个奇怪的行为。 每次我发送一条消息,然后在发送包含垃圾或空的另一条消息之后立即发送消息。 这有时会发生,有时则不然。 但大部分时间都是如此。 我尝试使用其他端口。

例如,如果我发送消息1,2和3,这是我在服务器控制台中看到的:

1

我能做错什么?

server.cpp - 几乎与此处看到的代码相同

#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 - 几乎与此处看到的代码相同

#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;
}
std::cin.getline(client_message, 2048);    

获取用户输入的一行 在这种情况下“1”。 这将是礼貌地NULL终止,但不看你不知道用户实际提供了多少数据。

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

将整个2048字节的client_message写入套接字。 因此,在'1'中,一个NULL,以及2046个未知内容的字节。 所有这些都将由服务器读取。

这是如何导致至少一些OP的异常行为:

这些2048字节的数据中的一些在一个数据包中结束。 其余的包裹在另一包中。 服务器读取第一个数据包并对其进行处理。 几毫秒后,第二个数据包到达。 第一个数据包为1并且为空,因此cout打印1并丢弃其余数据包,因为这就是coutchar * 第二个包有上帝知道什么。 cout将尝试以任何其他空终止字符串的方式解释它。 它将打印随机垃圾,直到找到空,奶牛回家,或程序崩溃。

这需要修复。 快速修复:

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

现在只发送用户的输入字符串和null。 考虑使用std::stringstd::getline而不是char数组和iostream::getline

但是因为TCP堆栈可能会将许多消息放入同一个数据包中,所以您需要知道消息何时开始和结束。 你不能指望一个消息一个数据包。

典型的解决方案

  1. read-a-byte read-a-byte read-a-byte-byte-byte,直到到达protpcol定义的终结符。 缓慢而痛苦,但有时是最好的解决方案。 在等待可能尚未到达的终结符时,在std::stringstream缓冲数据包可以缓解这种痛苦。

  2. 我更喜欢在固定大小的数据类型中将消息的长度预先添加到消息中。 接收器读取长度的大小,然后读取长度字节。 假设你发送一个无符号的32位长度字段。 接收器读取32位以获取长度,然后读取消息的长度字节。 通过网络发送二进制数时,请注意接收方中的不同字节序 要避免使用不同的字节序,请确保您的协议指定要使用的字节序。 行业标准是总是发送大端,但这些天你可能遇到的大多数处理器都是小端。 你打电话。

我对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可能有一种更好的方法,而上述内容可能是完全无用的废话。 请咨询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;

异步版本应该有点类似。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM