Связь между приложениями java/spring и c++/qt с веб-сокетами

Я пытаюсь реализовать веб-сокеты, используя spring с java/веб-приложением, чтобы позволить ему обмениваться сообщениями с приложением, написанным на С++, с использованием qt (и библиотеки веб-сокетов из него).

У меня есть в моем приложении java/spring эта конфигурация:

WebScoketConfig.java

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new SocketHandler(), "/name");
    }
}

SocketHandler.java

@Component
public class SocketHandler extends TextWebSocketHandler {
    List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
        Map<String, String> value = new Gson().fromJson(message.getPayload(), Map.class);
        session.sendMessage(new TextMessage("Hello " + value.get("name") + " !"));
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }
}

и я создал очень простой проект qt-creator с функцией main и одним классом MainWindow с двумя объектами: lineEdit, где пользователь вводит сообщение для отправки на сервер, и pushButton, чтобы продолжить отправку данных.

В моем классе MainWindow я реализую этот слот для обработки обмена данными:

void MainWindow::on_pushButton_clicked()
{
    QString message = this->ui->lineEdit->text();
    QWebSocket m_webSocket;
    m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
    m_webSocket.sendTextMessage("Hello " + message + " !");
    m_webSocket.close();
}

Но когда я запускаю оба приложения и пытаюсь отправить сообщение для java/веб-приложения, ничего не происходит. Я почти уверен, что ошибка, которую я совершил, связана с c++/qt, поскольку на стороне java/spring у меня есть код html/javascript, который позволяет мне тестировать обмен сообщениями и работает нормально.

Кто-нибудь может сказать мне, что я делаю неправильно здесь?

обновление: минимальный воспроизводимый пример — java/spring

проект может быть сгенерирован с помощью start.spring.io, только с зависимостью spring-websocket. помимо двух файлов, которые я уже добавил выше, в проекте будет:

ресурсы/статические/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello WebSocket</title>
  <link href="/main.css" rel="stylesheet">
</head>
<body>
  <table>
    <tr>
      <td>
        <button id="connect" type="button" onclick="connect();">Connect</button>
        <button id="disconnect" type="button" disabled="disabled" onclick="disconnect();">Disconnect</button>
      </td>
      <td>
        <label for="name">What is your name?</label>
        <input type="text" id="name" placeholder="Your name here...">
        <button id="send" type="button" onclick="send();">Send</button>
      </td>
    </tr>
  </table>

  <hr>

  <table id="conversation" border="2">
      <thead>
      <tr>
          <th>Greetings</th>
      </tr>
      </thead>
      <tbody id="greetings">
      </tbody>
  </table>

  <script src="/app.js"></script>
</body>
</html>

ресурсы/app.js

вар ВС;

function connect() {
    ws = new WebSocket('ws://localhost:8080/name');

    ws.onmessage = function(text) {
    var tr = document.createElement("tr");
    var td = document.createElement("td");
    td.innerText = text.data;
    tr.appendChild(td);
    document.querySelector("#greetings").appendChild(tr);
    }

  document.querySelector("#connect").setAttribute("disabled", "disabled");
  document.querySelector("#disconnect").removeAttribute("disabled");
  document.querySelector("#conversation").style.display = 'block';
  document.querySelector("#greetings").innerHTML = "";
}

function disconnect() {
    if (ws != null)
        ws.close();

    document.querySelector("#connect").removeAttribute("disabled");
    document.querySelector("#disconnect").setAttribute("disabled", "disabled");
    document.querySelector("#conversation").style.display = 'none';
    document.querySelector("#greetings").innerHTML = "";
}

function send() {
  var name = document.querySelector("#name");
    var data = JSON.stringify({'name': name.value});
  ws.send(data);
}

после сборки с mvn package просто запустите с java -jar target/app.jar.

обновление: минимальный воспроизводимый пример — c++/qt

проект создается с помощью qt-creator, как тип qt-widget. Он создаст проект с 5 файлами: websocket.pro, mainwindow.ui, mainwindow.h, mainwindow.cpp и main.cpp.

Откройте mainwindow.ui и добавьте lineEdit и pushButton с панели инструментов. щелкните правой кнопкой мыши кнопку, выберите Go to slot и выберите clicked(). Добавьте код выше.

Обновление 2

void MainWindow::on_pushButton_clicked()
{
    QString message = ui->lineEdit->text();

    connect(&m_webSocket, &QWebSocket::connected, [this, message](){
        QJsonObject object
        {
            {"name", message}
        };
        QJsonDocument d(object);
        m_webSocket.sendTextMessage(d.toJson().toStdString().c_str());
        m_webSocket.close();
    });

    m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
}

