I have a problem when sending a message to a websocket server.
2020-06-15 17:06:51,830 INFO [snc.mob.gro.myt.con.ws.MutinyWebsocket] (vert.x-eventloop-thread-6) erreur {}: io.netty.handler.codec.http.websocketx.WebSocketHandshakeException: Connection closed while handsh
ake in process
at io.vertx.core.http.impl.WebSocketHandshakeInboundHandler.channelInactive(WebSocketHandshakeInboundHandler.java:57)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:260)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:246)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:239)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelInactive(CombinedChannelDuplexHandler.java:418)
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:386)
at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:351)
at io.netty.handler.codec.http.HttpClientCodec$Decoder.channelInactive(HttpClientCodec.java:288)
at io.netty.channel.CombinedChannelDuplexHandler.channelInactive(CombinedChannelDuplexHandler.java:221)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:260)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:246)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:239)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1405)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:260)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:246)
at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:901)
at io.netty.channel.AbstractChannel$AbstractUnsafe$8.run(AbstractChannel.java:818)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:497)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
I tried with some chrome plugins and everything works fine.
Here is my code :
public void send(final String message){
WebSocketConnectOptions wsoptions = new WebSocketConnectOptions()
.setSsl(Boolean.FALSE).setURI( "/" ).setPort( 9304 ).setHost( "myserver.com" );
HttpClientOptions httpClientOptions = new HttpClientOptions( )
.setTrustAll( true ).setSsl( false );
Uni< WebSocket > webSocketUni = vertx.createHttpClient(httpClientOptions).webSocket( wsoptions );
webSocketUni
.subscribe()
.with( a -> {
a.handler( data -> {
log.info( "server message {}", data.toString() );
a.writeTextMessageAndForget( message );
} );
},
e -> log.info( "erreur {}", e ))
;
}
I don't know what's the handshake problem with my client.
Any help really appreciated.
Thank you!
Related
I have a node.js server that takes data from a "sleepy" client over a websocket, then tries to push that data to the web client via its websocket. Sleepy means that it sleeps for a few minutes then wakes up to take a measurement and push the data, then back to sleep. See below:
sleepy client --> websocket--> node.js server --> websocket --> web clients
There is only one sleepy client but there can be multiple web clients. So when new data arrives, I need to push it to all web client web sockets. To keep track of this, I have an array that track web clients and stores their ctx info for later.
But, and this is the problem, when I try to push data to the web client websocket, I get a context error and node.js crashes.
Here is my code:
var globalCtx = [];
app.ws.use( function( ctx, next ) {
ctx.websocket.on('message', function ( message )
{
if( "telemetry" in jsonMsg )
{
console.log( "Got Telemetry: " );
console.log( message.toString() );
// we just got a telemetry message,
// so push it to the all the web client websockets
globalCtx.forEach( (myCtx, next) => {
console.log( "DEBUG: ", util.inspect(myCtx) ); <<<-----crashes here
myCtx.send( jsonMsg ); <<<----- or crashes here when not debugging
});
}
else
if( "webClient" in jsonMsg )
{
console.log( "Got WS Client: " );
console.log( message.toString() );
// we just got a web client connection message
// so store its context for pushing to later
if( Array.isArray( globalCtx ) && globalCtx.length )
{
// search for an existing entry
for( let idx = 0; idx < globalCtx.length; idx++ )
{
if( globalCtx[ idx ].ip == ctx.ip )
{
// we already have this IP stored, do nothing
console.log("IP already found: ", ctx.ip );
//return next( ctx );
return;
}
}
// we made it here, this means that the IP wasn't found
console.log("not found, adding IP: ", ctx.ip );
globalCtx.push( ctx );
}
else
{
// the array is empty, so just add it
console.log("empty array adding IP: ", ctx.ip );
globalCtx.push( ctx );
}
}
});
return next(ctx);
});
Here is the error:
/home/pi/node_modules/koa/lib/response.js:73
return this.res.statusCode;
^
TypeError: Cannot read property 'statusCode' of undefined
at Object.get status [as status] (/home/pi/node_modules/koa/lib/response.js:73:21)
at /home/pi/node_modules/only/index.js:6:20
at Array.reduce (<anonymous>)
at module.exports (/home/pi/node_modules/only/index.js:5:15)
at Object.toJSON (/home/pi/node_modules/koa/lib/response.js: 562:12)
at Object.toJSON (/home/pi/node_modules/koa/lib/context.js:5 1:31)
at Object.inspect (/home/pi/node_modules/koa/lib/context.js: 33:17)
at formatValue (internal/util/inspect.js:745:19)
at Object.inspect (internal/util/inspect.js:319:10)
at /home/pi/koaThermostat/index2.js:46:46
Here is the error from the myCtx.send( jsonMsg ); line
/home/pi/koaThermostat/index2.js:47
myCtx.send( jsonMsg );
^
TypeError: myCtx.send is not a function
at /home/pi/koaThermostat/index2.js:47:23
Well I feel silly! When using the Koa context with websockets, you have to use the websocket object. So in my case it should have been myCtx.websocket.send() not myCtx.send().
Now I'm on to my next bug....
I am getting "HTTP/2 client preface string missing or corrupt."
My thoughts are that it has to do with the headers not being set correctly. It is likely the implementation of WifiClient/WifiSecureClient. I've been thinking about this for over several weeks and I'm stuck. Any advice?
[Updated: Answer below]
The client was generated using the nanopb protocol buffer compiler:
protoc --plugin=protoc-gen-nanopb=~/grpc/nanopb/generator/protoc-gen-nanopb --nanopb_out=. helloworld.proto
Arduino client:
DHT dht(DHTPIN, DHTTYPE);
WiFiClient client;
//WiFiClientSecure client;
void setup() {
Serial.setDebugOutput(true);
Serial.begin(115200);
delay(10);
WiFi.begin("<SSID>", "<My Password>");
delay(3000);
while (WiFi.status() != WL_CONNECTED) {
Serial.println("WIFI connection failed, reconnecting...");
delay(2000);
}
Serial.print("WiFi connected, ");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("Starting DHT11 sensor...");
dht.begin();
}
void loop() {
Serial.print("connecting to ");
Serial.println(addr);
// client.setInsecure();
if (!client.connect(addr, port)) {
Serial.println(addr);
Serial.println(port);
Serial.println("connection failed");
Serial.println("wait 5 sec to reconnect...");
delay(5000);
return;
}
Serial.println("reading humidity/temp...");
float hum = dht.readHumidity();
float tmp = dht.readTemperature(true);
Serial.println(hum);
Serial.println(tmp);
if (isnan(hum) || isnan(tmp)) {
Serial.println("failed to read sensor data");
delay(2000);
return;
}
float hiCel = dht.computeHeatIndex(tmp, hum, true);
helloworld_TempEvent temp = helloworld_TempEvent_init_zero;
temp.deviceId = 1;
temp.eventId = 0;
temp.humidity = hum;
temp.tempCel = tmp;
temp.heatIdxCel = hiCel;
sendTemp(temp);
delay(1000);
}
void sendTemp(helloworld_TempEvent e) {
uint8_t buffer[128];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&stream, helloworld_TempEvent_fields, &e)) {
Serial.println("failed to encode temp proto");
Serial.println(PB_GET_ERROR(&stream));
return;
}
Serial.print("sending temp... ");
Serial.println(e.tempCel);
client.write(buffer, stream.bytes_written);
}
The server was generated using the standard java protocol buffer compiler. The only thing I changed was adding a TempEvent (below).
... (helloworld template stuff) ...
// The request message containing temperatures
message TempEvent {
int32 deviceId = 1;
int32 eventId = 2;
float humidity = 3;
float tempCel = 4;
float heatIdxCel = 5;
}
The sample java client works without any issues. Where my problem lies is the simple client using nanopb on an ESP8266-01 wifi module which is sending the data using gRPC.
public class Server {
// Doesn't work
public static void main(String[] args) throws IOException, InterruptedException {
io.grpc.Server server = ServerBuilder
.forPort(8080)
.addService(new HelloServiceImpl()).build();
server.start();
server.awaitTermination();
}
// Works just fine
public static void main(String[] args) throws IOException, InterruptedException {
try (ServerSocket server = new ServerSocket(8080)) {
System.out.println("Server accepting connections on port " + server.getLocalPort());
TemperatureClient tempClient = new TemperatureClient();
while(true) {
Socket client = server.accept();
System.out.println("Client connected using remote port " + client.getPort());
final Thread t = new Thread(() -> {
try {
TempEvent p = TempEvent.parseFrom(client.getInputStream());
float i = p.getTempCel();
System.out.println("TEMP " + i);
} catch (IOException ioe) {
ioe.printStackTrace();
}
});
t.start();
}
}
}
The client is able to hit the server:
Nov 29, 2021 5:49:30 PM io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
INFO: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 080c10641d0000d84125e17aa0422de4459e42
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:108)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:306)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.grpc.netty.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
To debug this, I wanted to first see if I could use grpcurl, but I get this:
localhost#pro ~ % grpcurl -plaintext localhost:50051 list
Failed to list services: server does not support the reflection API
localhost#pro ~ % grpcurl -insecure localhost:50051 list
Failed to dial target host "localhost:50051": tls: first record does not look like a TLS handshake
I started looking into the implementation for WifiClient.h used in my implementation code, but does anyone have any ideas on a simple way to test this without digging into everything? I was thinking this should be super simple... but it is turning out to be much more entailed to generate a simple client than I thought. I feel like I am missing something here.
From other forums on here: "The client and server aren't agreeing. Typically this is because one is plaintext and the other using TLS. But it can also be due to HTTP/1 vs HTTP/2 in certain environments."
After looking at the Go Lang implementation, I just tried using WiFiClientSecure client.setInsecure(); // didn't work and the hex dump is below.
17:36:33.030 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND SETTINGS: ack=false settings={MAX_CONCURRENT_STREAMS=2147483647, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192}
17:36:33.031 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041
17:36:33.063 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND GO_AWAY: lastStreamId=2147483647 errorCode=1 length=126 bytes=485454502f3220636c69656e74207072656661636520737472696e67206d697373696e67206f7220636f72727570742e204865782064756d7020666f72207265...
17:36:33.064 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] Sent GOAWAY: lastStreamId '2147483647', errorCode '1', debugData 'HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 16030100d4010000d00303000000005c2f03aae7147c5f36'. Forcing shutdown of the connection.
Dec 10, 2021 5:36:33 PM io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
INFO: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 16030100d4010000d00303000000005c2f03aae7147c5f36
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:108)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:306)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239)
at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.grpc.netty.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
WiFiClient client;
if (!client.connect(addr, port)) {
This forms a basic TCP connection. However, gRPC is a complex protocol based on HTTP/2. Currently you are just writing raw protobuf messages to a TCP socket, which can work for communication but is certainly not what a gRPC server is expecting.
Nanopb does not have gRPC support in itself. There is a third-party project adding it, but it is currently unmaintained.
I am unable to use cancellation tokens to stop a TCP Listener. The first code extract is an example where I can successfully stop a test while loop in a method from another class. So I don't understand why I cant apply this similar logic to the TCP Listener Class. Spent many days reading convoluted answers on this topic and cannot find a suitable solution.
My software application requires that the TCP Listener must give the user the ability to stop it from the server end, not the client. If a user wants to re-configure the port number for this listener then they would currently have to shutdown the software in order for Windows to close the underlying socket, this is no good as would affect the other services running in my app.
This first extract of code is just an example where I am able to stop a while loop from running, this works OK but is not that relevant other than the faat I would expect this to work for my TCP Listener:
public void Cancel(CancellationToken cancelToken) // EXAMPLE WHICH IS WORKING
{
Task.Run(async () =>
{
while (!cancelToken.IsCancellationRequested)
{
await Task.Delay(500);
log.Info("Test Message!");
}
}, cancelToken);
}
Now below is the actual TCP Listener code I am struggling with
public void TcpServerIN(string inboundEncodingType, string inboundIpAddress, string inboundLocalPortNumber, CancellationToken cancelToken)
{
TcpListener listener = null;
Task.Run(() =>
{
while (!cancelToken.IsCancellationRequested)
{
try
{
IPAddress localAddr = IPAddress.Parse(inboundIpAddress);
int port = int.Parse(inboundLocalPortNumber);
listener = new TcpListener(localAddr, port);
// Start listening for client requests.
listener.Start();
log.Info("TcpListenerIN listener started");
// Buffer for reading data
Byte[] bytes = new Byte[1024];
String data = null;
// Enter the listening loop.
while (true)
{
// Perform a blocking call to accept client requests.
TcpClient client = listener.AcceptTcpClient();
// Once each client has connected, start a new task with included parameters.
var task = Task.Run(() =>
{
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();
data = null;
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Select Encoding format set by string inboundEncodingType parameter.
if (inboundEncodingType == "UTF8") { data = Encoding.UTF8.GetString(bytes, 0, i); }
if (inboundEncodingType == "ASCII") { data = Encoding.ASCII.GetString(bytes, 0, i); }
// Use this if you want to echo each message directly back to TCP Client
//stream.Write(msg, 0, msg.Length);
// If any TCP Clients are connected then pass the appended string through
// the rules engine for processing, if not don't send.
if ((listConnectedClients != null) && (listConnectedClients.Any()))
{
// Pass the appended message string through the SSSCRulesEngine
SendMessageToAllClients(data);
}
}
// When the remote client disconnetcs, close/release the socket on the TCP Server.
client.Close();
});
}
}
catch (SocketException ex)
{
log.Error(ex);
}
finally
{
// If statement is required to prevent an en exception thrown caused by the user
// entering an invalid IP Address or Port number.
if (listener != null)
{
// Stop listening for new clients.
listener.Stop();
}
}
}
MessageBox.Show("CancellationRequested");
log.Info("TCP Server IN CancellationRequested");
}, cancelToken);
}
Interesting to see that no one had come back with any solutions, admittedly it took me a long while to figure out a solution. The key to stopping the TCP Listener when using a synchronous blocking mode like the example below is to register the Cancellation Token with the TCP Listener itself, as well the TCP Client that may have already been connected at the time the Cancellation Token was fired. (see comments that are marked as IMPORTANT)
The example code may differ slightly in your own environment and I have extracted some code bloat that is unique to my project, but you'll get the idea in what we're doing here. In my project this TCP Server is started as a background service using NET Core 5.0 IHosted Services. My code below was adapted from the notes on MS Docs: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener?view=net-5.0
The main difference between the MS Docs and my example below is I wanted to allow multiple TCP Clients to connect hence the reason why I start up a new inner Task each time a new TCP Client connects.
/// <summary>
/// </summary>
/// <param name="server"></param>
/// <param name="port"></param>
/// <param name="logger"></param>
/// <param name="cancelToken"></param>
public void TcpServerRun(
int pluginId,
string pluginName,
string encoding,
int bufferForReadingData,
string ipAddress,
int port,
bool logEvents,
IServiceScopeFactory _scopeFactory,
CancellationToken cancelToken)
{
IPAddress localAddrIN = IPAddress.Parse(ipAddress);
TcpListener listener = new TcpListener(localAddrIN, port);
Task.Run(() =>
{
// Dispose the DbContext instance when the task has completed. 'using' = dispose when finished...
using var scope = _scopeFactory.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<TcpServer>>();
try
{
listener.Start();
cancelToken.Register(listener.Stop); // THIS IS IMPORTANT!
string logData = "TCP Server with name [" + pluginName + "] started Succesfully";
// Custom Logger - you would use your own logging method here...
WriteLogEvent("Information", "TCP Servers", "Started", pluginName, logData, null, _scopeFactory);
while (!cancelToken.IsCancellationRequested)
{
TcpClient client = listener.AcceptTcpClient();
logData = "A TCP Client with IP Address [" + client.Client.RemoteEndPoint.ToString() + "] connected to the TCP Server with name: [" + pluginName + "]";
// Custom Logger - you would use your own logging method here...
WriteLogEvent("Information", "TCP Servers", "Connected", pluginName, logData, null, _scopeFactory);
// Once each client has connected, start a new task with included parameters.
var task = Task.Run(async () =>
{
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();
// Buffer for reading data
Byte[] bytes = new Byte[bufferForReadingData]; // Bytes variable
String data = null;
int i;
cancelToken.Register(client.Close); // THIS IS IMPORTANT!
// Checks CanRead to verify that the NetworkStream is readable.
if (stream.CanRead)
{
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0 & !cancelToken.IsCancellationRequested)
{
data = Encoding.ASCII.GetString(bytes, 0, i);
logData = "TCP Server with name [" + pluginName + "] received data [" + data + "] from a TCP Client with IP Address [" + client.Client.RemoteEndPoint.ToString() + "]";
// Custom Logger - you would use your own logging method here...
WriteLogEvent("Information", "TCP Servers", "Receive", pluginName, logData, null, _scopeFactory);
}
// Shutdown and end connection
client.Close();
logData = "A TCP Client disconnected from the TCP Server with name: [" + pluginName + "]";
// Custom Logger - you would use your own logging method here...
WriteLogEvent("Information", "TCP Servers", "Disconnected", pluginName, logData, null, _scopeFactory);
}
}, cancelToken);
}
}
catch (SocketException ex)
{
// When the cancellation token is called, we will always encounter
// a socket exception for the listener.AcceptTcpClient(); blocking
// call in the while loop thread. We want to catch this particular exception
// and mark the exception as an accepted event without logging it as an error.
// A cancellation token is passed usually when the running thread is manually stopped
// by the user from the UI, or will occur when the IHosted service Stop Method
// is called during a system shutdown.
// For all other unexpected socket exceptions we provide en error log underneath
// in the else statement block.
if (ex.SocketErrorCode == SocketError.Interrupted)
{
string logData = "TCP Server with name [" + pluginName + "] was stopped due to a CancellationTokenSource cancellation. This event is triggered when the SMTP Server is manually stopped from the UI by the user or during a system shutdown.";
WriteLogEvent("Information", "TCP Servers", "Stopped", pluginName, logData, null, _scopeFactory);
}
else
{
string logData = "TCP Server with name [" + pluginName + "] encountered a socket exception error and exited the running thread.";
WriteLogEvent("Error", "TCP Servers", "Socket Exception", pluginName, logData, ex, _scopeFactory);
}
}
finally
{
// Call the Stop method to close the TcpListener.
// Closing the listener does not close any exisiting connections,
// simply stops listening for new connections, you are responsible
// closing the existing connections which we achieve by registering
// the cancel token with the listener.
listener.Stop();
}
});
}
Basically, I'm trying to check the status of my WebSocket Server.ws. However, when I query Server.ws.readyState, the only response I ever get is WebSocket.OPEN. How do I check if a WebSocket is disconnected if it always returns WebSocket.OPEN?
For example, I've tried to turn off the WiFi of the device used to test the Flutter app. Normally, after one second, the WebSocket is assumed disconnected and the connection is closed with a WebSocketStatus.GOING_AWAY close code. I assumed it would also change the WebSocket.readyState, but that doesn't seems to be the case.
So, how do I properly check the status of my WebSocket?
How I'm currently checking :
/// Connection status
IconButton _status() {
IconData iconData;
switch (Server.ws?.readyState) {
case WebSocket.CONNECTING:
print("readyState : CONNECTING");
iconData = Icons.wifi;
break;
case WebSocket.OPEN:
print("readyState : OPEN");
iconData = Icons.signal_wifi_4_bar;
break;
case WebSocket.CLOSING:
print("readyState : CLOSING");
iconData = Icons.signal_wifi_4_bar_lock;
break;
case WebSocket.CLOSED:
print("readyState : CLOSED");
iconData = Icons.warning;
break;
default:
print("readyState : " + Server.ws.readyState.toString());
break;
}
return new IconButton(
icon: new Icon(iconData),
tooltip: 'Connection Status', // TODO:Localize
onPressed: () {
setState(() {
Server.ws.close();
});
},
);
}
Additional info about the WebSocket :
/// Should be called when the IP is validated
void startSocket() {
try {
WebSocket.connect(Server.qr).then((socket) {
// Build WebSocket
Server.ws = socket;
Server.ws.listen(
handleData,
onError: handleError,
onDone: handleDone,
cancelOnError: true,
);
Server.ws.pingInterval = new Duration(
seconds: Globals.map["PingInterval"],
);
send(
"CONNECTION",
{
"deviceID": Globals.map["UUID"],
},
);
});
} catch (e) {
print("Error opening a WebSocket : $e");
}
}
/// Handles the closing of the connection.
void handleDone() {
print("WebSocket closed.");
new Timer(new Duration(seconds: Globals.map["PingInterval"]), startSocket);
}
/// Handles the WebSocket's errors.
void handleError(Error e) {
print("WebSocket error.");
print(e);
Server.ws.close();
}
I've gone ahead and taken a look at the source code for the WebSocket implementation. It appears that when the WebSocket is being closed with the status GOING_AWAY, the internal socket stream is being closed. However, it is possible that this event does not propagate to the transformed stream which handles the readyState of the instance. I would recommend filing a bug report at dartbug.com.
try setting the pingInterval, this checks for connection status every said interval, then the closeCode will update
Do we have any option to find out HTTP connection timedout status code exactly ? HTTP connection timedout and bad request url BOTH gives response code 0.Is there a way to identify connection timedout uniquely?
var self = Ti.UI.createWindow({
backgroundColor : '#ffffff'
});
var url = 'https://www.google.com';
if (Ti.Network.online) {
var xhr = Ti.Network.createHTTPClient({
timeout : 1000,
validatesSecureCertificate : false
});
xhr.onload = function(e) {
alert('onload:');
};
xhr.onerror = function(e) {
alert('e.error:' + e.error + ':status:' + xhr.status);
};
xhr.open('GET', url);
xhr.send(); }
self.open();
The above code is giving TimedOut status code as 0 in case of iphone, not like 408. But in case of Android TimedOut is not happening.
In Android , there are 2 cases to consider incase of Timeout. 2 types of errors are observed,
In logs,
Case 1:
TiHTTPClient: (TiHttpClient-9) [7700,93840] HTTP Error
(java.net.SocketTimeoutException): timeout [ERROR] : TiHTTPClient:
java.net.SocketTimeoutException: timeout
alert('e.error:' + e.error + ':status:' + xhr.status); gives output as
e.error:timeout :status:0...
Case 2:
TiHTTPClient: (TiHttpClient-14) [53148,203910] HTTP Error (java.net.SocketTimeoutException): SSL handshake timed out
[ERROR] : TiHTTPClient: java.net.SocketTimeoutException: SSL handshake timed out
alert('e.error:' + e.error + ':status:' + xhr.status); gives output as
e.error:SSL handshake timed out:status:0
so by checking the,
((e.error).toLowerCase() == "timeout" || (e.error).toLowerCase() ==
"sslhandshake timed out"),
we can
capturethe timeout scenario and display proper message accordingly.