Netty WebSocket Client Delays in channel.writeAndFlush - websocket

We have a WebSocket client application built using Netty 4.1.50. The application is running on a Linux VM/Container.
Here is the code snippet
EventLoop = new EpollEventLoopGroup(0, new DefaultThreadFactory("websocket-client");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(EpollSocketChannel.class)
.handler(channelInitializer)
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture cf = bootstrap.connect(configuration.getHost(), configuration.getPort());
channel = cf.channel();
/*
* Wait for it to complete, or throw a fault.
*/
try {
cf.sync();
} catch (Exception e) {
logger.warn("Failed to connect: ", e);
return;
}
logger.debug("Socket channel {} got created", channel);
cf.channel().closeFuture().sync();
logger.info("Connection closed");
logger.debug("Writing data to socket channel {}", channel);
String messageText = JsonUtil.toJson(transportMessage);
ChannelFuture channelFuture = channel.writeAndFlush(new TextWebSocketFrame(messageText));
channelFuture.addListener((ChannelFutureListener) future -> {
logger.debug("Data written to socket channel. Request id: {}", transportMessage.getRequestId());
});
We are doing lot load testing by sending a lot of messages over the WebSocket channel from the Server and under the load, we are observing delays in writing the message to WebSocket channel on the client-side.
Logs
DEBUG 06/22 09:12:13.246 com.test.client.websocket.WebSocketConnectionManagerImpl: ForkJoinPool.commonPool-worker-3 {X-REQUEST-ID=102953b0-772e-4187-947d-c129fa099291} Writing data to socket channel [id: 0xd63b3752]
DEBUG 06/22 09:12:13.378 com.test.client.websocket.WebSocketConnectionManagerImpl: websocket-client-1-1 {} Data written to socket channel. Request id: 102953b0-772e-4187-947d-c129fa099291
DEBUG 06/22 09:12:36.966 com.test.client.websocket.WebSocketConnectionManagerImpl: ForkJoinPool.commonPool-worker-3 {X-REQUEST-ID=34568e89-95ff-4be3-abcf-ac2edd315f31} Writing data to socket channel [id: 0xd63b3752]
DEBUG 06/22 09:13:07.392 com.test.client.websocket.WebSocketConnectionManagerImpl: websocket-client-1-1 {} Data written to socket channel. Request id: 34568e89-95ff-4be3-abcf-ac2edd315f31
DEBUG 06/22 09:15:06.449 com.test.client.websocket.WebSocketConnectionManagerImpl: I/O dispatcher 2 {X-REQUEST-ID=0e28fbae-4c06-4551-9a73-8c171cc09911} Writing data to socket channel [id: 0xd63b3752]
DEBUG 06/22 09:15:06.449 com.vmware.hcs.broker.connector.client.websocket.WebSocketConnectionManagerImpl: websocket-client-1-1 {} Data written to socket channel. Request id: 0e28fbae-4c06-4551-9a73-8c171cc09911
for request-id '34568e89-95ff-4be3-abcf-ac2edd315f31', it took ~30 sec to write into SOCKET channel.

Related

Ngxs + WebSocket | How to listen the socket events at client?

I am using Ngxs/websocket-plugin in my project.
I connected the WebSocket with
this.store.dispatch(new ConnectWebSocket());
and dispatching the socket messages with
sendMessage(from: string, message: string) {
const event = new SendWebSocketMessage({
type: 'message',
from,
message
});
this.store.dispatch(event);
}
The socket events are being received on the server but I am not getting how to listen to the socket event at the client?
The socket messages are being dispatched in the store by the ngxs/websocket-plugin.
This plugin will receive a message from the server and dispatch the message as an action with the corresponding type value. If the type property doesn't match any client-side #Action methods (with an Action with the corresponding static type property value) then no State will respond to the message.

How can i handle connection and reconnect if connection got closed?

I need this client stay connected for long, How can i make sure about connection? because the issue was in connection, so i am updating my question. what should i do if server close connection? or if client close connection? how can i handle it and reconnect client to the server?
public void consumeServerSentEvent() {
WebClient client = WebClient.create("http://localhost:8080/sse-server");
ParameterizedTypeReference<ServerSentEvent<String>> type
= new ParameterizedTypeReference<ServerSentEvent<String>>() {};
Flux<ServerSentEvent<String>> eventStream = client.get()
.uri("/stream-sse")
.retrieve()
.bodyToFlux(type);
eventStream.subscribe(
content -> logger.info("Time: {} - event: name[{}], id [{}], content[{}] ",
LocalTime.now(), content.event(), content.id(), content.data()),
error -> logger.error("Error receiving SSE: {}", error),
() -> logger.info("Completed!!!"));
}
According to documentation retrieve() returns Mono of ClientResponse, but for your case you need to consume Flux of the body.
Try some thing like this:
Flux<ServerSentEvent<String>> eventStream = client.get()
.uri("/stream-sse")
.retrieve()
.flatMapMany(response -> response.bodyToFlux(type));

with spring boot rsocket capture the cancel frame type

I have a spring boot rsocket implementation where if a client cancels or closes their rsocket request then I want to cancel other subscription registrations on the server.
In the logs on the spring boot server I can see that a cancel message is sent or received:
WARN i.r.t.n.s.WebsocketServerTransport$1 [reactor-http-nio-3] received WebSocket Close Frame - connection is closing
INFO r.u.Loggers$Slf4JLogger [reactor-http-nio-3] cancel()
How do I capture and handle this cancel signal?
I tried cancel endpoints but these don't capture the signal:
#MessageMapping("cancel")
Flux<Object> onCancel() {
log.info("Captured cancel signal");
}
or
#ConnectMapping("cancel")
Flux<Object> onCancel2() {
log.info("Captured cancel2 signal");
}
This question on cancel subscriptions is possibly related, and this question on detecting websocket disconnection
To capture the cancel signal you can use subscribe to onClose() event.
In your controller
#Controller
class RSocketConnectionController {
#ConnectMapping("client-id")
fun onConnect(rSocketRequester: RSocketRequester, clientId: String) {
// rSocketRequester.rsocket().dispose() //to reject connection
rSocketRequester
.rsocket()
.onClose()
.subscribe(null, null, {
log.info("{} just disconnected", clientId)
//TODO here whatever you want
})
}
}
Your client needs to send the SETUP frame properly to invoke this #ConnectMapping. If you use rsocket-js you need to add a payload like this:
const client = new RSocketClient({
// send/receive JSON objects instead of strings/buffers
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
//for connection mapping on server
payload: {
data: 'unique-client-id', //TODO you can receive this data on server side
metadata: String.fromCharCode("client-id".length) + "client-id"
},
// ms btw sending keepalive to server
keepAlive: 60000,
.....
}
});
It was not a well set out question. The answer is that
INFO r.u.Loggers$Slf4JLogger [reactor-http-nio-3] cancel()
is seen by a FluxSink that was setup from the original #MessageMapping endpoint.
For example:
#MessageMapping("hello")
Flux<Object> hello(#Payload String message) {
return myService.generateWorld(message);
}
In myService class
public Flux<Object> generateWorld(String hello) {
EmitterProcessor<Object> emitter = EmitterProcessor.create();
FluxSink<Object> sink = emitter.sink(FluxSink.OverflowStrategy.LATEST);
// doing stuff with sink here
sink.next(stuff());
// This part will handle a cancel from the client
sink.onCancel(() -> {log.info("********** SINK.onCancel ***********");});
return Flux.from(emitter));
}
The sink.onCancel() will handle a cancel of the flux to the hello endpoint, from the client.

