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

Multiple connections

Victordmdb opened this issue · comments

I'm not sure why I would be getting this issue, but if I have multiple simultaneous connections, onmessage seems to all be directed on to the last connection, and not the connection it originated from.

Is there something wrong with my code?

I have not had or heard of such an issue, so I would suspect the problem might be in your source code. If you do not figure it out, you can post a minimal example where this issue happens.

Sorry, actually the issue seems like it creates a new connection on message. Is that normal behaviour? Here are prints of the pointers

Firefox stream : Streamed opened on connection 0x7f96c5f95ac0, on server 0x7ffd76bfb5f0
Chrome stream : Streamed opened on connection 0x7f96beffcac0, on server 0x7ffd76bfb5f0

Chrome message :Message received on connection 0x7f96c5f957f0, on server 0x7ffd76bfb5f0
Firefox message : Message received on connection 0x7f96bfffe7f0, on server 0x7ffd76bfb5f0

Omg sorry no, the issue was with my code. Due to a shared variable.

No worries, glad you figured it out.

Though this is slightly related to my issue, how would one be able to recognize if a message is coming from the same computer as the onopen, or a previous onmessage? If there is a new connection for every message (is that actually supposed to happen?), it seems difficult to identify a unique source.

Is there some sort of unique identifier for the client?

The connection should not close after a message is sent, the connection should be persistent until a close is called, or an error occurs.

I created a really simple desktop streaming service for a hopeless lecture room at my university where I could not use a projector. The service shows how one can store connections though: https://github.com/eidheim/desktop-stream/blob/master/main.cpp

I realise my confusion was because I was looking at the pointers to the connection, not the value itself. Which seems to be different. I'm running into this issue whereas lambda functions i'm using assigning in the onmessage which have a callback for send, are having their reference values reassigned each time there is a new connection.

That is, my first connection has ID 139765550680544, and the callback will initially send to that connection. But when a second connection appears eg 139765617789328, the callback for BOTH the first and second will use this ID 139765617789328.

Aha! Nope, I've just realised it was my fault again. Sorry!

All my problems simply had to do with figuring out how to handle information associated with each connection, because I had just been using global objects.

Thanks for all the help!

Actually, the error still seems to be appearing. I have creation a class to hold the information for each connection, inside an unordered_map, indexed with '(size_t)connection.get()'. When I receive a message I create a callback lambda expression which I pass to the class, which itself then initialises a thread with at will eventually call the callback.

Earlier I thought my function was reassigning a value to the lambda expression because I was just using one global class instead of creating a class for each connection. But it didnt fix the issue,

The lambda expression captures the connection ptr and server (by ref).

Then the issue as above happens. If I have 1 connection there is no problem. But if I add a second, the callback of the first connection starts using the id of the second connection. Its changing the captured variable. Am I misunderstanding how lambda functions work in threads?

When you create a thread, make sure you capture copies of the connection objects instead of references. The references you are using might only be valid in the scope.

You should maybe read up shared_ptr and memory management in C++. This is especially important when working with threads. Mostly we try to avoid this by running events in a single threaded application, like how UI libraries do it. You then also avoid mutex locks and unlocks. However, in cases where you have heavy work that needs to be done, one would pass this to a thread, but then it is important that the connection shared_ptr object is copied into the thread lambda through capturing.

I'm trying to create unicast video streams for individual connections, hence the need for individual threads for each connection. It's a heavy workload. I believe I'm capturing the variables properly as well. Here's the overall code:

class MyClass {
     public:
        MyClass();
        ~MyClass();
        addCallback(std::function < void (std:string data) > callback);
        launch();
    private:
        std::function < void (std:string data) > callback;
}

MyClass::addCallback(callback){
    callback = callback;
}

MyClass::launch(){
    std::thread t = std::thread{ &MyFunct, callback };
    t.detach();
}

