WCF self-hosted WebSocket Service with Javascript client - c#

I have this WCF self-hosted WebSocket service code:
Main:
//Create a URI to serve as the base address
Uri httpUrl = new Uri("http://192.168.1.95:8080/service");
//Create ServiceHost
ServiceHost host = new ServiceHost(typeof(WebSocketService), httpUrl);
//Add a service endpoint
host.AddServiceEndpoint(typeof(IWebSocket), new NetHttpBinding(), "");
//Enable metadata exchange
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
//Start the Service
host.Open();
Console.WriteLine("Service is host at " + DateTime.Now.ToString());
Console.WriteLine("Host is running... Press <Enter> key to stop");
Console.ReadLine();
Interface:
namespace IWebSocketHostTest
{
[ServiceContract]
interface IWebSocketCallBack
{
[OperationContract(IsOneWay = true)]
void Send(int num);
}
[ServiceContract(CallbackContract = typeof(IWebSocketCallBack))]
public interface IWebSocket
{
[OperationContract]
void StartSend();
}
}
Service:
namespace IWebSocketHostTest
{
class WebSocketService : IWebSocket
{
Timer timer = null;
List<IWebSocketCallBack> callbackClientList = null;
public WebSocketService()
{
callbackClientList = new List<IWebSocketCallBack>();
timer = new Timer(3000);
timer.Elapsed += new ElapsedEventHandler(sendNumber);
timer.Start();
}
public void StartSend()
{
sender.addClient(OperationContext.Current.GetCallbackChannel<IWebSocketCallBack>());
}
private void sendNumber(Object o, ElapsedEventArgs eea)
{
timer.Stop();
var random = new Random();
int randomNum = random.Next(100);
foreach (IWebSocketCallBack callback in callbackClientList)
{
callback.Send(randomNum);
}
timer.Interval = random.Next(1000, 10000);
timer.Start();
}
}
}
This works perfect if i add a reference of this service in another .NET application.
But, what i need is to consume this service from an HTML+Javascript application, and i´m realy lost in how to do that. I couldn´t find a good example or tutorial with a Javascript client consuming a self-hosted WCF WebSocket service.
All the Javascript WebSocket code that i could find seems to be very simple, but i couldn´t make it work.
Here is my short JavaScript client test:
var ws = new WebSocket("ws://192.168.1.95:8080/service");
ws.onopen = function () {
console.log("WEBSOCKET CONNECTED");
};
it returns "WebSocket Error: Incorrect HTTP response. Status code 400, Bad Request" testing it with Fiddler.
What am i missing? Could you please give me some doc links to get more information or a code example?
Thank you!
EDIT:
Now i´ve tried using the "Microsoft.ServiceModel.WebSocket" library to try to make it work.
But, first, i don´t know if it´s still maintained by Microsoft or if it is deprecated, because i couldn´t find any information at MSDN and there is few info at internet.
And second, the "Open()" method of the "WebSocketHost" class is not found, so i don´t know how to make the server run...
Here is my code, i´ve taken it from a question at the ASP.NET forum.
using System;
using Microsoft.ServiceModel.WebSockets;
namespace WebSocketTest
{
class Program
{
static void Main(string[] args)
{
var host = new WebSocketHost<EchoService>(new Uri("ws://localhost:8080/echo"));
host.AddWebSocketEndpoint();
host.Open();
Console.Read();
host.Close();
}
}
class EchoService : WebSocketService
{
public override void OnOpen()
{
base.OnOpen();
Console.WriteLine("WebSocket opened.");
}
public override void OnMessage(string message)
{
Console.WriteLine("Echoing to client:");
Console.WriteLine(message);
this.Send(message);
}
protected override void OnClose()
{
base.OnClose();
Console.WriteLine("WebSocket closed.");
}
protected override void OnError()
{
base.OnError();
Console.WriteLine("WebSocket error occured.");
}
}
}
But, like i said before, the "host.Open()" method is not found, so i don´t know if i´m missing some reference or what, because i couldn´t find info about the WebSocketHost class... Any help?

