eidheim / Simple-WebSocket-Server

A very simple, fast, multithreaded, platform independent WebSocket (WS) and WebSocket Secure (WSS) server and client library implemented using C++11, Boost.Asio and OpenSSL. Created to be an easy way to make WebSocket endpoints in C++.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

asynchronous onmessage ?

rberlich opened this issue · comments

commented

Hi there,

looking at client_ws.hpp, it is not clear to me whether the "ping" between client and server will go on in the background, while onmessage() does its work (see "read_message_content" -- it appears as if either the ping or the onmessage function run, but not both).

My use-case may involve quite long times without any user-defined interaction between client and server (from minutes to days), while the data obtained from the server is processed within the onmessage function. This is for an application involving distributed parametric optimisation, so data transfers are generally small -- a few kilobytes -- but evaluation times may be quite long.

So, do I need to send my calculation into the background in its own thread and if so, do I need to take care of the synchronization of the final client.send() command issued by this thread ? After all the ping will continue in any case, if I do "expensive" calculations in a thread.

I would also like to understand what action is taken by client and server if the ping does not get an answer for a predefined number of submissions or time. Will this result in an onerror() call?

In any case, thanks for a great library!

Kind Regards,
Beet

Yes, when doing heavy calculations you should create your own thread. It is safe to use client.send() in this thread when the work is done, since synchronisation is already done in the send function through strand::post. In general, the onmessage/onopen/... functions are in most use cases run in 1 thread through asio (if you specify 1 thread when constructing the Server/Client object), and one then does not have to worry about mutexes and locks inside these functions.

Regarding the pings, only answering on pings is implemented. If you need to initiate and send a ping, you would have to implement this as well for instance in a separate thread.

I think I need to add that if you create a thread inside for instance onmessage, make sure you run std::thread::detach() so that asio can run other tasks while the thread is performing its task.

commented

Thanks for the comprehensive answer! I do not understand, though, why I would have to explicitly detach() the thread -- ASIO will not know about it, if my "std::thread m_t" would be a class variable, and my plan was indeed to let a thread-pool (not based on the websocket io_service) handle the processing. This way I could open more than one connection to the server in the presence of multiple cores and let multiple (usually single-threaded) workloads be processed in parallel. Both web-service clients (running in the same binary) could use the same threadpool.

Never mind the comment on detach then, and sorry for the confusion. The important issue is that the onmessage/onopen/.. functions return without any significant delay, so that asio can perform other tasks. If you have your own thread pool doing the processing, and you use send() from that pool, it should not be a problem.

commented

Sorry, I should have made it clearer that I'm wrapping your webservice client into my own class with its own set of onmessage(), onopen() ... functions, which in turn have access to my (mostly thread-safe) class internals. These functions are then registered with your client. onmessage() implements a protocol between client and server, so I can ask for work or lay low for a while, while the server has no work for me.
Thanks again for your anwer!

I'm trying to do the same whereas i'm calling send from a lambda function which i'm passing to a thread launched in onmessage, but it seems to hang.

This only happens if I detach. If I join it works fine.

@Victordmdb Make sure you detach the thread you are creating in onmessage, as onmessage should return as soon as possible. Also make sure you capture the connection object and keep it alive as long as needed.

@eidheim That's the issue, when I detach it will hang once my lambda callback tries to send through the connection (which is still open). I've double checked, the connection object is being captured correctly.

Do you maybe have an example of a threaded call from onmessage?

I've tried to boil down my code. Is this correct?

WsServer server(port, 2);
auto& ep = server.endpoint["^/connect/?$"];

ep.onmessage = [&](std::shared_ptr<WsServer::Connection> connection, std::shared_ptr<WsServer::Message> message) {

  auto callback = [&](std::string data) {
    auto send_stream = std::make_shared<WsServer::SendStream>();
    send_stream  << data;
    server.send( connection, send_stream, [](const boost::system::error_code& ec){
      //It never reaches here
    });
  }

  std::thread th = std::thread{&myProg, std::ref(callback)};
  th.detach();
}

  std::thread server_thread([&server](){
    server.start();
  });

  server_thread.join();

I have not tested this, but I would write it as follows:

WsServer server(port, 2);
auto& ep = server.endpoint["^/connect/?$"];

ep.onmessage = [&server](std::shared_ptr<WsServer::Connection> connection, std::shared_ptr<WsServer::Message> message) {
  std::thread th([&server, connection] {
    // time consuming calculation here
    auto send_stream = std::make_shared<WsServer::SendStream>();
    send_stream  << data;
    server.send( connection, send_stream, [](const boost::system::error_code& ec){
      // do something here
    });
  });
  th.detach();
};

server.start();

@eidheim Alright, but my code requires a callback mechanism as it is itself rather complicated, so does the lambda callback structure above make sense?

Make sure you copy the connection object, right now you pass it as reference. Also place the server_thread, server.start() and server_thread.join() outside of the onmessage function. Also consider the lifetime of the 'callback' object that you pass as reference to 'std::thread'. You can probably not use 'std::ref' on a local variable when leaving scope like you do in your code.

Right, the 'server_thread, server.start() and server_thread.join()' issue was because i badly copied. I managed to fixed the issue by copying connection instead of making a reference, as you said.

Thanks!