Basic WebSocket Chat - c#

My objective is to create a basic chat application using web sockets.
I've got a client setup, however I am getting a specific exception every time I try to receive text on the client's side specifically.
The exception I am getting is the following:
The buffer type '166' is invalid. Valid buffer types are: 'Close', 'BinaryFragment', 'BinaryMessage', 'UTF8Fragment', 'UTF8Message'
I get this exception on the following line of code var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None); in the below code:
private readonly ClientWebSocket_socket;
private async Task ListenAsync(Action<string> textReceived)
{
while (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseSent)
{
var buffer = new byte[1024];
var segment = new ArraySegment<byte>(buffer, 0, buffer.Length);
var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None); // problem occurs here
if (socketResult.MessageType == WebSocketMessageType.Close)
{
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
var text = Encoding.UTF8.GetString(buffer, 0, socketResult.Count);
textReceived(text);
}
}
what am I doing wrong? similar code works fine to receive the text on the server side.
EDIT:
The server listens fine and the _socket.ReceiveAsync method runs as expected. It waits until a message has been published and only then does it move on to reply. If I don't listen on the client side, Everything works. However when I start the server to listen for the client messages then start the client to listen, the client breaks. I have tried sending a message first from the client and then starting to listen for the response afterwards and I still experience the same thing.
Below is the method I use to listen on the server side.
private static WebSocket _socket;
public async Task ListenAsync()
{
while (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseSent)
{
var buffer = new byte[1024];
var segment = new ArraySegment<byte>(buffer, 0, buffer.Length);
var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None);
if (socketResult.MessageType == WebSocketMessageType.Close)
{
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
var text = Encoding.UTF8.GetString(buffer, 0, socketResult.Count);
await PublishAsync($"Server received the following text: {text} # {DateTime.Now:g}");
}
}
This is the method I use to publish messages from the server.
public async Task PublishAsync(string text)
{
var bytes = Encoding.UTF8.GetBytes(text);
var buffer = new ArraySegment<byte>(bytes, 0, bytes.Length);
await _socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

Related

c# ClientWebSocket creating mulitple websocket connections

I'm working on a .net core project where I need to create multiple websocket connections to a ws server using the ClientWebSocket class.
Below is the code to connect to the server and I am able to receive data in the while loop. However, none of the code after this works until the websocket is closed.
How do I create multiple websocket clients? Do I create a new long running Task everytime I need to create a new ws client?
Thank you in advance
using (ClientWebSocket ws = new ClientWebSocket())
{
await ws.ConnectAsync(new Uri(url), CancellationToken.None);
var msgbuf = new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)));
await ws.SendAsync(msgbuf, WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = WebSocket.CreateClientBuffer(4096, 4096);
while (ws.State != WebSocketState.Closed && !cancellationToken.IsCancellationRequested)
{
var receiveResult = await ws.ReceiveAsync(buffer, cancellationToken);
// if the token is cancelled while ReceiveAsync is blocking, the socket state changes to aborted and it can't be used
if (!cancellationToken.IsCancellationRequested)
{
// the server is notifying us that the connection will close; send acknowledgement
if (ws.State == WebSocketState.CloseReceived && receiveResult.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine($"\nAcknowledging Close frame received from server");
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
}
// display text or binary data
if (ws.State == WebSocketState.Open && receiveResult.MessageType != WebSocketMessageType.Close)
{
if (receiveResult.Count > 0)
{
string message = "";
if (receiveResult.EndOfMessage == false)
{
message += Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
}
else
{
message += Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
HandleMessage(message, cancellationTokenSource);
message = "";
}
}
}
}
}
Console.WriteLine($"Ending processing loop in state {ws.State}");
return ws;
}

ispossible create websocket server connected to another websocket?

I need WebSocket code for implement structure of my client. I will create WebSocket server for my client with receive by client from ex: binance websocket
Just part of connect to binance websocket need implement.
ASP.NET Core 5 C#
private async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
I did something like that. I created 2 WebSocket servers, one acting as a proxy to the other. You can check it out, maybe it works for you or at least you can find some answers there. It is a bit disordered because I did it as a research, so ask me if you need more info.
https://github.com/RenanDiaz/WebSockets
public override async Task OnConnected(WebSocket socket)
{
await base.OnConnected(socket);
if (_client == null)
{
_client = new ClientWebSocket();
await _client.ConnectAsync(new Uri("ws://localhost:5001/chat"), CancellationToken.None);
var thread = new Thread(new ThreadStart(ReceiveMessageFromAPIServer));
thread.Start();
}
}
private async void ReceiveMessageFromAPIServer()
{
var buffer = new byte[1024 * 4];
while (_client != null)
{
var result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
var messageString = Encoding.UTF8.GetString(buffer, 0, result.Count);
var message = JsonConvert.DeserializeObject<IncomingAPIServerMessage>(messageString);
await SendMessageToAll(message);
}
}