int main(int argc, char* argv[]){
  const int port = 8085;
  auto& stream = server.endpoint["^/stream/?$"];
  std::unordered_map<size_t, std::shared_ptr<MyClass>> MyClassList;

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

     auto callback = [&server, connection, &MyClassList ]( std::string data) {
       printf("\n\nSending through connection id %zu\n\n" (size_t)connection.get());
      //This is overwritten by the last connection, ie when the thread initialised by the 1st connection calls this, the capture information is now that of the 2nd connection (if there are 2 connections)
     }
     MyClassList[(size_t)connection.get()].addCallBack(callback);
     MyClassList[(size_t)connection.get()].launch();
   }

     stream.onopen= [&MyClassList ](std::shared_ptr<WsServer::Connection> connection, std::shared_ptr<WsServer::Message> message) {
          MyClassList.emplace((size_t)connection.get(),  std::make_shared<MyClass>(connection));
     }

}

Would you mind creating a complete minimal example that I can just paste into my ws_examples.cpp file? That is, just fix the above (add the needed includes as well) so that I can look how it looks in my IDE.

Especially callback=callback; looks weird.

I've tried setting it up with the WsClient, but the 2nd doest seem to connect.

#include "server_ws.hpp"
#include "client_ws.hpp"
#include <unordered_map>

using namespace std;

typedef SimpleWeb::SocketServer<SimpleWeb::WS> WsServer;
typedef SimpleWeb::SocketClient<SimpleWeb::WS> WsClient;

int MyFunct ( std::function < void (std::string data) > callback ){
  int loop = 0;
  while(true){
      callback("call loop "+std::to_string(loop ++));
      sleep(1);
  }
}


class MyClass {
     public:
        MyClass();
        ~MyClass();
        void addCallBack(std::function < void (std::string data) > callback);
        void launch();
    private:
        std::function < void (std::string data) > _callback;
};

MyClass::MyClass(){};
MyClass::~MyClass(){};

void MyClass::addCallBack( std::function< void (std::string data) > callback){
    _callback = callback;
}

void MyClass::launch(){
    cout << "Server: Launching Threaded function" << endl;
    std::thread t = std::thread{ &MyFunct, _callback };
    t.detach();
    cout << "Server: Succesfully launched thread" << endl;

}

int main() {
    //WebSocket (WS)-server at port 8085 using 1 thread
    WsServer server(8085, 5);

    auto& stream = server.endpoint["^/stream/?$"];
    std::unordered_map<size_t, std::shared_ptr<MyClass>> MyClassList;

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

      auto message_str=message->string();
      cout << "Server: Message received: \"" << message_str << "\" from " << (size_t)connection.get() << endl;

      auto callback = [ connection ]( std::string data ) {
        cout << "Server Thread received callback request for connection "<< (size_t)connection.get()<< " : "<<data<<std::endl;
        //This is overwritten by the last connection, ie when the thread initialised by the 1st connection calls this, the capture information is now that of the 2nd connection (if there are 2 connections)
      };

      MyClassList[(size_t)connection.get()]->addCallBack(callback);
      MyClassList[(size_t)connection.get()]->launch();
    };

    stream.onopen = [&MyClassList](std::shared_ptr<WsServer::Connection> connection) {
      cout << "Server: Opened stream " << (size_t)connection.get() << endl;
      MyClassList.emplace((size_t)connection.get(),  std::make_shared<MyClass>());
    };


    thread server_thread([&server](){
        //Start WS-server
        server.start();
    });    

this_thread::sleep_for(chrono::seconds(1));

WsClient client1("localhost:8085/stream");
    client1.onopen=[&client1]() {
        cout << "Client1: Opened connection" << endl;

        string message="Hello";
        cout << "Client1: Sending message: \"" << message << "\"" << endl;

        auto send_stream=make_shared<WsClient::SendStream>();
        *send_stream << message;
        client1.send(send_stream);
    };





    WsClient client2("localhost:8085/stream");
    client2.onopen=[&client2]() {
        cout << "Client2: Opened connection" << endl;

        string message="Hello";
        cout << "Client2: Sending message: \"" << message << "\"" << endl;

        auto send_stream=make_shared<WsClient::SendStream>();
        *send_stream << message;
        client2.send(send_stream);
    };

    client1.start();
    this_thread::sleep_for(chrono::seconds(3));
    client2.start();

    server_thread.join();

    return 0;
}

Arg, in this demo it works. It must be to do with my code somewhere then. Frustrating.

Ok it is a deeper issue with my callback. Its because I'm passing it to a C function, and having trouble making it thread safe. I posted the issue here:
http://stackoverflow.com/questions/41198854/using-a-c-class-member-function-as-a-c-callback-function-thread-safe-version