A 40 sec delay of SIP call initiation using JSSIP / WebRTC - javascript

I am developing a JavaScript-based web SIP client communicating with Asterisk SIP server.
The SIP client is using JSSIP 3.4.2, I'm testing on Chrome version 80.
Both SIP client and SIP server are behind firewalls. I'm using STUN server stun.l.google.com:19302.
The call is established well, but there's a 40 sec delay between calling the "call" method and establishing a call (starting an RTP session).
Here's the code of SIP UA registration:
// SIP UA registration
var currentUserSipAccount = {
uri: '211',
pwd: 'secret'
};
var sipDomain = 'sip.my-domain.com';
var sipServerUrl = 'wss://' + sipDomain + ':8089/ws';
var socket = new JsSIP.WebSocketInterface(sipServerUrl);
var connectionParams = {};
connectionParams.sockets = [socket];
connectionParams.register = true;
connectionParams.uri = 'sip:' + currentUserSipAccount.uri + '#' + sipDomain;
connectionParams.password = currentUserSipAccount.pwd;
var bwPhone = new JsSIP.UA(connectionParams);
Here's the code of call initiation:
// SIP call
var callNumber = 'sip:233#' + sipDomain;
var callOptions = {
mediaConstraints: {
audio: true, // only audio calls
video: false
},
pcConfig: {
iceServers: [
{'urls': ['stun:stun.l.google.com:19302']}
]
}
};
bwPhone.call(callNumber, callOptions);
I have setup logging of each SIP event and found that the delay is related to the onicegatheringstatechange and onicecandidate events.
Here's the Wireshark log:
Each 10 sec, a STUN request is being sent, followed by an instant response. This happens 4 times.
Here is the browser console log I am getting:
The computer on which I'm doing a call has multiple network interfaces. I see icecandidate events containing two IP addresses, one of them (169.254.128.100) is related to Ethernet and not used, another one (192.168.1.33) is related to WiFi and is used for connecting to Internet.
I also see in the browser console log, that the STUN response is being received within several milliseconds after initiating the call. But after that, JSSIP waits for 40 seconds!
How to avoid this 40 sec delay?