After spending a day with the same task I finally got working solution. Hope it will help someone in the future.
Client JS script:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>WebSocket Chat</title>
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.1.js"></script>
<script type="text/javascript">
var ws;
$().ready(function ()
{
$("#btnConnect").click(function ()
{
$("#spanStatus").text("connecting");
ws = new WebSocket("ws://localhost:8080/hello");
ws.onopen = function ()
{
$("#spanStatus").text("connected");
};
ws.onmessage = function (evt)
{
$("#spanStatus").text(evt.data);
};
ws.onerror = function (evt)
{
$("#spanStatus").text(evt.message);
};
ws.onclose = function ()
{
$("#spanStatus").text("disconnected");
};
});
$("#btnSend").click(function ()
{
if (ws.readyState == WebSocket.OPEN)
{
var res = ws.send($("#textInput").val());
}
else
{
$("#spanStatus").text("Connection is closed");
}
});
$("#btnDisconnect").click(function ()
{
ws.close();
});
});
</script>
</head>
<body>
<input type="button" value="Connect" id="btnConnect" />
<input type="button" value="Disconnect" id="btnDisconnect" /><br />
<input type="text" id="textInput" />
<input type="button" value="Send" id="btnSend" /><br />
<span id="spanStatus">(display)</span>
</body>
</html>
Self hosted server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
namespace WebSocketsServer
{
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
// Create the ServiceHost.
using(ServiceHost host = new ServiceHost(typeof(WebSocketsServer), baseAddress))
{
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
CustomBinding binding = new CustomBinding();
binding.Elements.Add(new ByteStreamMessageEncodingBindingElement());
HttpTransportBindingElement transport = new HttpTransportBindingElement();
//transport.WebSocketSettings = new WebSocketTransportSettings();
transport.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
transport.WebSocketSettings.CreateNotificationOnConnection = true;
binding.Elements.Add(transport);
host.AddServiceEndpoint(typeof(IWebSocketsServer), binding, "");
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
[ServiceContract(CallbackContract = typeof(IProgressContext))]
public interface IWebSocketsServer
{
[OperationContract(IsOneWay = true, Action = "*")]
void SendMessageToServer(Message msg);
}
[ServiceContract]
interface IProgressContext
{
[OperationContract(IsOneWay = true, Action = "*")]
void ReportProgress(Message msg);
}
public class WebSocketsServer: IWebSocketsServer
{
public void SendMessageToServer(Message msg)
{
var callback = OperationContext.Current.GetCallbackChannel<IProgressContext>();
if(msg.IsEmpty || ((IChannel)callback).State != CommunicationState.Opened)
{
return;
}
byte[] body = msg.GetBody<byte[]>();
string msgTextFromClient = Encoding.UTF8.GetString(body);
string msgTextToClient = string.Format(
"Got message {0} at {1}",
msgTextFromClient,
DateTime.Now.ToLongTimeString());
callback.ReportProgress(CreateMessage(msgTextToClient));
}
private Message CreateMessage(string msgText)
{
Message msg = ByteStreamMessage.CreateMessage(
new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgText)));
msg.Properties["WebSocketMessageProperty"] =
new WebSocketMessageProperty
{
MessageType = WebSocketMessageType.Text
};
return msg;
}
}
}
UPDATE
As of .net 4.5 new way of writing server side have emerged. The benefits are cleaner code and possibility to support secure web sockets (WSS) over https.
public class WebSocketsServer
{
#region Fields
private static CancellationTokenSource m_cancellation;
private static HttpListener m_listener;
#endregion
#region Private Methods
private static async Task AcceptWebSocketClientsAsync(HttpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var hc = await server.GetContextAsync();
if (!hc.Request.IsWebSocketRequest)
{
hc.Response.StatusCode = 400;
hc.Response.Close();
return;
}
try
{
var ws = await hc.AcceptWebSocketAsync(null).ConfigureAwait(false);
if (ws != null)
{
Task.Run(() => HandleConnectionAsync(ws.WebSocket, token));
}
}
catch (Exception aex)
{
// Log error here
}
}
}
private static async Task HandleConnectionAsync(WebSocket ws, CancellationToken cancellation)
{
try
{
while (ws.State == WebSocketState.Open && !cancellation.IsCancellationRequested)
{
String messageString = await ReadString(ws).ConfigureAwait(false);
var strReply = "OK"; // Process messageString and get your reply here;
var buffer = Encoding.UTF8.GetBytes(strReply);
var segment = new ArraySegment<byte>(buffer);
await ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
}
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None);
}
catch (Exception aex)
{
// Log error
try
{
await ws.CloseAsync(WebSocketCloseStatus.InternalServerError, "Done", CancellationToken.None).ConfigureAwait(false);
}
catch
{
// Do nothing
}
}
finally
{
ws.Dispose();
}
}
private static async Task<String> ReadString(WebSocket ws)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);
WebSocketReceiveResult result = null;
using (var ms = new MemoryStream())
{
do
{
result = await ws.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
#endregion
#region Public Methods
public static void Start(string uri)
{
m_listener = new HttpListener();
m_listener.Prefixes.Add(uri);
m_listener.Start();
m_cancellation = new CancellationTokenSource();
Task.Run(() => AcceptWebSocketClientsAsync(m_listener, m_cancellation.Token));
}
public static void Stop()
{
if(m_listener != null && m_cancellation != null)
{
try
{
m_cancellation.Cancel();
m_listener.Stop();
m_listener = null;
m_cancellation = null;
}
catch
{
// Log error
}
}
}
#endregion
}

Related

C# how to publish mqtt from resolve string

I have a class with project code below. I would like to get the value of Program.Log(GetHallo(result)); and publish this as a message payload to an mqtt broker.
I found a class Publisher with this mqtt code from https://www.youtubecom/watch?v=lcsnsj1yBs0 and I'm wondering how to get the value of Program.Log(GetHallo(result)); from my code and then publish this to mqtt. How can I integrate the code from publisher.cs in my own class that generates the Program.Log(GetHallo(result));
class Publisher
{
static async Task Main(string[] args)
{
var mqttFactory = new MqttFactory();
IMqttClient client = mqttFactory.CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer("192.168.4.33", 1883)
.WithCleanSession()
.Build();
client.UseConnectedHandler(e =>
{
Console.WriteLine("Connected to the broker successfully");
});
client.UseDisconnectedHandler(e =>
{
Console.WriteLine("Disconnected from the broker successfully");
});
await client.ConnectAsync(options);
Console.WriteLine("Please press a key to publish the message");
//Console.ReadLine();
await PublishmessageAsync(client);
await client.DisconnectAsync();
}
private static async Task PublishmessageAsync(IMqttClient client)
{
string messagePayload = "xxxxxxxx!";
var message = new MqttApplicationMessageBuilder()
.WithTopic("RishabhRharma")
.WithPayload(messagePayload)
.WithAtLeastOnceQoS()
.Build();
if (client.IsConnected)
{
await client.PublishAsync(message);
}
}
}
private void Completed(IAsyncResult ar)
{
Action completed = ar.AsyncState as Action;
try
{
var result = new Hallo(this.Panel.EndSend(ar));
Program.Log(GetHallo(result));
}
catch (AtsFaultException e)
{
string message = string.Format("Fault response {0}, {1}", e.Code, e.Message);
Program.Error(message);
}
catch (Exception e)
{
Program.Error(e.Message);
}
finally
{
completed?.Invoke();
}
}

Sockets, Nullreference Exception

I am trying to use web socket with my bot to communicate with the server. But on run time it throws the System.NullReferenceException. I am running socket in background on a different thread so that it does not interfear with the bot.
I am using WebsocketSharp library.
First message comes in just fine but on second message it throws exception at following line in HumanCollaboratorDialog class.
await context.PostAsync(e.Data);
My Socket Stream Class is as following:
public static class SocketStream
{
public static WebSocket ws;
private static List<string> serverMsg = new List<string>();
public static void initializeSocket()
{
ws = new WebSocket("ws://Some IP:8080/human-collaborator/data");
Debug.WriteLine("****** INITIALIZED SOCKET (should happen only once) *****");
Task.Run(() => startSocketStream());
}
private static void startSocketStream()
{
int attempts = 0;
while (!ws.IsAlive)
{
try
{
attempts++;
ws.Connect();
}
catch (WebSocketException)
{
Debug.WriteLine("Connection attempts: " + attempts.ToString());
}
}
ws.OnOpen += (sender, args) =>
{
Debug.WriteLine("# SOCKET OPENED");
};
ws.OnError += (sender, args) =>
{
Debug.WriteLine("# SOME ERROR OCCURED");
};
ws.OnClose += (sender, args) =>
{
Debug.WriteLine("# SOCKET CLOSED");
};
}
}
I am calling the initializeSocket() method in Global.asx to run it on application level
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
SocketStream.initializeSocket();
}
}
My HumanCollaboratorDialog class is as following:
[Serializable]
public class HumanCollaboratorDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
SocketStream.ws.OnMessage += async (sender, e) =>
{
try
{
await context.PostAsync(e.Data);
}
catch (HttpRequestException ex)
{
throw ex;
}
};
Thread.Sleep(500);
string output = message.Text;
SocketStream.ws.Send(output);
Thread.Sleep(500);
context.Wait(MessageReceivedAsync);
}
}
My MessagesController has following POST method:
public virtual async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new HumanCollaboratorDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Neithet e.Data nor context is empty. I think problem is with socket connection or may be i am doing something wrong in SocketStream class. following is the image
Your bot is a web service. Messages are sent to the service by the client (a web page, an application, another service, etc.) and received in the MessagesController's Post method. There's no need to have the socket code on the server for what you're trying to do. Web Sockets are useful for receiving messages on a client from the bot via a Direct Line connection.
Here is an example of using the Bot Framework's Direct Line Client and creating a web socket connection. Notice how the web socket is created from a conversation's StreamUrl:
DirectLineClientCredentials creds = new DirectLineClientCredentials(directLineSecret);
DirectLineClient directLineClient = new DirectLineClient(creds);
Conversation conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
webSocketClient.Connect();
while (true)
{
string input = Console.ReadLine().Trim();
if (input.ToLower() == "exit")
{
break;
}
else
{
if (input.Length > 0)
{
Activity userMessage = new Activity
{
From = new ChannelAccount(fromUser),
Text = input,
Type = ActivityTypes.Message
};
await directLineClient.Conversations.PostActivityAsync(conversation.ConversationId, userMessage);
}
}
}
}
private static void WebSocketClient_OnMessage(object sender, MessageEventArgs e)
{
// avoid null reference exception when no data received
if (string.IsNullOrWhiteSpace(e.Data))
{
return;
}
var activitySet = JsonConvert.DeserializeObject<ActivitySet>(e.Data);
var activities = from x in activitySet.Activities
where x.From.Id == botId
select x;
foreach (Activity activity in activities)
{
Console.WriteLine(activity.Text);
}
}
This is from a console application that is using the Direct Line to communicate with the Bot and is listening for messages using web sockets here:
https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-DirectLineWebSockets

