C , Poco Socketstream в Websocket для передачи пакетов JSON

#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 , был проведен сравнительный анализ, чтобы показать простую разницу в пропускной способности между процессами, а веб-сокеты предлагают более высокую задержку, чем сокеты, в моем случае это будет включать веб-сокеты.