You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
6.8 KiB
165 lines
6.8 KiB
2 years ago
|
using UnityEngine;
|
||
|
|
||
|
using System;
|
||
|
using System.Net;
|
||
|
using System.Net.Sockets;
|
||
|
using System.Collections.Concurrent;
|
||
|
// For parsing the client websocket requests
|
||
|
using System.Text;
|
||
|
using System.Text.RegularExpressions;
|
||
|
// For creating a thread
|
||
|
using System.Threading;
|
||
|
|
||
|
|
||
|
namespace WebSocketServer {
|
||
|
|
||
|
public enum WebSocketEventType {
|
||
|
Open,
|
||
|
Close,
|
||
|
Message
|
||
|
}
|
||
|
|
||
|
public struct WebSocketMessage {
|
||
|
public WebSocketMessage(WebSocketConnection connection, string data) {
|
||
|
this.id = Guid.NewGuid().ToString();
|
||
|
this.connection = connection;
|
||
|
this.data = data;
|
||
|
}
|
||
|
|
||
|
public string id { get; }
|
||
|
public WebSocketConnection connection { get; }
|
||
|
public string data { get; }
|
||
|
}
|
||
|
|
||
|
public struct WebSocketEvent {
|
||
|
public WebSocketEvent(WebSocketConnection connection, WebSocketEventType type, string data) {
|
||
|
this.id = Guid.NewGuid().ToString();
|
||
|
this.connection = connection;
|
||
|
this.type = type;
|
||
|
this.data = data;
|
||
|
}
|
||
|
|
||
|
public string id { get; }
|
||
|
public WebSocketEventType type { get; }
|
||
|
public WebSocketConnection connection { get; }
|
||
|
public string data { get; }
|
||
|
}
|
||
|
|
||
|
public class WebSocketConnection {
|
||
|
|
||
|
public string id;
|
||
|
private TcpClient client;
|
||
|
private NetworkStream stream;
|
||
|
private WebSocketServer server;
|
||
|
private Thread connectionHandler;
|
||
|
|
||
|
public WebSocketConnection(TcpClient client, WebSocketServer server) {
|
||
|
this.id = Guid.NewGuid().ToString();
|
||
|
this.client = client;
|
||
|
this.stream = client.GetStream();
|
||
|
this.server = server;
|
||
|
}
|
||
|
|
||
|
public bool Establish() {
|
||
|
// Wait for enough bytes to be available
|
||
|
while (!stream.DataAvailable);
|
||
|
while(client.Available < 3);
|
||
|
// Translate bytes of request to a RequestHeader object
|
||
|
Byte[] bytes = new Byte[client.Available];
|
||
|
stream.Read(bytes, 0, bytes.Length);
|
||
|
RequestHeader request = new RequestHeader(Encoding.UTF8.GetString(bytes));
|
||
|
|
||
|
// Check if the request complies with WebSocket protocol.
|
||
|
if (WebSocketProtocol.CheckConnectionHandshake(request)) {
|
||
|
// If so, initiate the connection by sending a reply according to protocol.
|
||
|
Byte[] response = WebSocketProtocol.CreateHandshakeReply(request);
|
||
|
stream.Write(response, 0, response.Length);
|
||
|
|
||
|
Debug.Log("WebSocket client connected.");
|
||
|
|
||
|
// Start message handling
|
||
|
connectionHandler = new Thread(new ThreadStart(HandleConnection));
|
||
|
connectionHandler.IsBackground = true;
|
||
|
connectionHandler.Start();
|
||
|
|
||
|
// Call the server callback.
|
||
|
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Open, null);
|
||
|
server.events.Enqueue(wsEvent);
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void HandleConnection () {
|
||
|
while (true) {
|
||
|
WebSocketDataFrame dataframe = ReadDataFrame();
|
||
|
|
||
|
if (dataframe.fin) {
|
||
|
if ((WebSocketOpCode)dataframe.opcode == WebSocketOpCode.Text) {
|
||
|
// Let the server know of the message.
|
||
|
string data = WebSocketProtocol.DecodeText(dataframe);
|
||
|
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Message, data);
|
||
|
server.events.Enqueue(wsEvent);
|
||
|
} else if ((WebSocketOpCode)dataframe.opcode == WebSocketOpCode.Close) {
|
||
|
// Handle closing the connection.
|
||
|
Debug.Log("Client closed the connection.");
|
||
|
// Close the connection.
|
||
|
stream.Close();
|
||
|
client.Close();
|
||
|
// Call server callback.
|
||
|
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Close, null);
|
||
|
server.events.Enqueue(wsEvent);
|
||
|
// Jump out of the loop.
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
Debug.Log("Framentation encoutered.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private WebSocketDataFrame ReadDataFrame() {
|
||
|
const int DataframeHead = 2; // Length of dataframe head
|
||
|
const int ShortPayloadLength = 2; // Length of a short payload length field
|
||
|
const int LongPayloadLength = 8; // Length of a long payload length field
|
||
|
const int Mask = 4; // Length of the payload mask
|
||
|
|
||
|
// Wait for a dataframe head to be available, then read the data.
|
||
|
while (!stream.DataAvailable && client.Available < DataframeHead);
|
||
|
Byte[] bytes = new Byte[DataframeHead];
|
||
|
stream.Read(bytes, 0, DataframeHead);
|
||
|
|
||
|
// Decode the message head, including FIN, OpCode, and initial byte of the payload length.
|
||
|
WebSocketDataFrame dataframe = WebSocketProtocol.CreateDataFrame();
|
||
|
WebSocketProtocol.ParseDataFrameHead(bytes, ref dataframe);
|
||
|
|
||
|
// Depending on the dataframe length, read & decode the next bytes for payload length
|
||
|
if (dataframe.length == 126) {
|
||
|
while (client.Available < ShortPayloadLength); // Wait until data is available
|
||
|
Array.Resize(ref bytes, bytes.Length + ShortPayloadLength);
|
||
|
stream.Read(bytes, bytes.Length - ShortPayloadLength, ShortPayloadLength); // Read the next two bytes for length
|
||
|
} else if (dataframe.length == 127) {
|
||
|
while (client.Available < LongPayloadLength); // Wait until data is available
|
||
|
Array.Resize(ref bytes, bytes.Length + LongPayloadLength);
|
||
|
stream.Read(bytes, bytes.Length - LongPayloadLength, LongPayloadLength); // Read the next two bytes for length
|
||
|
}
|
||
|
WebSocketProtocol.ParseDataFrameLength(bytes, ref dataframe); // Parse the length
|
||
|
|
||
|
if (dataframe.mask) {
|
||
|
while (client.Available < Mask); // Wait until data is available
|
||
|
Array.Resize(ref bytes, bytes.Length + Mask);
|
||
|
stream.Read(bytes, bytes.Length - Mask, Mask); // Read the next four bytes for mask
|
||
|
}
|
||
|
|
||
|
while (client.Available < dataframe.length); // Wait until data is available
|
||
|
Array.Resize(ref bytes, bytes.Length + dataframe.length);
|
||
|
stream.Read(bytes, bytes.Length - dataframe.length, dataframe.length); // Read the payload
|
||
|
dataframe.data = bytes;
|
||
|
|
||
|
return dataframe;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|