#c #json #websocket #poco-libraries
Вопрос:
Я работаю над проектом, который довольно сильно зависит от задержки пакетов. Архитектор всей системы хочет использовать протокол Poco ::Net ::Websocket в качестве транспортного уровня между различными узлами. До этого момента мы запускали приложение с ZMQ, но слишком много накладных расходов, и мы не видим нужных нам скоростей. Итак, мне поручено преобразовать часть системы ZMQ в WebSockets.
Я никогда раньше не использовал WebSockets, и у меня возникают проблемы с пониманием того, что происходит. Я знаю, что WebSockets изначально были разработаны как способ ускорить обмен данными между веб-браузером и сервером страниц, уменьшая задержку за счет использования полнодуплексной двунаправленной связи.
Существует множество руководств по javascript, nodejs и даже python, но это более ограничено, когда вы говорите о C , с которым я работаю. Примеры, которые я нашел, больше ориентированы на запросы браузера, что меня не удивляет. Вместо обычной передачи пакетов TCP, что и было бы нашим вариантом использования.
Вот мои мысли и вопросы.
Видя, что я действительно не могу понять, что происходит, я решил сначала начать с конфигурации Poco ::Net :: SocketStream. Я решил сделать это таким образом, исходя из мысли, что Websockets — это не что иное, как сокет TCP, вставленный в структуру протокола более высокого уровня. Я подумал, что переход будет проще, чем пытаться с нуля. Итак, я развернул серверное и клиентское приложение, используя стандартные библиотеки сокетов Poco.
Я использовал приведенный здесь пример в качестве основы. Я просто перенес все в один файл. Я также запустил Poco ::Net::TCPServer, образец можно найти здесь .
Я изменил оба, чтобы управлять тем фактом, что я отправляю пакеты JSON между клиентом и сервером. Я взял фрагменты нашего исходного кода для форматирования пакетов JSON и кодирования их с помощью CBOR. В итоге я сохранил формат zmq :: message_t, который я изначально использовал, поскольку у меня возникли проблемы с получением пакетов на другом конце. Так как это не было основной проблемой, я просто пропустил эту часть на данный момент. Итак, я знаю, что части ZMQ приведенного ниже кода не нужны, небольшая проблема для другого раза.
Вот код клиента для справки.
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPMessage.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/HTTPClientSession.h"
#include <iostream>
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/Net/WebSocket.h"
#include <jsoncons/json.hpp>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/cbor/cbor.hpp>
#include <jsoncons_ext/jsonpath/json_query.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <iostream>
#include <string>
#include <zmq.hpp>
using namespace jsoncons;
using Poco::Net::HTTPClientSession;
using Poco::Net::HTTPMessage;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::WebSocket;
class ClientHandler
{
private:
std::string host;
int port;
// IP endpoint/socket address (consists of host addr and port #)
Poco::Net::SocketAddress socketAddr;
// Interface to a TCP stream socket
// Poco::Net::StreamSocket socket;
Poco::Net::Socket socket;
// Stream for reading from / writing to a socket (accepts a socket)
Poco::Net::SocketStream stream;
public:
ClientHandler(std::string h, int p) : host(h), port(p), socketAddr(h, p), socket(), stream(socket)
{
std::cout << "Host: " << host << "tPort: " << port << std::endl;
}
// Connect to the initialized socket address' hostname and port
bool connected()
{
std::cout << "Creating a connection with ["
<< socketAddr.host().toString()
<< "] through port [" << socketAddr.port() << "] ...";
try
{
// socket.connect(socketAddr);
// std::cout << "Success!" << std::endl;
}
catch (Poco::Exception err)
{
std::cout << std::endl;
std::cout << "Socket connection error: [" << err.displayText() << "]" << std::endl;
return false;
}
return true;
}
// Send a message to the connected server
bool sendMessage()
{
std::cout << std::endl;
const char channel[] = "fool";
std::string channelPort = std::to_string(5577);
try
{
std::string message;
std::cout << "Enter a message to send to the server: ";
std::cin >> message;
json msg = json(json_object_arg, {{"$op", "create_channel"}, {"$svc", "zmq"}, {"channel_name", channel}, {"port", channelPort}});
std::vector<uint8_t> buffer;
jsoncons::cbor::encode_cbor(msg, buffer);
zmq::const_buffer msg_out = zmq::buffer(buffer);
if (message.compare("exit") != 0)
{
std::cout << "Sending the message to the server!nt";
// socket.sendBytes(msg_out.data(), msg_out.size());
return true;
}
else
return false;
}
catch (Poco::Exception err)
{
std::cout << "Data send error: [" << err.displayText() << "]" << std::endl;
return false;
}
}
};
int main(int argc, char **argv)
{
int port = 2001;
std::string hostname = "10.0.12.97";
// Handle the server-client connection and send some JSON
try
{
ClientHandler handler(hostname, port);
if (handler.connected())
while (handler.sendMessage())
;
}
catch (Poco::Exception err)
{
std::cout << "Handler Error -> " << err.displayText() << std::endl;
}
return 0;
}
So, How do I get from this ^ to Websockets?
I have been reading and digging through the documentation for Poco Websockets. At first, I thought I needed to figure out the HTTPClientSession, HTTPRequest, and HTTPResponse objects I saw in so many examples. However, looking at Documentation for Poco Websockets constructors, it appeared all I needed to do was pass it a socket connection. So spent some time trying to figure that out, but after going back to the documentation it appears that it specifies that I need to send it a socket that is already a Websocket.
Creates a WebSocket from another Socket, which must be a WebSocket,
That’s what I get for not actually reading it. So I spent some more time reading through the constructors and it appears that I do in fact have to send at least 3 arguments.
WebSocket
WebSocket(
HTTPClientSession amp; cs,
HTTPRequest amp; request,
HTTPResponse amp; response
);
Creates a client-side WebSocket, using the given HTTPClientSession and HTTPRequest for the initial handshake (HTTP Upgrade request).
So I’m now back to HTTPClientSession, HTTPRequest, and HTTPResponse objects.
I think I understand HTTPClientSession. Here I should provide the Server name and port. So updating the code above should look something like this
...
Poco::Net::HTTPClientSession cs;
public:
ClientHandler(std::string h, int p) : host(h), port(p), cs(h, p)
...
HTTPResponse looks to be a pointer to a response object for the WebSocket to return the HTTPRequest response. Looks to contain a status code, date, reason(not sure what reason is)
The HTTPRequest is a point of interest. I’m lost as to what I need to format this as. There are three arguments for the request.
- Метод — это выглядит не более чем сообщением серверу, что вы что-то запрашиваете или хотите что-то передать.
- HTTP_POST — я бы предположил, что это то, что я хочу?
- HTTP_GET
- URI — для HTTP_GET это будет путь к ресурсу, который я хочу получить. И для Post это будет путь, по которому я хотел бы что-то опубликовать на сервере
- Версия — выглядит понятной, так как это HTTP-версия, которую я хотел бы использовать.
но вот в чем загвоздка, я не хочу публиковать / получать что-либо, связанное с URI, я просто хочу передать пакет JSON и управлять им на другом конце. Я нашел пример кода клиента, который использует эту информацию, но я не понимаю, почему он использует HTTP_GET и причину ссылки /?encode=text . Кажется, я нигде не могу найти подробности об этом. Это приводит меня к заданному значению объекта запроса, не понимаю, для чего нужен вызов «set ()» или как он будет работать в моей ситуации.
С новой информацией я обновил свой конструктор до этого.
...
Poco::Net::WebSocket *ws;
public:
ClientHandler(std::string h, int p) : host(h), port(p)
{
std::cout << "TEST" << std::endl;
// Poco::Net::HTTPClientSession cs(h, p);
Poco::Net::HTTPClientSession cs(host, port);
Poco::Net::HTTPRequest req(HTTPRequest::HTTP_GET, "/?encoding=text", HTTPMessage::HTTP_1_1);
// req.set("origin", "http://www.websocket.org");
Poco::Net::HTTPResponse resp;
ws = new WebSocket(cs, req, resp);
std::cout << "Host: " << host << "tPort: " << port << std::endl;
}
...
Тем не менее, я ввожу конструктор, но никогда не покидаю его. Он зависает, когда я пытаюсь создать WebSocket. Я предполагаю, что ему что-то не нравится. Я попробовал HTTPClientSesseion cs двумя способами. и оставляя req.set() также. Просто зависает.
Может ли кто-нибудь дать мне некоторое представление о том, как я могу запустить Poco ::Net ::Websocket? и некоторая информация, чтобы я мог немного лучше понять, что происходит. Поэтому, когда я попытаюсь использовать серверную часть, у меня будет дополнительная информация, которая поможет.
Комментарии:
1. Как вы сказали, я очень сомневаюсь, что использование Websockets снизит вашу задержку. Обычно UDP может дать вам меньшую задержку, поскольку он не имеет соединения. Я думаю, вам нужно протестировать вашу систему, чтобы убедиться, что вы знаете, где существует проблема. Кроме того, убедитесь, что задержка действительно является узким местом, а не пропускной способностью. Параллельные соединения / потоки также могут создавать серьезные проблемы. Также, возможно, изучите семейство адресов AF_XDP, если ваш HW поддерживает его для обхода сетевого стека, если вам нужно.
2. @CookieButter между zmq и websocket произошло значительное снижение задержки, в итоге я использовал Boost вместо Poco. Было намного больше доступной документации. Если вы посмотрите на эту ссылку, github.com/goldsborough/ipc-bench , был проведен сравнительный анализ, чтобы показать простую разницу в пропускной способности между процессами, а веб-сокеты предлагают более высокую задержку, чем сокеты, в моем случае это будет включать веб-сокеты.