[英]Websockets with c++ asio library weird behavior
我使用asio
库在C ++中编写了一个基本的客户端 - 服务器应用程序。 客户端将消息从控制台发送到服务器。
如果我在linux或windows上的localhost上运行它,它的效果很好。 但是,当我在我的实际服务器上运行它时,我得到一个奇怪的行为。 每次我发送一条消息,然后在发送包含垃圾或空的另一条消息之后立即发送消息。 这有时会发生,有时则不然。 但大部分时间都是如此。 我尝试使用其他端口。
例如,如果我发送消息1,2和3,这是我在服务器控制台中看到的:
我能做错什么?
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并丢弃其余数据包,因为这就是cout
对char *
。 第二个包有上帝知道什么。 cout
将尝试以任何其他空终止字符串的方式解释它。 它将打印随机垃圾,直到找到空,奶牛回家,或程序崩溃。
这需要修复。 快速修复:
std::cin.getline(client_message, 2048);
size_t len = strlen(client_message)
asio::write(socket, asio::buffer(client_message, len+1))
现在只发送用户的输入字符串和null。 考虑使用std::string
和std::getline
而不是char
数组和iostream::getline
但是因为TCP堆栈可能会将许多消息放入同一个数据包中,所以您需要知道消息何时开始和结束。 你不能指望一个消息一个数据包。
典型的解决方案
read-a-byte read-a-byte read-a-byte-byte-byte,直到到达protpcol定义的终结符。 缓慢而痛苦,但有时是最好的解决方案。 在等待可能尚未到达的终结符时,在std::stringstream
缓冲数据包可以缓解这种痛苦。
我更喜欢在固定大小的数据类型中将消息的长度预先添加到消息中。 接收器读取长度的大小,然后读取长度字节。 假设你发送一个无符号的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.