Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing WebRTC Signaling using WebSockets

In order to connect peer to peer for communication using the WebRTC API, you need to implement signaling. Signaling needs a two way communication channel to work. WebSockets are a two way communication channel. I would like to use WebSockets to implement signaling, however, I have no idea how to approach this. Everything I've found online just discusses the different two way channels you can use rather than providing an example on how to use them.

I'm using JavaScript for the client side and Java for the server side. So, how would I implement signaling using WebSockets?

like image 423
chRyNaN Avatar asked Jul 29 '14 16:07

chRyNaN


1 Answers

Here's a way I figured out how to do this. I haven't tested the code yet, so, I'm not sure if it works. Also, I'm skeptical about whether this will be efficient. If you have a better solution, see a problem in my code, or any advice feel free to elaborate. The idea behind this is to have the WebSocket Server Endpoint filter through messages and send them to the appropriate user.

  • First, I am sending messages to clients using a predetermined user id. The WebSocket Server Endpoint is only aware of Session objects which contain session id's (which cannot be changed; no set method). In order to link the session id and user id, I will need a database table (much more reliable than keeping this in memory).

  • Then, the first message sent to the WebSocket Server Endpoint from the client will contain that user's id. This will be mapped to the database along with the session id and the client will be considered "connected". Of course, this happens after actually connecting to the WebSocket.

  • Now, once "connected", we can send (signaling) messages to another user, as long as, we know their user id (which I assume we do). The WebSocket Server Endpoint checks the recipient user id against the database to find their session id. Once found, the message can be sent.

  • Finally, on the client side, we can decipher the message and send the appropriate one back (using the same process above).

So, as stated before, we will need a database table for the clients session and user id's. Since I'm using Java for all the back-end code, we will use ORM (object relational mapping) by creating an Entity class to hold the attributes. Something like this:

    @Entity
    public class WebSocketUser {
        @Id@GeneratedValue
        private long id;
        private long userId;
        private long sessionId;

        //constructors, getters and setters
    }

Now, we can make the Server Endpoint class:

    @ServerEndpoint("/SignalingServerEndpoint")
    public class SignalingServerEndpoint {
        //these class variables will be useful for 
        //access to the database and such
        private EntityManagerFactory emf;
        private EntityManager em;
        private EntityTransaction tx;
        private TypedQuery<WebsocketUser> query;
        private WebsocketUser wsu;

Since, we are not in an EJB, we must control the Entity Manager like in an application managed environment. Add the onOpen and onClose methods to the Websocket:

    @OnOpen
    public void open(Session session, Endpoint config){
        emf = Persistence.createEntityManagerFactory(""); //TODO add persistence unit reference here
        em = emf.createEntityManager();
        tx = em.getTransaction();
    }

    @OnClose
    public void close(Session session, CloseReason reason){
        //if session is closing and information still exists in the database; remove it
        if (!wsu.equals(null)){
            tx.begin();
            em.remove(wsu);
            tx.commit();
        }
        em.close();
        emf.close();
    }

Next, in the onMessage method in the WebSocket Server Endpoint, we filter the messages. I chose to send messages in the JSON format. This allows you to easily decipher the information (I used the org.json library). The onMessage method:

    @OnMessage
    public void message(Session session, String msg){
        try {
            JSONObject obj = new JSONObject(msg);
            if (!obj.has("toUserId")){
                //adding user to the database, so they can access their session ID with their user ID
                WebsocketUser wsu = new WebsocketUser(obj.getLong("userId"), session.getId());
                tx.begin();
                em.persist(wsu);
                tx.commit();
            }else if (obj.has("toUserId")){
                //message to be sent to the user with the specified user ID
                //get session ID from database
                query = em.createQuery("SELECT u FROM WebsocketUser u WHERE u.userId = " + obj.getLong("toUserId"), WebsocketUser.class);
                wsu = (WebsocketUser) query.getSingleResult();
                Set<Session> sessions = session.getOpenSessions();
                for (Iterator<Session> i = sessions.iterator(); i.hasNext();){
                    Session s = i.next();
                    if (s.getId().equals(wsu.getSessionId())){
                        s.getAsyncRemote().sendText(obj.getString("message"));
                        break;
                    }
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

Finally, all we have left is the client side (javascript). Create the WebSocket variable:

    var signalingSocket = new WebSocket("/SignalingServerEndpoint"); //TODO need the full and correct path to the WebSocket

Now, for the method that will send the message to "connect" to the WebSocket Server Endpoint:

    function connect(){
        var msg = {
            "userId": userId
        };
        signalingSocket.send(JSON.stringify(msg));

And, finally, all we have is the onMessage method on the client side (which will decipher the message and possibly send information to the other client) and all the actual signaling code (ICE servers, constraints, etc.). I won't go into all the signaling work but there is a good tutorial here. I hope this helps anyone else faced with a similar problem. As I said, I haven't tested the code so I'm not positive if it will work. Also, I am very skeptical whether this will even be efficient. But it is at least a start.

like image 55
chRyNaN Avatar answered Oct 24 '22 17:10

chRyNaN