TCP/IP multiple clients connection in C#

I wrote client and server apps, there are those codes:
Client:
using System.Net.Sockets;
using System.Net;
using System.IO;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Client
{
class Program
{
private static TcpClient client = new TcpClient();
private static StreamReader reader;
private static StreamWriter writer;
private static bool refresh;
private static List<string> messages = new List<string>();
public static void Main()
{
Console.Title = "Client";
do //try to connect
{
Console.WriteLine("Connecting to server...");
try
{
client.Connect(IPAddress.Parse("127.0.0.1"), 8080);
}
catch (SocketException) { }
Thread.Sleep(10);
} while (!client.Connected);
// \/ CONNECTED \/
Console.WriteLine("Connected.");
reader = new StreamReader(client.GetStream());
writer = new StreamWriter(client.GetStream());
var sendTask = Task.Run(() => SendMessage()); //task for sending messages
var recieveTask = Task.Run(() => RecieveMessage()); //task for recieving messages
var updateConvTask = Task.Run(() => UpdateConversation()); //task for update console window
Task.WaitAll(sendTask, recieveTask); //wait for end of all tasks
}
private static void SendMessage()
{
string msgToSend = string.Empty;
do
{
Console.WriteLine("Enter a message to send to the server");
msgToSend = Console.ReadLine();
writer.WriteLine(msgToSend);
writer.Flush();
} while (!msgToSend.Equals("Exit"));
EndConnection();
}
private static void RecieveMessage()
{
try
{
while (client.Connected)
{
//Console.Clear();
string msg = reader.ReadLine();
if(msg != string.Empty)
{
if (msg == "%C") //special message from server, clear messages if recieve it
{
messages.Clear();
}
else
{
messages.Add(msg);
refresh = true; //refresh console window
}
}
//Console.Clear();
//Console.WriteLine(msgFromServer);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static void UpdateConversation()
{
//string conversationTmp = string.Empty;
try
{
while (true)
{
if (refresh) //only if refresh
{
refresh = false;
Console.Clear();
messages.ForEach(msg => Console.WriteLine(msg)); //write all messages
Console.WriteLine();
}
}
}
catch (Exception) { }
}
private static void EndConnection()
{
reader.Close();
writer.Close();
client.Close();
}
}
}
Server:
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Server
{
class Program
{
private static List<Client> clients = new List<Client>();
private static TcpListener listener = null;
private static StreamReader reader = null;
private static StreamWriter writer = null;
private static List<Task> clientTasks = new List<Task>();
private static List<string> messages = new List<string>();
public static void Main()
{
Console.Title = "Server";
try
{
listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
listener.Start();
Console.WriteLine("Server started...");
var connectTask = Task.Run(() => ConnectClients());
//var listenTask = Task.Run(() => ListenClients());
Task.WaitAll(connectTask);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
if (listener != null)
{
listener.Stop();
}
}
}
private static void ConnectClients()
{
Console.WriteLine("Waiting for incoming client connections...");
while (true)
{
if (listener.Pending()) //if someone want to connect
{
clients.Add(new Client(listener.AcceptTcpClient(), "Client: " + (clients.Count + 1)));
Console.WriteLine(clients[clients.Count - 1].clientName + " connected to server.");
clientTasks.Add(Task.Run(() => HandleClient(clients[clients.Count - 1]))); //start new task for new client
}
}
}
private static void HandleClient(Client TCPClient)
{
string s = string.Empty;
writer = new StreamWriter(TCPClient.client.GetStream());
reader = new StreamReader(TCPClient.client.GetStream());
try
{
while (!(s = reader.ReadLine()).Equals("Exit") || (s == null))
{
if(!TCPClient.client.Connected)
{
Console.WriteLine("Client disconnected.");
clients.Remove(TCPClient);
}
Console.WriteLine("From client: " + TCPClient.clientName + " -> " + s);
messages.Add(TCPClient.clientName + ": " + s); //save new message
//Console.WriteLine(s);
foreach (Client c in clients) //refresh all connected clients
{
c.writer.WriteLine("%C"); //clear client
foreach (string msg in messages)
{
c.writer.WriteLine(msg);
c.writer.Flush();
}
}
}
CloseServer();
}
catch (Exception e) { Console.WriteLine(e); }
}
private static void CloseServer()
{
reader.Close();
writer.Close();
clients.ForEach(tcpClient => tcpClient.client.Close());
}
}
}
Client class code:
class Client
{
public TcpClient client;
public StreamWriter writer; //write to client
public string clientName;
public Client(TcpClient client, string clientName)
{
this.client = client;
reader = new StreamReader(client.GetStream());
writer = new StreamWriter(client.GetStream());
this.clientName = clientName;
}
}
Clients send messages to the server that store all of them and next send back the entire conversation to all clients.
Client class contains information about the connected client, a server has a list of Client instances.
For one client it works well, but when I have two clients, in begin it works too, but after a few messages one client send messages to them two, and another client can't send messages.
Please help, I'm new in TCP communication.
I think that you would synchronize Tasks: https://www.google.pl/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=synchronizing+tasks+c%23

How to work with System.Net.WebSockets without ASP.NET?

I want to implement a simple chat server with the new System.Net.WebSockets classes in .NET 4.5 and later (on Windows 8.1). However, I only find examples making use of those classes in an ASP.NET environment (especially the ones here: http://www.codemag.com/Article/1210051)
I don't have such one, and would like to implement the websocket server as "raw" as possible, but without having to reimplement all the websocket protocol as Microsoft hopefully already did that in .NET 4.5.
I thought of simply instantiating a new WebSocket class like I'd do with a normal Socket, but the constructor is protected. So I went to create a class inheriting from it, but then I noticed I had to implement so many abstract methods and properties that it looked like I'm rewriting the whole logic (especially because I had to implement things like State or SendAsync).
I'm afraid that the MSDN documentation didn't help me. The documentation there has a pre-release status and many comments just say "TBD" or "when its implemented".
Yes.
The easiest way is to use an HTTPListener. If you search for HTTPListener WebSocket you'll find plenty of examples.
In a nutshell (pseudo-code)
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add("http://localhost/");
httpListener.Start();
HttpListenerContext context = await httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync( ... );
}
}
Requires .NET 4.5 and Windows 8 or later.
I just stumbled on this link that shows how to implement a IHttpHandler using just the System.Net.WebSockets implementation. The handler is required as the .NET WebSocket implementation is dependent on IIS 8+.
using System;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
namespace AspNetWebSocketEcho
{
public class EchoHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
context.AcceptWebSocketRequest(HandleWebSocket);
else
context.Response.StatusCode = 400;
}
private async Task HandleWebSocket(WebSocketContext wsContext)
{
const int maxMessageSize = 1024;
byte[] receiveBuffer = new byte[maxMessageSize];
WebSocket socket = wsContext.WebSocket;
while (socket.State == WebSocketState.Open)
{
WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else if (receiveResult.MessageType == WebSocketMessageType.Binary)
{
await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept binary frame", CancellationToken.None);
}
else
{
int count = receiveResult.Count;
while (receiveResult.EndOfMessage == false)
{
if (count >= maxMessageSize)
{
string closeMessage = string.Format("Maximum message size: {0} bytes.", maxMessageSize);
await socket.CloseAsync(WebSocketCloseStatus.MessageTooLarge, closeMessage, CancellationToken.None);
return;
}
receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer, count, maxMessageSize - count), CancellationToken.None);
count += receiveResult.Count;
}
var receivedString = Encoding.UTF8.GetString(receiveBuffer, 0, count);
var echoString = "You said " + receivedString;
ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(echoString));
await socket.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
public bool IsReusable
{
get { return true; }
}
}
}
Hope it helped!
Ian's answer definitely was good, but I needed a loop process. The mutex was key for me. This is a working .net core 2 example based on his. I can't speak to scalability of this loop.
using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace WebSocketServerConsole
{
public class Program
{
static HttpListener httpListener = new HttpListener();
private static Mutex signal = new Mutex();
public static void Main(string[] args)
{
httpListener.Prefixes.Add("http://localhost:8080/");
httpListener.Start();
while (signal.WaitOne())
{
ReceiveConnection();
}
}
public static async System.Threading.Tasks.Task ReceiveConnection()
{
HttpListenerContext context = await
httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello world")),
WebSocketMessageType.Text, true, CancellationToken.None);
}
}
signal.ReleaseMutex();
}
}
}
and a test html page for it.
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:8080/";
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
}
function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
}
function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>
Here is my complete working example...
Start up a host
namespace ConsoleApp1;
public static class Program
{
public static async Task Main(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<Server>();
services.AddHostedService<Server>();
});
IHost host = hostBuilder.Build();
await host.RunAsync();
}
}
Create a server to accept clients and talk to them
using ConsoleApp15.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.WebSockets;
using System.Text;
namespace ConsoleApp15;
public class Server : IHostedService
{
private readonly ILogger<Server> Logger;
private readonly HttpListener HttpListener = new();
public Server(ILogger<Server> logger)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
HttpListener.Prefixes.Add("http://localhost:8080/");
}
public async Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Started");
HttpListener.Start();
while (!cancellationToken.IsCancellationRequested)
{
HttpListenerContext? context = await HttpListener.GetContextAsync().WithCancellationToken(cancellationToken);
if (context is null)
return;
if (!context.Request.IsWebSocketRequest)
context.Response.Abort();
else
{
HttpListenerWebSocketContext? webSocketContext =
await context.AcceptWebSocketAsync(subProtocol: null).WithCancellationToken(cancellationToken);
if (webSocketContext is null)
return;
string clientId = Guid.NewGuid().ToString();
WebSocket webSocket = webSocketContext.WebSocket;
_ = Task.Run(async() =>
{
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
await webSocket.SendAsync(
Encoding.ASCII.GetBytes($"Hello {clientId}\r\n"),
WebSocketMessageType.Text,
endOfMessage: true,
cancellationToken);
}
});
_ = Task.Run(async() =>
{
byte[] buffer = new byte[1024];
var stringBuilder = new StringBuilder(2048);
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
WebSocketReceiveResult receiveResult =
await webSocket.ReceiveAsync(buffer, cancellationToken);
if (receiveResult.Count == 0)
return;
stringBuilder.Append(Encoding.ASCII.GetString(buffer, 0, receiveResult.Count));
if (receiveResult.EndOfMessage)
{
Console.WriteLine($"{clientId}: {stringBuilder}");
stringBuilder = new StringBuilder();
}
}
});
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Stopping...");
HttpListener.Stop();
Logger.LogInformation("Stopped");
return Task.CompletedTask;
}
}
Create a WithCancellationToken for the Async methods that don't accept a CancellationToken parameter. This is so the server shuts down gracefully when told to.
namespace ConsoleApp15.Extensions;
public static class TaskExtensions
{
public static async Task<T?> WithCancellationToken<T>(this Task<T> source, CancellationToken cancellationToken)
{
var cancellationTask = new TaskCompletionSource<bool>();
cancellationToken.Register(() => cancellationTask.SetCanceled());
_ = await Task.WhenAny(source, cancellationTask.Task);
if (cancellationToken.IsCancellationRequested)
return default;
return source.Result;
}
}
Start the Postman app
File => New
Select "WebSocket request"
Enter the following as the url ws://localhost:8080/
Click [Connect]