person Kleber Mota    schedule 09.04.2020    source источник
comment
Не уверен, но... я предполагаю, что QWebSocket::sendTextMessage на самом деле не отправляет сообщение немедленно. Скорее он откладывает отправку до тех пор, пока вы не вернете управление циклу обработки событий Qt. В этом случае сообщение может быть потеряно, когда ваш QWebSocket выходит за рамки.   -  person G.M.    schedule 09.04.2020
comment
Как я мог это исправить?   -  person Kleber Mota    schedule 10.04.2020
comment
Трудно ответить без минимального воспроизводимого примера, но попробуйте сделать QWebSocket m_webSocket членом данных класса MainWindow.   -  person G.M.    schedule 10.04.2020
comment
Не знаю, поможет ли что-нибудь, я пытаюсь обновить свой вопрос, чтобы включить этот минимальный воспроизводимый пример, потому что только создание m_websocket члена данных MainWindow не делает эту работу.   -  person Kleber Mota    schedule 10.04.2020
comment
@KleberMota Вы говорите, что ничего не происходит, что вы имеете в виду? Что вы надеетесь получить?   -  person eyllanesc    schedule 19.04.2020
comment
когда я отправляю сообщение в приложении c++/qt, оно должно отображаться в приложении java/spring, точно так же, как если бы я использовал веб-интерфейс, доступный в проекте java/spring.   -  person Kleber Mota    schedule 19.04.2020
comment
какую ошибку вы нашли? в кодах С++ или Java? Здесь оба компилируются без проблем.   -  person Kleber Mota    schedule 19.04.2020


Ответы (1)


Проблема в том, что вы пытаетесь отправить текст, не проверив успешность соединения. Решение состоит в том, чтобы использовать подключенный сигнал в дополнение к тому, чтобы сделать m_webSocket членом класса, как указано в комментариях:

*.h

private:
    Ui::MainWindow *ui;
    QWebSocket m_webSocket;

*.cpp

void MainWindow::on_pushButton_clicked()
{
    QString message = ui->lineEdit->text();

    connect(&m_webSocket, &QWebSocket::connected, [this, message](){
        m_webSocket.sendTextMessage("Hello " + message + " !");
        m_webSocket.close();
    });

    m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
}

Обновлять:

В вашем проекте я заметил следующие ошибки:

  • По какой-то причине, когда я тестировал Google Chrome, мне не удалось подключиться, поэтому я добавил registry.addHandler(new SocketHandler(), "/name").setAllowedOrigins("*"); в конфигурацию.

  • Переменная "session" обрабатывает только отправку данных в сокет, если вы хотите отправить эту информацию во все сокеты (js и qt), вы должны выполнить итерацию.

  • Когда сеанс отключен, не удаляйте его из «сеансов», это может вызвать ошибки. Вы должны удалить сеанс в методе afterConnectionClosed.

  • В вашем коде вы звоните для подключения к серверу в слоте, связанном с подключенным сигналом, что глупо, поскольку этот слот вызывается после подключения, и для этого вы должны сначала вызвать метод open. В любом случае открытие соединения, ожидание установления соединения, отправка сообщения и закрытие соединения не являются хорошей идеей, лучше открыть соединение перед отправкой сообщения и закрыть его при необходимости (например, при закрытии графического интерфейса или пользователь хочет закрыть его, как это делается в js, поскольку отправка информации не мгновенная, а асинхронная).

Полный код находится здесь.

person eyllanesc    schedule 10.04.2020
comment
Хм, интересно, когда я копирую/вставляю ваш код, приложение c++/qt, кажется, достигает приложения java/spring, но я получил эту ошибку: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $. Если я изменю код на добавленный к вопросу (см. update2), ошибка не появится, но в приложение java/spring ничего не будет отправлено. - person Kleber Mota; 10.04.2020
comment
@KleberMota Как странно, у меня это работает. Может между нашими проектами что-то отличается, не могли бы вы выложить оба приложения на GitHub помимо указания версий всех используемых вами библиотек? - person eyllanesc; 19.04.2020
comment
2 проекта: (java) github.com/klebermo/websocket_java, (c++) github.com/klebermo/websocket_cpp - person Kleber Mota; 19.04.2020
comment
@KleberMota Я уже исправил ваш код и создал 2 PR для ваших репозиториев. - person eyllanesc; 19.04.2020