Flutter websocket disconnect listening

In Flutter, I wanna listen to websocket disconnect event, how to achieve that?
The websocket connect will be drop when app goes to background, I still not found a method to let it continuesly running in background (does anyone have solution?), So I have to detect if a websocket connect is lost or something, so that I can re-connect when lost connection.
Pls help if anyone knows how to achieve that.
You can find out if websocket is closed by implementing onDone callback. See the example below:
_channel = IOWebSocketChannel.connect(
'ws://yourserver.com:port',
);
///
/// Start listening to new notifications / messages
///
_channel.stream.listen(
(dynamic message) {
debugPrint('message $message');
},
onDone: () {
debugPrint('ws channel closed');
},
onError: (error) {
debugPrint('ws error $error');
},
);
Hope that helps.
If your server closes the connection just use pinginterval like this
ws.pingInterval = const Duration(seconds: 5);
onDone should be called.
basic ping pong is enough.
Other answers around SO and the web suggest that you can't just keep sockets open in the background (which seems reasonable, you'd be keeping open network connections that may affect battery life). Depending on your use case, you might be better looking at Push Notifications or something that checks on a schedule.
How to keep iphone ios xmpp connection alive while in the background?
Websocket paused when android app goes to background
https://www.quora.com/How-do-I-keep-Socket-IO-running-in-the-background-on-iOS
WebSocketChannel channel = WebSocketChannel.connect(uri );
Stream stream = channel.stream;
stream.listen((event) {
print('Event from Stream: $event');
},onError: (e){
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
},
onDone: (() {
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
})
);
I recommend you to use this multiplatform websocket package https://pub.dev/packages/websocket_universal , there you can even track all WS events happening (and even built-in ping measurment if you need any):
import 'package:websocket_universal/websocket_universal.dart';
/// Example works with Postman Echo server
void main() async {
/// Postman echo ws server (you can use your own server URI)
/// 'wss://ws.postman-echo.com/raw'
/// For local server it could look like 'ws://127.0.0.1:42627/websocket'
const websocketConnectionUri = 'wss://ws.postman-echo.com/raw';
const textMessageToServer = 'Hello server!';
const connectionOptions = SocketConnectionOptions(
pingIntervalMs: 3000, // send Ping message every 3000 ms
timeoutConnectionMs: 4000, // connection fail timeout after 4000 ms
/// see ping/pong messages in [logEventStream] stream
skipPingMessages: false,
/// Set this attribute to `true` if do not need any ping/pong
/// messages and ping measurement. Default is `false`
pingRestrictionForce: false,
);
/// Example with simple text messages exchanges with server
/// (not recommended for applications)
/// [<String, String>] generic types mean that we receive [String] messages
/// after deserialization and send [String] messages to server.
final IMessageProcessor<String, String> textSocketProcessor =
SocketSimpleTextProcessor();
final textSocketHandler = IWebSocketHandler<String, String>.createClient(
websocketConnectionUri, // Postman echo ws server
textSocketProcessor,
connectionOptions: connectionOptions,
);
// Listening to webSocket status changes
textSocketHandler.socketHandlerStateStream.listen((stateEvent) {
// ignore: avoid_print
print('> status changed to ${stateEvent.status}');
});
// Listening to server responses:
textSocketHandler.incomingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket got text message from server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Listening to debug events inside webSocket
textSocketHandler.logEventStream.listen((debugEvent) {
// ignore: avoid_print
print('> debug event: ${debugEvent.socketLogEventType}'
' [ping=${debugEvent.pingMs} ms]. Debug message=${debugEvent.message}');
});
// Listening to outgoing messages:
textSocketHandler.outgoingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket sent text message to server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Connecting to server:
final isTextSocketConnected = await textSocketHandler.connect();
if (!isTextSocketConnected) {
// ignore: avoid_print
print('Connection to [$websocketConnectionUri] failed for some reason!');
return;
}
textSocketHandler.sendMessage(textMessageToServer);
await Future<void>.delayed(const Duration(seconds: 30));
// Disconnecting from server:
await textSocketHandler.disconnect('manual disconnect');
// Disposing webSocket:
textSocketHandler.close();
}

Preventing a 'heartbeat timeout' with websockets and SocketIO

I am using NodeJS and SocketIO for my websocket solution. It works fine, but after a few minutes, my socket server always times out with the following messages in my console:
debug - fired heartbeat timeout for client
info - transport end <heartbeat timeout>
debug - set close timeout for client
debug - cleared close timeout for client
debug - discarding transport
Here is my complete server.js file:
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs')
app.listen(3000);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'from socket server' });
socket.on('swipe', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);
socket.emit('swipe event received on server!');
});
How can I prevent the timeouts from happening?
Check out the close timeout and heartbeat timeout options here
You can set these programmatically on the server via:
var io = require('socket.io').listen(80);
io.set('close timeout', 60);
io.set('heartbeat timeout', 60);
As for the design of your application, you should check out this question for whether or not you should change the timeout.

Resources