Connecting Silverlight client to SignalR server

I've been driving myself nuts trying to resolve this issue so really hoping someone has some insight.
I have a console application which runs/hosts my signalR server.
I have already successfully connected to it using a web(javascript) client and a windows forms client with no trouble at all.
BUT for the life of me I cannot get a silverlight client to connect to it. Initially I was getting a
'System.Security.SecurityException' occurred in Microsoft.Threading.Tasks error
on
await Connection.Start();
I managed to fix that by force sending the clientaccesspolicy file using code i found on a random thread.
THREAD
However the connection still never establishes. The status goes thru connecting, disconnected, connection closed.
I am at my wits end as to why this won't work. Any input is appreciated. Code below.
MainPage.xaml.cs
public partial class MainPage : UserControl
{
private SignalRClient client;
public MainPage()
{
InitializeComponent();
dataGrid1.ItemsSource = new ItemsCollection();
client = new SignalRClient();
client.RunAsync();
Debug.WriteLine("Init Done");
}
}
-
SignalRClient.cs
public class SignalRClient
{
private HubConnection Connection { get; set; }
private IHubProxy HubProxy { get; set; }
const string url = "http://localhost:8080/";
public SignalRClient()
{
}
public async void RunAsync()
{
Connection = new HubConnection(url, useDefaultUrl: true);
Connection.Closed += Connection_Closed;
Connection.StateChanged += ConnectionDidSomething;
HubProxy = Connection.CreateHubProxy("TickerHub");
HubProxy.On<string>("receiveAllData", data => Debug.WriteLine("RECDATA={0}", data));
try
{
await Connection.Start();
}
catch (HttpClientException e)
{
Debug.WriteLine("Unable to connect to server.1 {0}", e.Message);
return;
}
catch (HttpRequestException e)
{
Debug.WriteLine("Unable to connect to server.2 {0}", e.Message);
return;
}
}
-
Server
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8080/";
using (WebApp.Start(url))
{
Console.WriteLine("SignalR server running on {0}", url);
Console.ReadLine();
}
Console.ReadLine();
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
Console.WriteLine("Configuration");
//Tried this approach too
/*app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableJSONP = true
};
map.RunSignalR(hubConfiguration);
});*/
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR<ClientAccessPolicyConnection>("/clientaccesspolicy.xml");
}
}
-
TickerHub.cs
public class TickerHub : Hub
{
public override Task OnConnected()
{
string connectionID = Context.ConnectionId;
Console.WriteLine("New Connection:" + connectionID);
InitNewClient(connectionID);
return base.OnConnected();
}
//send all data to newly connected client
public void InitNewClient(string connectionID)
{
}
//client requested all data
public void GetAllData()
{
Console.WriteLine("Get Data Triggered");
Clients.All.receiveAllData("TESTING123");
}
}
I figured it out! Hopefully this helps someone in the future.
Its quite simple. This is what you need to have in your startup class configuration method.
Below that is the code required to send the clientaccesspolicy.xml.
class Startup
{
public void Configuration(IAppBuilder app)
{
// Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR<ClientAccessPolicyConnection>("/clientaccesspolicy.xml");
}
}
-
public class ClientAccessPolicyConnection : PersistentConnection
{
public override Task ProcessRequest(Microsoft.AspNet.SignalR.Hosting.HostContext context)
{
string[] urlArray = context.Request.Url.ToString().Split('/');
string path = urlArray[urlArray.Length - 1];
if (path.Equals("clientaccesspolicy.xml", StringComparison.InvariantCultureIgnoreCase))
{
//Convert policy to byteArray
var array = Encoding.UTF8.GetBytes(ClientAccessPolicy);
var segment = new ArraySegment<byte>(array);
//Write response
context.Response.ContentType = "text/xml";
context.Response.Write(segment);
//Return empty task to escape from SignalR's default Connection/Transport checks.
return EmptyTask;
}
return EmptyTask;
}
private static readonly Task EmptyTask = MakeTask<object>(null);
public static Task<T> MakeTask<T>(T value)
{
var tcs = new TaskCompletionSource<T>();
tcs.SetResult(value);
return tcs.Task;
}
public static readonly string ClientAccessPolicy =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<access-policy>"
+ "<cross-domain-access>"
+ "<policy>"
+ "<allow-from http-request-headers=\"*\">"
+ "<domain uri=\"*\"/>"
+ "</allow-from>"
+ "<grant-to>"
+ "<resource path=\"/\" include-subpaths=\"true\"/>"
+ "</grant-to>"
+ "</policy>"
+ "</cross-domain-access>"
+ "</access-policy>";
}

Categories