mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-15 14:59:33 +00:00
Implementing standard websocket packet.
This commit is contained in:
@@ -27,7 +27,7 @@ namespace MTSC_TestServer
|
||||
.AddLogger(new DebugConsoleLogger())
|
||||
.AddExceptionHandler(new ExceptionConsoleLogger())
|
||||
//.AddHandler(new BroadcastHandler())
|
||||
.AddHandler(new WebsocketHandler().AddWebsocketHandler(new EchoModule()))
|
||||
.AddHandler(new WebsocketHandler().AddWebsocketHandler(new BroadcastModule()))
|
||||
.AddHandler(new HttpHandler().AddHttpModule(new HelloWorldModule()))
|
||||
.Run();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MTSC.Common.Http;
|
||||
using MTSC.Common.WebSockets;
|
||||
using MTSC.Common.WebSockets.ClientModules;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -84,9 +85,10 @@ namespace MTSC.Client.Handlers
|
||||
}
|
||||
else if(state == SocketState.Established)
|
||||
{
|
||||
WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes);
|
||||
foreach(IWebsocketModule websocketModule in websocketModules)
|
||||
{
|
||||
if(websocketModule.HandleReceivedMessage(client, this, message.MessageBytes))
|
||||
if(websocketModule.HandleReceivedMessage(client, this, receivedMessage))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ namespace MTSC.Common.WebSockets.ClientModules
|
||||
#region Public Methods
|
||||
public void SendMessage(WebsocketHandler websocketHandler, string message)
|
||||
{
|
||||
byte[] encodedMessage = WebsocketHelper.EncodeMessage(message, true);
|
||||
byte[] encodedMessage = WebsocketHelper.EncodeTextMessage(message, true);
|
||||
websocketHandler.QueueMessage(encodedMessage);
|
||||
}
|
||||
#endregion
|
||||
#region Interface Implementation
|
||||
bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes)
|
||||
bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage receivedMessage)
|
||||
{
|
||||
string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
|
||||
client.Log(">" + receivedMessage);
|
||||
string messageString = Encoding.UTF8.GetString(receivedMessage.Data);
|
||||
client.Log(">" + messageString);
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace MTSC.Common.WebSockets.ClientModules
|
||||
/// <param name="handler">Handler currently processing the message.</param>
|
||||
/// <param name="messageBytes">Array containing the message.</param>
|
||||
/// <returns>True if no other module should handle this message.</returns>
|
||||
bool HandleReceivedMessage(Client.Client client, IHandler handler, byte[] messageBytes);
|
||||
bool HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage messageBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MTSC.Server;
|
||||
using MTSC.Server.Handlers;
|
||||
|
||||
namespace MTSC.Common.WebSockets.ServerModules
|
||||
{
|
||||
public class BroadcastModule : IWebsocketModule
|
||||
{
|
||||
void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage)
|
||||
{
|
||||
receivedMessage.Masked = false;
|
||||
foreach(ClientData otherClient in server.Clients)
|
||||
{
|
||||
(handler as WebsocketHandler).QueueMessage(otherClient, receivedMessage.Data);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,28 @@ namespace MTSC.Common.WebSockets.ServerModules
|
||||
public class EchoModule : IWebsocketModule
|
||||
{
|
||||
#region Public Methods
|
||||
public void SendMessage(WebsocketHandler handler, ClientData client, string message)
|
||||
public void SendMessage(WebsocketHandler handler, ClientData client, WebsocketMessage message)
|
||||
{
|
||||
byte[] encodedMessage = WebsocketHelper.EncodeMessage(message);
|
||||
byte[] encodedMessage = message.GetMessageBytes();
|
||||
handler.QueueMessage(client, encodedMessage);
|
||||
}
|
||||
#endregion
|
||||
#region Interface Implementation
|
||||
bool IWebsocketModule.HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes)
|
||||
bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage)
|
||||
{
|
||||
string receivedMessage = WebsocketHelper.DecodeMessage(messageBytes);
|
||||
SendMessage((WebsocketHandler)handler, client, receivedMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,13 @@ namespace MTSC.Common.WebSockets.ServerModules
|
||||
{
|
||||
public interface IWebsocketModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a connection has been initialized.
|
||||
/// </summary>
|
||||
/// <param name="server">Server object.</param>
|
||||
/// <param name="handler">Handler currently processing.</param>
|
||||
/// <param name="client">Client object.</param>
|
||||
void ConnectionInitialized(Server.Server server, IHandler handler, ClientData client);
|
||||
/// <summary>
|
||||
/// Handle a received message.
|
||||
/// </summary>
|
||||
@@ -15,6 +22,13 @@ namespace MTSC.Common.WebSockets.ServerModules
|
||||
/// <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);
|
||||
bool HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage);
|
||||
/// <summary>
|
||||
/// Called when a connection has been closed.
|
||||
/// </summary>
|
||||
/// <param name="server">Server object.</param>
|
||||
/// <param name="handler">Handler currently processing.</param>
|
||||
/// <param name="client">Client object.</param>
|
||||
void ConnectionClosed(Server.Server server, IHandler handler, ClientData client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace MTSC.Common.WebSockets
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array containing the received message.</param>
|
||||
/// <returns>String containing the received message.</returns>
|
||||
public static string DecodeMessage(byte[] bytes)
|
||||
public static string DecodeTextMessage(byte[] bytes)
|
||||
{
|
||||
byte b = bytes[1];
|
||||
int dataLength = 0;
|
||||
@@ -72,7 +72,7 @@ namespace MTSC.Common.WebSockets
|
||||
/// </summary>
|
||||
/// <param name="message">Message to encode.</param>
|
||||
/// <returns>Byte array containing the encoded message.</returns>
|
||||
public static byte[] EncodeMessage(string message, bool masked = false)
|
||||
public static byte[] EncodeTextMessage(string message, bool masked = false)
|
||||
{
|
||||
byte[] response;
|
||||
byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using MTSC.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Common.WebSockets
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing the bytes of a websocket received message.
|
||||
/// </summary>
|
||||
public class WebsocketMessage
|
||||
{
|
||||
public enum Opcodes
|
||||
{
|
||||
Data = 1,
|
||||
Text = 2,
|
||||
Close = 8,
|
||||
Ping = 9,
|
||||
Pong = 10
|
||||
}
|
||||
|
||||
byte controlByte = new byte();
|
||||
byte[] lengthBytes;
|
||||
byte[] data;
|
||||
/// <summary>
|
||||
/// FIN bit.
|
||||
/// </summary>
|
||||
public bool FIN { get => (controlByte & 0x80) == 0x80; set => controlByte = (byte)(value? controlByte | 0x80 : controlByte & 0x7F);}
|
||||
/// <summary>
|
||||
/// Frame Opcode
|
||||
/// </summary>
|
||||
/// <remarks>Gets and sets the 4 lower bits of the first byte.</remarks>
|
||||
public Opcodes Opcode { get => (Opcodes)(controlByte & 0xF);
|
||||
set => controlByte = (byte)((controlByte & 0xF0) | ((int)value & 0xF)); }
|
||||
/// <summary>
|
||||
/// Mask bit.
|
||||
/// </summary>
|
||||
public bool Masked { get => (lengthBytes[0] & 0x80) == 0x1; set => lengthBytes[0] = (byte)(value ? lengthBytes[0] | 0x80 : lengthBytes[0] & 0x7F); }
|
||||
/// <summary>
|
||||
/// Length of message.
|
||||
/// </summary>
|
||||
public ulong MessageLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((lengthBytes[0] & 0x7F) <= 125)
|
||||
{
|
||||
return (ulong)(lengthBytes[0] & 0x7F);
|
||||
}
|
||||
else if ((lengthBytes[0] & 0x7F) == 126)
|
||||
{
|
||||
return (ulong)((lengthBytes[2] << 8) + lengthBytes[1]);
|
||||
}
|
||||
else if ((lengthBytes[0] & 0x7F) == 127)
|
||||
{
|
||||
return (ulong)((lengthBytes[8] << 56) + (lengthBytes[7] << 48) + (lengthBytes[6] << 40) + (lengthBytes[5] << 32) +
|
||||
(lengthBytes[4] << 24) + (lengthBytes[3] << 16) + (lengthBytes[2] << 8) + lengthBytes[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if(value <= 125)
|
||||
{
|
||||
if(lengthBytes.Length != 1)
|
||||
{
|
||||
byte[] newLengthBytes = new byte[1];
|
||||
newLengthBytes[0] = lengthBytes[0];
|
||||
lengthBytes = newLengthBytes;
|
||||
}
|
||||
lengthBytes[0] = (byte)((lengthBytes[0] & 0x80) | ((byte)value & 0x7F));
|
||||
}
|
||||
else if(value <= UInt16.MaxValue)
|
||||
{
|
||||
if (lengthBytes.Length != 3)
|
||||
{
|
||||
byte[] newLengthBytes = new byte[3];
|
||||
newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80);
|
||||
newLengthBytes[0] += 126;
|
||||
lengthBytes = newLengthBytes;
|
||||
}
|
||||
for(int i = 1; i < 3; i++)
|
||||
{
|
||||
lengthBytes[i] = (byte)(value & 0xFF);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
else if(value <= UInt64.MaxValue)
|
||||
{
|
||||
if (lengthBytes.Length != 8)
|
||||
{
|
||||
byte[] newLengthBytes = new byte[9];
|
||||
newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80);
|
||||
newLengthBytes[0] += 127;
|
||||
lengthBytes = newLengthBytes;
|
||||
}
|
||||
for (int i = 1; i < 9; i++)
|
||||
{
|
||||
lengthBytes[i] = (byte)(value & 0xFF);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Mask of the message.
|
||||
/// </summary>
|
||||
public byte[] Mask { get; set; }
|
||||
/// <summary>
|
||||
/// Data of the message.
|
||||
/// </summary>
|
||||
public byte[] Data
|
||||
{
|
||||
get => data;
|
||||
set
|
||||
{
|
||||
MessageLength = (ulong)value.Length;
|
||||
data = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of websocket message containing the given message.
|
||||
/// </summary>
|
||||
/// <param name="messageBytes">Byte array containing the message bytes.</param>
|
||||
public WebsocketMessage(byte[] messageBytes)
|
||||
{
|
||||
controlByte = messageBytes[0];
|
||||
Mask = new byte[4];
|
||||
ulong dataLength = 0;
|
||||
int dataIndex = 0;
|
||||
if ((messageBytes[1] & 0x7F) <= 125)
|
||||
{
|
||||
lengthBytes = new byte[1];
|
||||
lengthBytes[0] = messageBytes[1];
|
||||
dataLength = (ulong)(messageBytes[1] & 0x7F);
|
||||
dataIndex = 2;
|
||||
}
|
||||
else if ((messageBytes[1] & 0x7F) == 126)
|
||||
{
|
||||
dataLength = (ulong)((messageBytes[3] << 8) + messageBytes[2]);
|
||||
lengthBytes = new byte[3];
|
||||
Array.Copy(messageBytes, 1, lengthBytes, 0, 3);
|
||||
dataIndex = 4;
|
||||
}
|
||||
else if ((messageBytes[1] & 0x7F) == 127)
|
||||
{
|
||||
dataLength = (ulong)((messageBytes[9] << 56) + (messageBytes[8] << 48) + (messageBytes[7] << 40) + (messageBytes[6] << 32) +
|
||||
(messageBytes[5] << 24) + (messageBytes[4] << 16) + (messageBytes[3] << 8) + messageBytes[2]);
|
||||
dataIndex = 10;
|
||||
lengthBytes = new byte[9];
|
||||
Array.Copy(messageBytes, 1, lengthBytes, 0, 9);
|
||||
}
|
||||
else
|
||||
{
|
||||
lengthBytes = new byte[0];
|
||||
throw new InvalidWebsocketFormatException("Length formatting is wrong");
|
||||
}
|
||||
if ((messageBytes[1] & 0x80) > 0)
|
||||
{
|
||||
Array.Copy(messageBytes, dataIndex, Mask, 0, 4);
|
||||
dataIndex += 4;
|
||||
}
|
||||
data = new byte[messageBytes.Length - dataIndex];
|
||||
for(ulong i = 0; i < dataLength; i++)
|
||||
{
|
||||
data[i] = (byte)(messageBytes[(ulong)dataIndex + i] ^ Mask[i % 4]);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new instance of websocket message.
|
||||
/// </summary>
|
||||
public WebsocketMessage()
|
||||
{
|
||||
controlByte = new byte();
|
||||
Mask = new byte[4];
|
||||
data = new byte[0];
|
||||
lengthBytes = new byte[1];
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the packed message bytes.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the message.</returns>
|
||||
public byte[] GetMessageBytes()
|
||||
{
|
||||
if(data == null)
|
||||
{
|
||||
throw new NoDataException("There is no data in the message");
|
||||
}
|
||||
byte[] messageBytes = new byte[1 + lengthBytes.Length + (Masked ? 4 : 0) + data.Length];
|
||||
messageBytes[0] = controlByte;
|
||||
Array.Copy(lengthBytes, 0, messageBytes, 1, lengthBytes.Length);
|
||||
if (Masked)
|
||||
{
|
||||
Array.Copy(Mask, 0, messageBytes, lengthBytes.Length, Mask.Length);
|
||||
}
|
||||
Array.Copy(data, 0, messageBytes, lengthBytes.Length + (Masked ? 4 : 0), data.Length);
|
||||
return messageBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Exceptions
|
||||
{
|
||||
public class InvalidWebsocketFormatException : Exception
|
||||
{
|
||||
public InvalidWebsocketFormatException()
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidWebsocketFormatException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidWebsocketFormatException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected InvalidWebsocketFormatException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Exceptions
|
||||
{
|
||||
public class NoDataException : Exception
|
||||
{
|
||||
public NoDataException()
|
||||
{
|
||||
}
|
||||
|
||||
public NoDataException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NoDataException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected NoDataException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MTSC.Common.Http;
|
||||
using MTSC.Common.WebSockets;
|
||||
using MTSC.Common.WebSockets.ServerModules;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -16,6 +17,7 @@ namespace MTSC.Server.Handlers
|
||||
private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
|
||||
private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
private static SHA1 sha1Provider = SHA1.Create();
|
||||
private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||
private enum SocketState
|
||||
{
|
||||
Initial,
|
||||
@@ -45,7 +47,12 @@ namespace MTSC.Server.Handlers
|
||||
/// <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));
|
||||
WebsocketMessage sendMessage = new WebsocketMessage();
|
||||
sendMessage.Data = message;
|
||||
sendMessage.FIN = true;
|
||||
rng.GetBytes(sendMessage.Mask);
|
||||
sendMessage.Opcode = WebsocketMessage.Opcodes.Text;
|
||||
messageQueue.Enqueue(new Tuple<ClientData, byte[]>(client, sendMessage.GetMessageBytes()));
|
||||
}
|
||||
#endregion
|
||||
#region Handler Implementation
|
||||
@@ -95,9 +102,10 @@ namespace MTSC.Server.Handlers
|
||||
}
|
||||
else if(webSockets[client] == SocketState.Established)
|
||||
{
|
||||
foreach(IWebsocketModule websocketModule in websocketModules)
|
||||
WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes);
|
||||
foreach (IWebsocketModule websocketModule in websocketModules)
|
||||
{
|
||||
if(websocketModule.HandleReceivedMessage(this, client, message.MessageBytes))
|
||||
if(websocketModule.HandleReceivedMessage(server, this, client, receivedMessage))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
var message = document.getElementById("textarea").value;
|
||||
doSend(message);
|
||||
}
|
||||
|
||||
function closeSocket(){
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
window.addEventListener("load", init, false);
|
||||
|
||||
@@ -70,5 +74,6 @@
|
||||
|
||||
<textarea id="textarea" rows="4" cols="50">Write here messages.</textarea>
|
||||
<button type="button" onclick="sendMessage()">Send!</button>
|
||||
<button type="button" onclick="closeSocket()">Close connection!</button>
|
||||
<div id="output"></div>
|
||||
|
||||
Reference in New Issue
Block a user