ClientWebSocket example hangs

The example shown on the following page doesn't work:
Using c# ClientWebSocket with streams
It hangs on the following line:
await ws.ConnectAsync(serverUri, CancellationToken.None);
It appears the connection is not made.
Please indicate the simplest modification to make the following code work. I do not wish to use any 3rd party tools or libraries.
private static async Task DoClientWebSocket()
{
using (ClientWebSocket ws = new ClientWebSocket())
{
Uri serverUri = new Uri("wss://echo.websocket.org/");
await ws.ConnectAsync(serverUri, CancellationToken.None);
while (ws.State == WebSocketState.Open)
{
string msg = "hello123";
ArraySegment<byte> bytesToSend = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg));
await ws.SendAsync(bytesToSend, WebSocketMessageType.Text, true, CancellationToken.None);
ArraySegment<byte> bytesReceived = new ArraySegment<byte>(new byte[1024]);
WebSocketReceiveResult result = await ws.ReceiveAsync(bytesReceived, CancellationToken.None);
Console.WriteLine(Encoding.UTF8.GetString(bytesReceived.Array, 0, result.Count));
}
}
}
You are correct. You don't need to add any header in order to use wss://echo.websocket.org/. Your code run just fine at my end. But I'll suggest one improvement to include timeout for your ConnectAsync, SendAsync and ReceiveAsync calls so that it do not get stuck for long.
I have restricted code to call SendAsync to just 5 times so that its easier to verify output.
[Edited:] Include logic to receive complete response by calling `ReceiveAsync multiple times.
private static async Task DoClientWebSocket()
{
using (ClientWebSocket ws = new ClientWebSocket())
{
Uri serverUri = new Uri("wss://echo.websocket.org/");
//Implementation of timeout of 5000 ms
var source = new CancellationTokenSource();
source.CancelAfter(5000);
await ws.ConnectAsync(serverUri, source.Token);
var iterationNo = 0;
// restricted to 5 iteration only
while (ws.State == WebSocketState.Open && iterationNo++ < 5)
{
string msg = "hello0123456789123456789123456789123456789123456789123456789";
ArraySegment<byte> bytesToSend =
new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg));
await ws.SendAsync(bytesToSend, WebSocketMessageType.Text,
true, source.Token);
//Receive buffer
var receiveBuffer = new byte[200];
//Multipacket response
var offset = 0;
var dataPerPacket = 10; //Just for example
while (true)
{
ArraySegment<byte> bytesReceived =
new ArraySegment<byte>(receiveBuffer, offset, dataPerPacket);
WebSocketReceiveResult result = await ws.ReceiveAsync(bytesReceived,
source.Token);
//Partial data received
Console.WriteLine("Data:{0}",
Encoding.UTF8.GetString(receiveBuffer, offset,
result.Count));
offset += result.Count;
if (result.EndOfMessage)
break;
}
Console.WriteLine("Complete response: {0}",
Encoding.UTF8.GetString(receiveBuffer, 0,
offset));
}
}
}
static void Main(string[] args)
{
var taskWebConnect = Task.Run(() => DoClientWebSocket());
taskWebConnect.Wait();
}
Output on command prompt:
Data:hello01234
Data:5678912345
Data:6789123456
Data:7891234567
Data:8912345678
Data:9123456789
Complete response: hello0123456789123456789123456789123456789123456789123456789

How can I send audio to Nexmo Voice through websocket

I am trying to implement Nexmo's Voice api, with websockets, in a .Net Core 2 web api.
This api needs to :
receive audio from phone call, through Nexmo
use Microsoft Cognitive Speech to text api
send the text to a bot
use Microsoft Cognitive text to speech on the reply of the bot
send back the speech to nexmo, through their voice api websocket
For now, I'm bypassing the bot steps, as I am first trying to connect to the websocket.
When trying an echo method (send back to the websocket the audio received), it works without any issue.
But when I try to send the speech from Microsoft text to speech, the phone call ends.
I am not finding any documentation implementing something different than just an echo.
The TextToSpeech and SpeechToText methods work as expected when used outside of the websocket.
Here's the websocket with the speech-to-text :
public static async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
while(!result.EndOfMessage)
{
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
var text = SpeechToText.RecognizeSpeechFromBytesAsync(buffer).Result;
Console.WriteLine(text);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
And here's the websocket with the text-to-speech :
public static async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
var ttsAudio = await TextToSpeech.TransformTextToSpeechAsync("Hello, this is a test", "en-US");
await webSocket.SendAsync(new ArraySegment<byte>(ttsAudio, 0, ttsAudio.Length), WebSocketMessageType.Binary, true, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
Update March 1st 2019
in reply to Sam Machin's comment
I tried splitting the array into chunks of 640 bytes each (I'm using 16000khz sample rate), but nexmo still hangs up the call, and I still don't hear anything.
public static async Task NexmoTextToSpeech(HttpContext context, WebSocket webSocket)
{
var ttsAudio = await TextToSpeech.TransformTextToSpeechAsync("This is a test", "en-US");
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await SendSpeech(context, webSocket, ttsAudio);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing Socket", CancellationToken.None);
}
private static async Task SendSpeech(HttpContext context, WebSocket webSocket, byte[] ttsAudio)
{
const int chunkSize = 640;
var chunkCount = 1;
var offset = 0;
var lastFullChunck = ttsAudio.Length < (offset + chunkSize);
try
{
while(!lastFullChunck)
{
await webSocket.SendAsync(new ArraySegment<byte>(ttsAudio, offset, chunkSize), WebSocketMessageType.Binary, false, CancellationToken.None);
offset = chunkSize * chunkCount;
lastFullChunck = ttsAudio.Length < (offset + chunkSize);
chunkCount++;
}
var lastMessageSize = ttsAudio.Length - offset;
await webSocket.SendAsync(new ArraySegment<byte>(ttsAudio, offset, lastMessageSize), WebSocketMessageType.Binary, true, CancellationToken.None);
}
catch (Exception ex)
{
}
}
Here's the exception that sometimes appears in the logs :
System.Net.WebSockets.WebSocketException (0x80004005): The remote
party closed the WebSocket connection without completing the close
handshake.
It looks like you're writing the whole audio clip to the websocket, the Nexmo interface requires the audio to be in 20ms frames one per message, this means that you need to break your clip up into 320 or 640 byte (depending on if you're using 8Khz or 16Khz) chunks and write each one to the socket. If you try and write too larger file to the socket it will close as you are seeing.
See https://developer.nexmo.com/voice/voice-api/guides/websockets#writing-audio-to-the-websocket for the details.

No data received by TcpListener from an SmtpClient

I'm trying to solve strange problem that's causing the SMTP server I normally use to return a "unknown recipient" message for my own email address. In production I use a different SMTP server so this just blocks my testing.
I tried to use Fiddler to analyse the traffic but apparently Fiddler doesn't detect any SMTP traffic. I'm untrained with WireShark and this is just a small side project I can't spend large amount of time on so I thought I'd just piece together a small TCP service instead just to see what's being sent by the SmtpClient instance I use.
This is the TcpListener code ...
class Service
{
public Task Listen(int port, CancellationToken ct, TcpHandler tcpHandler = null)
{
tcpHandler = tcpHandler ?? defaultTcpHandler;
return Task.Run(async () =>
{
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port);
var listener = new TcpListener(endpoint);
listener.Start();
try
{
while (!ct.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync();
handleRequestAsync(client, tcpHandler, ct);
}
listener.Stop();
}
catch (SocketException ex)
{
Console.WriteLine($"SocketException: {ex}");
}
finally
{
listener.Stop();
}
},
ct);
}
void handleRequestAsync(TcpClient client, TcpHandler tcpHandler, CancellationToken ct)
{
Task.Run(async () =>
{
var stream = client.GetStream();
if (!stream.CanRead)
return;
var sb = new StringBuilder();
var buffer = new byte[4096];
int receivedCount;
while ((receivedCount = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
var received = Encoding.ASCII.GetString(buffer, 0, receivedCount);
sb.Append(received);
}
var request = sb.ToString();
if (request.Length != 0 && tcpHandler(sb.ToString(), out var response))
{
var send = Encoding.ASCII.GetBytes(response);
stream.Write(send, 0, send.Length);
stream.Flush();
}
client.Close();
});
}
Yes, the code is pretty naive with no real error handling etc. but, again, this was just a way to see what's being sent. The problem is that the call to stream.ReadAsync blocks and never returns. I also checked the TcpClient.Available property and it returns zero (0). Eventually the SmtpClient times out and nothing ever seems to get sent. I also haven't deep-dived into the SMTP protocol so maybe I'm missing something? Do I need to somehow acknowledge the connection before data gets sent by SmtpClient?
I haven't been doing much network coding for alot of years and I was never a ninja in this field. Any pointers or hints would be greatly appreciated.
Cheers!

Categories

Resources