Gathering candidates can be very long, and usually, when the delay is large, the last ice candidate will failed to be found.
To solve your delay, your can control the timeout and abort when you decide. This is an example for a timeout of 5 seconds with jssip:
var myCandidateTimeout = null;
_session.on('icecandidate', function(candidate, ready) {
console.log('getting a candidate' + candidate.candidate.candidate);
if (myCandidateTimeout!=null)
clearTimeout(myCandidateTimeout);
// 5 seconds timeout after the last icecandidate received!
myCandidateTimeout = setTimeout(candidate.ready, 5000);
}

Got some hints in the JSSIP group. To stop gathering for ICE candidates and continue SIP flow, I need to call event.ready() inside the icecandidate event handler.
This code resolved the issue (not sure what is srflx, maybe that's not necessary):
session.on("icecandidate", function (event) {
if (event.candidate.type === "srflx" &&
event.candidate.relatedAddress !== null &&
event.candidate.relatedPort !== null) {
event.ready();
}
});

If you do not plan to create a conference call, then you can do this. (works for me)
session.on("icecandidate", function (event) {
event.ready();
});

Related

WebSockets in Chrome and Firefox Disconnecting After One Minute of Inactivity

I have found that WebSockets in Chrome and Firefox disconnect after exactly one minute of inactivity. Based on stuff I've seen online, I was all set to blame proxies or some server settings or something, but this does not happen in IE or Edge. It seems like if sockets are disconnected by the server after one minute of inactivity that would apply to IE and Edge just as much as Chrome and Firefox.
Does anyone know why this is? Is it documented anywhere? I know a possible way to stop it by pinging, but I'm more interested in why it's happening. The reason code given on disconnect is 1006, indicating that the browser closed the connection. No errors are thrown and the onerror event for the socket is not triggered.
This project was built at https://glitch.com/edit/#!/noiseless-helmet where you can see and run everything. The client page is served here: https://noiseless-helmet.glitch.me/
Here is my client page:
<div id="div">
</div>
<script>
let socket = new WebSocket("wss://noiseless-helmet.glitch.me/");
socket.onmessage = function(event) {
div.innerHTML += "<br>message " + new Date().toLocaleString() + " " + event.data;
};
socket.onopen = function (event) {
div.innerHTML += "<br>opened " + new Date().toLocaleString();
socket.send("Hey socket! " + new Date().toLocaleString());
};
socket.onclose = function(event) {
div.innerHTML += "<br>socket closed " + new Date().toLocaleString();
div.innerHTML += "<br>code: " + event.code;
div.innerHTML += "<br>reason: " + event.reason;
div.innerHTML += "<br>clean: " + event.wasClean;
};
socket.onerror = function(event) {
div.innerHTML += "<br>error: " + event.error;
};
</script>
And here is my Node.js server code:
var express = require('express');
var app = express();
app.use(express.static('public'));
let server = require('http').createServer(),
WebSocketServer = require('ws').Server,
wss = new WebSocketServer({ server: server });
app.get("/", function (request, response) {
response.sendFile(__dirname + '/views/index.html');
});
let webSockets = [];
wss.on('connection', function connection(socket) {
webSockets.push(socket);
webSockets.forEach((w) => { w.send("A new socket connected"); });
socket.on('close', (code, reason) => {
console.log('closing socket');
console.log(code);
console.log(reason);
let i = webSockets.indexOf(socket);
webSockets.splice(i, 1);
});
});
server.on('request', app);
server.listen(process.env.PORT, function () {
console.log('Your app is listening on port ' + server.address().port);
});
It seems like if sockets are disconnected by the server after one minute of inactivity that would apply to IE and Edge just as much as Chrome and Firefox.
Hmmm, no, it doesn't. IE and Edge might be implementing a ping packet as part of the WebSocket protocol.
The WebSocket protocol includes support for a protocol level ping that the JavaScript API doesn't expose. It's a bit lower-level than the user level pinging that is often implemented.
This ping-pong traffic resets the timers in any network intermediaries (proxies, load balancers, etc') - and they all time connections to mark stale connections for closure (for example, the Heroku setup times connections at 55 seconds).
Most browsers trust the server to implement the ping, which is polite (since servers need to manage their load and their timeout for pinging...
...however it's also slightly frustrating, since browsers have no idea if a connection was abnormally lost and JavaScript doesn't emit an event for the WebSocket protocol ping. This is why many JavaScript clients implement a user level ping (i.e., a JSON {event: "ping", data: {...}} or another "empty" event message).
Anyway, I just wanted to point out that your assumption was incorrect, this is still a timeout occurring and the difference in browser behavior is probably related to the browsers themselves.
For a few specifics regarding nginx default timeouts (when proxying WebSocket connections) you can read #Hendry's answer.
As much as i understood from researching this, this is caused by websocket timing out over a period of time when no data is sent. This is probably per browser.
You could use pings to resolve this or just reconnect when you need to use the socket again.
It makes sense to not keep sockets open when they are not used from server side as from browser side. For example, Chrome has a limit how many connections can be open, if the limit would be 64 connections and you have open 64 tabs (which is very likely for me as i always have loads of tabs open) and each tab is connected to a server, no more connections could be done (Actually similar thing happened to me once, when i ran out of available sockets in Chrome, funny).
There is proxy_read_timeout (http://nginx.org/r/proxy_read_timeout)
which as well applies to WebSocket connections. You have to bump
it if your backend do not send anything for a long time.
Alternatively, you may configure your backend to send websocket
ping frames periodically to reset the timeout (and check if the
connection is still alive).
https://forum.nginx.org/read.php?2,236382,236383#msg-236383
Web Sockets have an idle timeout of 60 seconds: if you do not use a heartbeat or similar via ping and pong frames then the socket assumes that the user has closed the page and closes the socket to save resources.
https://www.codeproject.com/Questions/1205863/Websocket-is-closed-after-min
https://github.com/tornadoweb/tornado/issues/1070
The WebSocket protocol specification defines Ping and Pong frames that can be used for keep-alive, heart-beats, network status probing. Ping means client/server is sending an iq to tell the other side server/client that to keep the connection alive and also the other side will send an acknowledgement with pong having same payload data.
You can also define a timeout when the browser stops respond or be considered dead.
read more: http://vunse.blogspot.in/2014/04/websocket-ping-pong.html
Maybe not a clean solution but this is how I implemented websocket in JS to automatically reconnect when disconnected
var socket_main
const mainSocketMessageListener = (event) => {
//retreive the data here
console.log(event.data)
}
const mainSocketOpenListener = (event) => {
console.log("Websocket opened")
//Example of sending message to websocket here
socket_main.send(JSON.stringify({
event: "subscribe",
data: ["all"]
}))
}
const mainSocketCloseListener = (event) => {
if (socket_main) {
console.error('Websocket disconnected.')
}
socket_main = new WebSocket('wss://ws.example.com')
socket_main.addEventListener('open', mainSocketOpenListener)
socket_main.addEventListener('message', mainSocketMessageListener)
socket_main.addEventListener('close', mainSocketCloseListener)
}
//connect the first time
mainSocketCloseListener()
I think the issue is related to policy changes on chrome and other browsers.
Please see discussions at
"Chrome terminates WebSocket connection": https://github.com/SignalR/SignalR/issues/4536
"How to detect when browser throttles timers and websockets disconnection": How to detect when browser throttles timers and websockets disconnection after a user leaves a tab or turns off the screen? (javascript)

How to make XMLHttpRequest reuse a TCP connection?

I have Apache running on Windows 7 Pro. I installed Apache using XAMPP bundle.
I need to call an API once every second. To do that, I created a SharedWorker who calls the API every second using XMLHttpRequest
However, I am running into an issue where the web server Apache hits it's max TCP connection allowed. Of course I can increase the allowed TCP connections on the server, but that does not solve my problem it only patches it until the server gets busy and overloaded.
After troubleshooting the TCP problem, it become clear to me that the XMLHttpRequest is not reusing an existing TCP connection. It opens a new TCP connection with every request/every second. I expect to have 1 TCP connection to be utilized to handle my XMLHttpRequest connection.
While I was the only user is connection to the website on the server, I launched TCPView on the web server to watch the tcp connections. I started out with 30 TCP connection with a state of TIME_WAIT. Every second later, one more connection was created until it reached about 122-130 and then it stopped. Now the server seems to be recycling the connections every 60 seconds, but still generating a new TCP connection for every XMLHttpRequest every second.
I also understand that each time a page is loaded the client/browser could create multiple TCP connection for various of reasons. But, I am expecting to have 1 TCP connection to handle my XMLHttpRequest and resuse that connection over and over.
I know some may suggest using WebSockets or Server-Sent-Events, but in my case I can't. I must keep my ShardWorker implementation.
Question
What can I do to make the XMLHttpRequest reuse of an open TCP connection?
Below is my ShardWorker implementation i.e. worker.js
var clients = new Array();
var xhr = null;
//runs only when a new connection starts
onconnect = function(event) {
var port = event.ports[0];
clients.push(port);
port.start();
//implement a channel for a communication between the connecter and the SharedWorker
port.addEventListener("message",
function(event) {
replyToClientMessage(event, port);
} , false
);
}
//reply to any message sent to the SharedWorker with the same message but add the phrase "SharedWorker Said: " to it
replyToClientMessage = function (event, port) {
port.postMessage("Worker Replied With: " + event.data);
}
//check all open clients and post a message to each
function notifyAllPorts(msg){
var len = clients.length;
var port;
for(i = 0; i < len; i++) {
port = clients[i];
port.postMessage(msg);
}
}
function checkQueue(cb) {
//create a new XMLHttpRequest only once
if (xhr == null) {
xhr = new XMLHttpRequest();
xhr.addEventListener("loadend", cb);
xhr.addEventListener("load", handlerMessageReceived);
}
xhr.open('GET', '/add-ons/icws/push.php', true);
xhr.send();
}
//handler a sucessfull request
function handlerMessageReceived()
{
var queue = JSON.parse(this.responseText);
notifyAllPorts(queue);
}
var promise = Promise.resolve(true);
setInterval(function () {
promise = promise.then(function () {
return new Promise(function (resolve) {
checkQueue(resolve);
});
});
}, 1000);
Here is how I put Sharedworker to work
//establish connection to the shared worker
var worker = new SharedWorker("/add-ons/icws/js/worker.js" );
//listen for a message send from the worker
worker.port.addEventListener("message",
function(event) {
console.log(event.data);
processServerData(event.data);
}
, false
);
worker.onerror = function(event){
console.log(event);
};
//start the connection to the shared worker
worker.port.start();

Can't close server (nodeJS)

Why I can't close the server by requesting localhost:13777/close in browser (it continues to accept new requests), but it will gracefully close on timeout 15000? Node version is 0.10.18. I fell into this problem, trying to use code example from docs on exceptions handling by domains (it was giving me 'Not running' error every time I secondly tried to request error page) and finally came to this code.
var server
server = require("http").createServer(function(req,res){
if(req.url == "/close")
{
console.log("Closing server (no timeout)")
setTimeout(function(){
console.log("I'm the timeout")
}, 5000);
server.close(function(){
console.log("Server closed (no timeout)")
})
res.end('closed');
}
else
{
res.end('ok');
}
});
server.listen(13777,function(){console.log("Server listening")});
setTimeout(function(){
console.log("Closing server (timeout 15000)")
server.close(function(){console.log("Server closed (timeout 15000)")})
}, 15000);
The server is still waiting on requests from the client. The client is utilizing HTTP keep-alive.
I think you will find that while the existing client can make new requests (as the connection is already established), other clients won't be able to.
Nodejs doesn't implement a complex service layer on top of http.Server. By calling server.close() you are instructing the server to no longer accept any "new" connections. When a HTTP Connection:keep-alive is issued the server will keep the socket open until the client terminates or the timeout is reached. Additional clients will not be able to issue requests
The timeout can be changed using server.setTimeout() https://nodejs.org/api/http.html#http_server_settimeout_msecs_callback
Remember if a client has created a connection before the close event that connection can continually be used.
It seems that a lot of people do not like this current functionality but this issue has been open for quite a while:
https://github.com/nodejs/node/issues/2642
As the other answers point out, connections may persist indefinitely and the call to server.close() will not truly terminate the server if any such connections exist.
We can write a simple wrapper function which attaches a destroy method to a given server that terminates all connections, and closes the server (thereby ensuring that the server ends nearly immediately!)
Given code like this:
let server = http.createServer((req, res) => {
// ...
});
later(() => server.close()); // Fails to reliably close the server!
We can define destroyableServer and use the following:
let destroyableServer = server => {
// Track all connections so that we can end them if we want to destroy `server`
let sockets = new Set();
server.on('connection', socket => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket)); // Stop tracking closed sockets
});
server.destroy = () => {
for (let socket of sockets) socket.destroy();
sockets.clear();
return new Promise((rsv, rjc) => server.close(err => err ? rjc(err) : rsv()));
};
return server;
};
let server = destroyableServer(http.createServer((req, res) => {
// ...
}));
later(() => server.destroy()); // Reliably closes the server almost immediately!
Note the overhead of entering every unique socket object into a Set

How to create data channel in WebRTC peer connection?

I'm trying to learn how to create an RTCPeerConnection so that I can use the DataChannel API. Here's what I have tried from what I understood:
var client = new mozRTCPeerConnection;
var server = new mozRTCPeerConnection;
client.createOffer(function (description) {
client.setLocalDescription(description);
server.setRemoteDescription(description);
server.createAnswer(function (description) {
server.setLocalDescription(description);
client.setRemoteDescription(description);
var clientChannel = client.createDataChannel("chat");
var serverChannel = server.createDataChannel("chat");
clientChannel.onmessage = serverChannel.onmessage = onmessage;
clientChannel.send("Hello Server!");
serverChannel.send("Hello Client!");
function onmessage(event) {
alert(event.data);
}
});
});
I'm not sure what's going wrong, but I'm assuming that the connection is never established because no messages are being displayed.
Where do I learn more about this? I've already read the Getting Started with WebRTC - HTML5 Rocks tutorial.
I finally got it to work after sifting through a lot of articles: http://jsfiddle.net/LcQzV/
First we create the peer connections:
var media = {};
media.fake = media.audio = true;
var client = new mozRTCPeerConnection;
var server = new mozRTCPeerConnection;
When the client connects to the server it must open a data channel:
client.onconnection = function () {
var channel = client.createDataChannel("chat", {});
channel.onmessage = function (event) {
alert("Server: " + event.data);
};
channel.onopen = function () {
channel.send("Hello Server!");
};
};
When the client creates a data channel the server may respond:
server.ondatachannel = function (channel) {
channel.onmessage = function (event) {
alert("Client: " + event.data);
};
channel.onopen = function () {
channel.send("Hello Client!");
};
};
We need to add a fake audio stream to the client and the server to establish a connection:
navigator.mozGetUserMedia(media, callback, errback);
function callback(fakeAudio) {
server.addStream(fakeAudio);
client.addStream(fakeAudio);
client.createOffer(offer);
}
function errback(error) {
alert(error);
}
The client creates an offer:
function offer(description) {
client.setLocalDescription(description, function () {
server.setRemoteDescription(description, function () {
server.createAnswer(answer);
});
});
}
The server accepts the offer and establishes a connection:
function answer(description) {
server.setLocalDescription(description, function () {
client.setRemoteDescription(description, function () {
var port1 = Date.now();
var port2 = port1 + 1;
client.connectDataConnection(port1, port2);
server.connectDataConnection(port2, port1);
});
});
}
Phew. That took a while to understand.
I've posted a gist that shows setting up a data connection, compatible with both Chrome and Firefox.
The main difference is that where in FF you have to wait until the connection is set up, in Chrome it's just the opposite: it seems you need to create the data connection before any offers are sent back/forth:
var pc1 = new RTCPeerConnection(cfg, con);
if (!pc1.connectDataConnection) setupDC1(); // Chrome...Firefox defers per other answer
The other difference is that Chrome passes an event object to .ondatachannel whereas FF passes just a raw channel:
pc2.ondatachannel = function (e) {
var datachannel = e.channel || e;
Note that you currently need Chrome Nightly started with --enable-data-channels for it to work as well.
Here is a sequence of events I have working today (Feb 2014) in Chrome. This is for a simplified case where peer 1 will stream video to peer 2.
Set up some way for the peers to exchange messages. (The variance in how people accomplish this is what makes different WebRTC code samples so incommensurable, sadly. But mentally, and in your code organization, try to separate this logic out from the rest.)
On each side, set up message handlers for the important signalling messages. You can set them up and leave them up. There are 3 core messages to handle & send:
an ice candidate sent from the other side ==> call addIceCandidate with it
an offer message ==> SetRemoteDescription with it, then make an answer & send it
an answer message ===> SetRemoteDescription with it
On each side, create a new peerconnection object and attach event handlers to it for important events: onicecandidate, onremovestream, onaddstream, etc.
ice candidate ===> send it to other side
stream added ===> attach it to a video element so you can see it
When both peers are present and all the handlers are in place, peer 1 gets a trigger message of some kind to start video capture (using the getUserMedia call)
Once getUserMedia succeeds, we have a stream. Call addStream on the peer 1's peer connection object.
Then -- and only then -- peer 1 makes an offer
Due to the handlers we set up in step 2, peer 2 gets this and sends an answer
Concurrently with this (and somewhat obscurely), the peer connection object starts producing ice candidates. They get sent back and forth between the two peers and handled (steps 2 & 3 above)
Streaming starts by itself, opaquely, as a result of 2 conditions:
offer/answer exchange
ice candidates received, exchanged, and added
I haven't found a way to add video after step 9. When I want to change something, I go back to step 3.

Node.js WebSocket Broadcast

I'm using the ws library for WebSockets in Node.js and
I'm trying this example from the library examples:
var sys = require("sys"),
ws = require("./ws");
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// server closes connection after 10s, will also get "close" event
setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
websocket.write("Thanks!");
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
});
}).listen(8080);
All OK. It works, but running 3 clients, for instance, and sending "Hello!" from one will make the server only reply "Thanks!" to the client which sent the message, not to all.
How can I broadcast "Thanks!" to all connected clients when someone sends "Hello!"?
Thanks!
If you want to send out to all clients, you have to keep track of them. Here is a sample:
var sys = require("sys"),
ws = require("./ws");
// # Keep track of all our clients
var clients = [];
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// # Add to our list of clients
clients.push(websocket);
// server closes connection after 10s, will also get "close" event
// setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
// # Write out to all our clients
for(var i = 0; i < clients.length; i++) {
clients[i].write("Thanks!");
}
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
for(var i = 0; i < clients.length; i++) {
// # Remove from our connections list so we don't send
// # to a dead socket
if(clients[i] == websocket) {
clients.splice(i);
break;
}
}
});
}).listen(8080);
I was able to get it to broadcast to all clients, but it's not heavily tested for all cases. The general concept should get you started though.
EDIT: By the way I'm not sure what the 10 second close is for so I've commented it out. It's rather useless if you're trying to broadcast to all clients since they'll just keep getting disconnected.
I would recommend you to use socket.io. It has example web-chat functionality out of the box and also provides abstraction layer from the socket technology on client (WebSockets are supported by Safari, Chrome, Opera and Firefox, but disabled in Firefox and Opera now due to security vulnerabilities in ws-protocol).

Categories

Resources