mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-24 03:56:32 +00:00
Implemented websocket protocol and basic echo websocket module.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using MTSC.Common.Http.ServerModules;
|
||||
using MTSC.Common.WebSockets.ServerModules;
|
||||
using MTSC.Exceptions;
|
||||
using MTSC.Logging;
|
||||
using MTSC.Server;
|
||||
@@ -26,7 +27,7 @@ namespace MTSC_TestServer
|
||||
.AddLogger(new DebugConsoleLogger())
|
||||
.AddExceptionHandler(new ExceptionConsoleLogger())
|
||||
//.AddHandler(new BroadcastHandler())
|
||||
.AddHandler(new HttpHandler().AddHttpModule(new HelloWorldModule()))
|
||||
.AddHandler(new WebsocketHandler().AddWebsocketHandler(new EchoModule()))
|
||||
.Run();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MTSC.Server;
|
||||
using MTSC.Server.Handlers;
|
||||
|
||||
namespace MTSC.Common.WebSockets.ServerModules
|
||||
{
|
||||
public class EchoModule : IWebsocketModule
|
||||
{
|
||||
#region Public Methods
|
||||
public void SendMessage(WebsocketHandler handler, ClientData client, string message)
|
||||
{
|
||||
byte[] encodedMessage = EncodeMessage(message);
|
||||
handler.QueueMessage(client, encodedMessage);
|
||||
}
|
||||
#endregion
|
||||
#region Private Methods
|
||||
private string DecodeMessage(byte[] bytes)
|
||||
{
|
||||
byte secondByte = bytes[1];
|
||||
int dataLength = secondByte & 127;
|
||||
int indexFirstMask = 2;
|
||||
if (dataLength == 126)
|
||||
indexFirstMask = 4;
|
||||
else if (dataLength == 127)
|
||||
indexFirstMask = 10;
|
||||
|
||||
IEnumerable<byte> keys = bytes.Skip(indexFirstMask).Take(4);
|
||||
int indexFirstDataByte = indexFirstMask + 4;
|
||||
|
||||
byte[] decoded = new byte[bytes.Length - indexFirstDataByte];
|
||||
for (int i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
|
||||
{
|
||||
decoded[j] = (byte)(bytes[i] ^ keys.ElementAt(j % 4));
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(decoded, 0, decoded.Length);
|
||||
}
|
||||
private static byte[] EncodeMessage(string message)
|
||||
{
|
||||
byte[] response;
|
||||
byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
|
||||
byte[] frame = new byte[10];
|
||||
|
||||
int indexStartRawData = -1;
|
||||
int length = bytesRaw.Length;
|
||||
|
||||
frame[0] = (byte)129;
|
||||
if (length <= 125)
|
||||
{
|
||||
frame[1] = (byte)length;
|
||||
indexStartRawData = 2;
|
||||
}
|
||||
else if (length >= 126 && length <= 65535)
|
||||
{
|
||||
frame[1] = (byte)126;
|
||||
frame[2] = (byte)((length >> 8) & 255);
|
||||
frame[3] = (byte)(length & 255);
|
||||
indexStartRawData = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame[1] = (byte)127;
|
||||
frame[2] = (byte)((length >> 56) & 255);
|
||||
frame[3] = (byte)((length >> 48) & 255);
|
||||
frame[4] = (byte)((length >> 40) & 255);
|
||||
frame[5] = (byte)((length >> 32) & 255);
|
||||
frame[6] = (byte)((length >> 24) & 255);
|
||||
frame[7] = (byte)((length >> 16) & 255);
|
||||
frame[8] = (byte)((length >> 8) & 255);
|
||||
frame[9] = (byte)(length & 255);
|
||||
|
||||
indexStartRawData = 10;
|
||||
}
|
||||
|
||||
response = new byte[indexStartRawData + length];
|
||||
|
||||
int i, reponseIdx = 0;
|
||||
|
||||
//Add the frame bytes to the reponse
|
||||
for (i = 0; i < indexStartRawData; i++)
|
||||
{
|
||||
response[reponseIdx] = frame[i];
|
||||
reponseIdx++;
|
||||
}
|
||||
|
||||
//Add the data bytes to the response
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
response[reponseIdx] = bytesRaw[i];
|
||||
reponseIdx++;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
#endregion
|
||||
#region Interface Implementation
|
||||
bool IWebsocketModule.HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes)
|
||||
{
|
||||
string receivedMessage = DecodeMessage(messageBytes);
|
||||
SendMessage((WebsocketHandler)handler, client, receivedMessage);
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using MTSC.Server;
|
||||
using MTSC.Server.Handlers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Common.WebSockets.ServerModules
|
||||
{
|
||||
public interface IWebsocketModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle a received message.
|
||||
/// </summary>
|
||||
/// <param name="handler">Handler that currently processes the message.</param>
|
||||
/// <param name="messageBytes">Bytes of the message.</param>
|
||||
/// <param name="client">Client data.</param>
|
||||
/// <returns>True if no other module should process the message.</returns>
|
||||
bool HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes);
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,8 @@
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Common\WebSockets\ClientModules\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
using MTSC.Common.Http;
|
||||
using MTSC.Common.WebSockets.ServerModules;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Server.Handlers
|
||||
{
|
||||
public class WebsocketHandler : IHandler
|
||||
{
|
||||
private static string WebsocketHeaderAcceptKey = "Sec-WebSocket-Accept";
|
||||
private static string WebsocketHeaderKey = "Sec-WebSocket-Key";
|
||||
private static string WebsocketProtocolKey = "Sec-WebSocket-Protocol";
|
||||
private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
|
||||
private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
private static SHA1 sha1Provider = SHA1.Create();
|
||||
private enum SocketState
|
||||
{
|
||||
Initial,
|
||||
Handshaking,
|
||||
Established,
|
||||
Closed
|
||||
}
|
||||
ConcurrentDictionary<ClientData, SocketState> webSockets = new ConcurrentDictionary<ClientData, SocketState>();
|
||||
ConcurrentQueue<Tuple<ClientData, byte[]>> messageQueue = new ConcurrentQueue<Tuple<ClientData, byte[]>>();
|
||||
List<IWebsocketModule> websocketModules = new List<IWebsocketModule>();
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Add a webSocket module onto the server.
|
||||
/// </summary>
|
||||
/// <param name="module">Module to be added.</param>
|
||||
/// <returns>This handler object.</returns>
|
||||
public WebsocketHandler AddWebsocketHandler(IWebsocketModule module)
|
||||
{
|
||||
this.websocketModules.Add(module);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Send a message to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to be sent to client.</param>
|
||||
public void QueueMessage(ClientData client, byte[] message)
|
||||
{
|
||||
messageQueue.Enqueue(new Tuple<ClientData, byte[]>(client, message));
|
||||
}
|
||||
#endregion
|
||||
#region Handler Implementation
|
||||
void IHandler.ClientRemoved(Server server, ClientData client)
|
||||
{
|
||||
SocketState state = SocketState.Initial;
|
||||
webSockets.TryRemove(client, out state);
|
||||
}
|
||||
|
||||
bool IHandler.HandleClient(Server server, ClientData client)
|
||||
{
|
||||
webSockets[client] = SocketState.Initial;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message)
|
||||
{
|
||||
if (webSockets[client] == SocketState.Initial)
|
||||
{
|
||||
HttpMessage request = new HttpMessage();
|
||||
request.ParseRequest(message.MessageBytes);
|
||||
if(request.Method == HttpMessage.MethodEnum.Get &&
|
||||
request[HttpMessage.GeneralHeadersEnum.Connection].ToLower() == "upgrade" &&
|
||||
request[WebsocketProtocolVersionKey] == "13")
|
||||
{
|
||||
/*
|
||||
* Prepare the handshake string.
|
||||
*/
|
||||
string base64Key = request[WebsocketHeaderKey];
|
||||
base64Key = base64Key.Trim();
|
||||
string handshakeKey = base64Key + GlobalUniqueIdentifier;
|
||||
string returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey)));
|
||||
|
||||
/*
|
||||
* Prepare the response.
|
||||
*/
|
||||
HttpMessage response = new HttpMessage();
|
||||
response.StatusCode = HttpMessage.StatusCodes.SwitchingProtocols;
|
||||
response[HttpMessage.GeneralHeadersEnum.Upgrade] = "websocket";
|
||||
response[HttpMessage.GeneralHeadersEnum.Connection] = "Upgrade";
|
||||
response[WebsocketHeaderAcceptKey] = returnBase64Key;
|
||||
server.QueueMessage(client, response.GetResponse(true));
|
||||
webSockets[client] = SocketState.Established;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(webSockets[client] == SocketState.Established)
|
||||
{
|
||||
foreach(IWebsocketModule websocketModule in websocketModules)
|
||||
{
|
||||
if(websocketModule.HandleReceivedMessage(this, client, message.MessageBytes))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IHandler.HandleSendMessage(Server server, ClientData client, ref Message message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IHandler.PreHandleReceivedMessage(Server server, ClientData client, ref Message message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void IHandler.Tick(Server server)
|
||||
{
|
||||
while (messageQueue.Count > 0)
|
||||
{
|
||||
Tuple<ClientData, byte[]> tuple = null;
|
||||
if (messageQueue.TryDequeue(out tuple))
|
||||
{
|
||||
server.QueueMessage(tuple.Item1, tuple.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+12
-8
@@ -2,6 +2,7 @@
|
||||
using MTSC.Logging;
|
||||
using MTSC.Server.Handlers;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
@@ -28,7 +29,7 @@ namespace MTSC.Server
|
||||
List<IHandler> handlers = new List<IHandler>();
|
||||
List<ILogger> loggers = new List<ILogger>();
|
||||
List<IExceptionHandler> exceptionHandlers = new List<IExceptionHandler>();
|
||||
Queue<Tuple<ClientData, byte[]>> messageQueue = new Queue<Tuple<ClientData, byte[]>>();
|
||||
ConcurrentQueue<Tuple<ClientData, byte[]>> messageQueue = new ConcurrentQueue<Tuple<ClientData, byte[]>>();
|
||||
int loopMillis = 16;
|
||||
#endregion
|
||||
#region Properties
|
||||
@@ -325,15 +326,18 @@ namespace MTSC.Server
|
||||
{
|
||||
while (messageQueue.Count > 0)
|
||||
{
|
||||
Tuple<ClientData, byte[]> queuedOrder = messageQueue.Dequeue();
|
||||
Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2);
|
||||
for (int i = handlers.Count - 1; i >= 0; i--)
|
||||
Tuple<ClientData, byte[]> queuedOrder = null;
|
||||
if (messageQueue.TryDequeue(out queuedOrder))
|
||||
{
|
||||
IHandler handler = handlers[i];
|
||||
ClientData client = queuedOrder.Item1;
|
||||
handler.HandleSendMessage(this, client, ref sendMessage);
|
||||
Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2);
|
||||
for (int i = handlers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
IHandler handler = handlers[i];
|
||||
ClientData client = queuedOrder.Item1;
|
||||
handler.HandleSendMessage(this, client, ref sendMessage);
|
||||
}
|
||||
CommunicationPrimitives.SendMessage(queuedOrder.Item1.TcpClient, sendMessage, queuedOrder.Item1.SslStream);
|
||||
}
|
||||
CommunicationPrimitives.SendMessage(queuedOrder.Item1.TcpClient, sendMessage, queuedOrder.Item1.SslStream);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>WebSocket Test</title>
|
||||
<script language="javascript" type="text/javascript">
|
||||
|
||||
var wsUri = "ws://localhost:80";
|
||||
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>');
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user