Improve WebSocket performance

This commit is contained in:
Li 2022-11-18 19:45:17 +13:00
parent eefc2b926d
commit 458748e6b0
8 changed files with 118 additions and 73 deletions

View file

@ -59,9 +59,23 @@ namespace HISP.Cli
shutdownHandle.Set();
}
private static string formatMessage(string type, string text)
{
#if OS_WINDOWS
string newline = "\r\n";
#else
string newline = "\n";
#endif
return DateTime.Now.ToString("MM-dd-yyyy HH:mm:dd") + ": [" + type + "] " + text + newline;
}
public static void LogToFile(bool error, string type,string text)
{
sw.WriteLine(DateTime.Now.ToString("MM-dd-yyyy HH:mm:dd") + ": [" + type + "] " + text + sw.NewLine);
sw.WriteLine(formatMessage(type, text));
if (error)
sw.Flush();
}
public static void LogStdout(bool error, string type, string text)
{
@ -69,9 +83,9 @@ namespace HISP.Cli
LogToFile(error, type, text);
if (error)
Console.Error.WriteAsync(DateTime.Now.ToString("MM-dd-yyyy HH:mm:dd")+": [" + type + "] " + text + Console.Error.NewLine);
Console.Error.WriteAsync(formatMessage(type, text));
else
Console.Out.WriteAsync(DateTime.Now.ToString("MM-dd-yyyy HH:mm:dd") + ": [" + type + "] " + text + Console.Out.NewLine);
Console.Out.WriteAsync(formatMessage(type, text));
}
@ -92,12 +106,12 @@ namespace HISP.Cli
switch (arg)
{
case "--install-service":
#if OS_LINUX
#if OS_LINUX
File.WriteAllBytes("/etc/systemd/system/HISP.service", Resources.HISPService);
LogStdout(false, "INFO", "Crreated Service! enable it with \"sudo systemctl enable HISP\"");
#else
#else
LogStdout(true, "ERROR", "Installing as a service unsupported on this platform");
#endif
#endif
break;
default:
if (arg.Contains("="))

View file

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.8.42.0")]
[assembly: AssemblyFileVersion("1.8.42.0")]
[assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.45.0")]

View file

@ -1,5 +1,5 @@
Package: hisp
Version: 1.8.42
Version: 1.8.45
Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev
Maintainer: Li
Homepage: https://islehorse.com

View file

@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.42.0")]
[assembly: AssemblyFileVersion("1.8.42.0")]
[assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.45.0")]

View file

@ -13,7 +13,6 @@ namespace HISP.Server.Network
internal Action<byte[]> onReceiveCallback;
internal Action onDisconnectCallback;
internal List<byte> currentPacket = new List<byte>();
internal byte[] workBuffer = new byte[0xFFFF];
internal bool isDisconnecting = false;

View file

@ -1,4 +1,4 @@
//#define WEBSOCKET_DEBUG
#define WEBSOCKET_DEBUG
using HISP.Security;
using HISP.Util;
using System;
@ -26,8 +26,8 @@ namespace HISP.Server.Network
private const int WEBSOCKET_EXPECTED_SIZE_SET = 0;
private const int WEBSOCKET_EXPECTED_SIZE_UNSET = -1;
private List<byte> currentMessage = new List<byte>();
private byte[] currentMessage = new byte[0];
private byte[] currentPacket = new byte[0];
private byte lastOpcode;
private Int64 expectedLength = -1;
@ -126,59 +126,58 @@ namespace HISP.Server.Network
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
Int64 received = 0;
// add to current packet
// if current packet is less than size of an expected incoming message
// then keep receiving until full message received.
for (received = 0; received < available; received++)
currentPacket.Add(buffer[received]);
int oldLength = currentPacket.Length;
Array.Resize(ref currentPacket, oldLength + available);
Array.ConstrainedCopy(buffer, 0, currentPacket, oldLength, available);
if (isCurrentPacketLengthLessThanExpectedLength())
return;
else
setUnknownExpectedLength();
byte[] webSocketMsg = currentPacket.ToArray();
//byte[] webSocketMsg = currentPacket.ToArray();
if (!handshakeDone)
{
if (IsStartOfHandshake(webSocketMsg) && IsEndOfHandshake(webSocketMsg))
if (IsStartOfHandshake(currentPacket) && IsEndOfHandshake(currentPacket))
{
string httpHandshake = Encoding.UTF8.GetString(webSocketMsg);
string httpHandshake = Encoding.UTF8.GetString(currentPacket);
byte[] handshakeResponse = parseHandshake(httpHandshake);
base.Send(handshakeResponse);
currentPacket.Clear();
Array.Resize(ref currentPacket, 0);
handshakeDone = true;
}
}
else if(currentPacket.Count > 2) // else, begin parsing websocket message
else if(currentPacket.Length > 2) // else, begin parsing websocket message
{
byte[] unmaskKey = new byte[4];
bool finished = (webSocketMsg[0] & 0b10000000) != 0;
bool finished = (currentPacket[0] & 0b10000000) != 0;
bool rsv1 = (webSocketMsg[0] & 0b01000000) != 0;
bool rsv2 = (webSocketMsg[0] & 0b00100000) != 0;
bool rsv3 = (webSocketMsg[0] & 0b00010000) != 0;
bool rsv1 = (currentPacket[0] & 0b01000000) != 0;
bool rsv2 = (currentPacket[0] & 0b00100000) != 0;
bool rsv3 = (currentPacket[0] & 0b00010000) != 0;
byte opcode = Convert.ToByte(webSocketMsg[0] & 0b00001111);
byte opcode = Convert.ToByte(currentPacket[0] & 0b00001111);
bool mask = (webSocketMsg[1] & 0b10000000) != 0;
Int64 messageLength = Convert.ToInt64(webSocketMsg[1] & 0b01111111);
bool mask = (currentPacket[1] & 0b10000000) != 0;
Int64 messageLength = Convert.ToInt64(currentPacket[1] & 0b01111111);
int offset = 2;
if (messageLength == WEBSOCKET_LENGTH_INT16)
{
if (webSocketMsg.LongLength >= offset + 2)
if (currentPacket.LongLength >= offset + 2)
{
byte[] uint16Bytes = new byte[2];
Array.ConstrainedCopy(webSocketMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
Array.ConstrainedCopy(currentPacket, offset, uint16Bytes, 0, uint16Bytes.Length);
uint16Bytes = uint16Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt16(uint16Bytes);
@ -187,10 +186,10 @@ namespace HISP.Server.Network
}
else if (messageLength == WEBSOCKET_LENGTH_INT64)
{
if (webSocketMsg.LongLength >= offset + 8)
if (currentPacket.LongLength >= offset + 8)
{
byte[] int64Bytes = new byte[8];
Array.ConstrainedCopy(webSocketMsg, offset, int64Bytes, 0, int64Bytes.Length);
Array.ConstrainedCopy(currentPacket, offset, int64Bytes, 0, int64Bytes.Length);
int64Bytes = int64Bytes.Reverse().ToArray();
messageLength = BitConverter.ToInt64(int64Bytes);
@ -200,13 +199,13 @@ namespace HISP.Server.Network
if (mask)
{
Array.ConstrainedCopy(webSocketMsg, offset, unmaskKey, 0, unmaskKey.Length);
Array.ConstrainedCopy(currentPacket, offset, unmaskKey, 0, unmaskKey.Length);
offset += unmaskKey.Length;
}
// Handle tcp fragmentation
Int64 actualLength = (webSocketMsg.LongLength - offset);
Int64 actualLength = (currentPacket.LongLength - offset);
// check if full message received, if not then set expected length
// and return, thus entering the loop at the beginning
@ -216,11 +215,13 @@ namespace HISP.Server.Network
webSocketLog("Partial websocket frame received, expected size: " + messageLength + " got size: " + actualLength);
return;
}
else
{
// clone current packet array
byte[] currentPacketCopy = currentPacket.ToArray();
// set current packet array size back to 0
setUnknownExpectedLength();
currentPacket.Clear();
}
Array.Resize(ref currentPacket, 0);
// dont care about extensions
if (rsv1 || rsv2 || rsv3) return;
@ -237,8 +238,17 @@ namespace HISP.Server.Network
case WEBSOCKET_BINARY:
case WEBSOCKET_TEXT:
case WEBSOCKET_PING:
for (Int64 i = 0; i < messageLength; i++)
currentMessage.Add(mask ? Convert.ToByte(webSocketMsg[offset + i] ^ unmaskKey[i % unmaskKey.Length]) : Convert.ToByte(webSocketMsg[offset + i]));
oldLength = currentMessage.Length;
Array.Resize(ref currentMessage, oldLength + Convert.ToInt32(messageLength));
if (mask)
{
for (int i = 0; i < Convert.ToInt32(messageLength); i++)
currentMessage[oldLength + i] = Convert.ToByte(currentPacketCopy[offset + i] ^ unmaskKey[i % unmaskKey.Length]);
}
else
{
Array.ConstrainedCopy(currentPacketCopy, offset, currentMessage, oldLength, Convert.ToInt32(messageLength));
}
break;
case WEBSOCKET_CLOSE:
this.Disconnect();
@ -249,14 +259,13 @@ namespace HISP.Server.Network
if (finished)
{
byte[] message = currentMessage.ToArray();
if (lastOpcode != WEBSOCKET_PING && message.LongLength > 0)
onReceiveCallback(message);
if (lastOpcode != WEBSOCKET_PING && currentMessage.LongLength > 0)
onReceiveCallback(currentMessage);
else
Send(message);
Send(currentMessage);
currentMessage.Clear();
Array.Resize(ref currentMessage, 0);
Array.Resize(ref currentPacket, 0);
}
@ -274,8 +283,8 @@ namespace HISP.Server.Network
if (totalSent <= total)
total = Convert.ToInt32(totalSent);
for (int i = 0; i < total; i++)
buffer[i] = webSocketMsg[loc + i];
Array.ConstrainedCopy(currentPacketCopy, Convert.ToInt32(loc), buffer, 0, total);
webSocketLog("Found another frame at the end of this one, processing!");
ProcessReceivedPackets(total, buffer);
@ -283,13 +292,13 @@ namespace HISP.Server.Network
loc += total;
}
}
}
}
// specify transport name is "WebSocket"
public override string Name
{
get
@ -321,7 +330,7 @@ namespace HISP.Server.Network
// despite its name, this has nothing to do with graphics
// rather this is for WebSocket frames
List<byte> frameBuffer = new List<byte>();
List<byte> frameHeader = new List<byte>();
for (Int64 remain = totalData; remain > 0; remain -= toSend)
{
@ -337,7 +346,7 @@ namespace HISP.Server.Network
finish = true;
}
frameBuffer.Add(Convert.ToByte((0x00) | (finish ? 0b10000000 : 0b00000000) | opcode));
frameHeader.Add(Convert.ToByte((0x00) | (finish ? 0b10000000 : 0b00000000) | opcode));
// do special length encoding
byte maskAndLength = Convert.ToByte((0x00) | (mask ? 0b10000000 : 0b00000000));
@ -363,23 +372,40 @@ namespace HISP.Server.Network
}
// Add to buffer
frameBuffer.Add(maskAndLength);
Helper.ByteArrayToByteList(additionalLengthData, frameBuffer);
frameHeader.Add(maskAndLength);
Helper.ByteArrayToByteList(additionalLengthData, frameHeader);
// Generate masking key;
byte[] maskingKey = new byte[4];
GameServer.RandomNumberGenerator.NextBytes(maskingKey);
if (mask)
Helper.ByteArrayToByteList(maskingKey, frameBuffer);
{
GameServer.RandomNumberGenerator.NextBytes(maskingKey);
Helper.ByteArrayToByteList(maskingKey, frameHeader);
}
int headerSize = frameHeader.Count;
byte[] frame = new byte[toSend + headerSize];
Array.Copy(frameHeader.ToArray(), frame, headerSize);
frameHeader.Clear();
// Mask data using key.
Int64 totalSent = (totalData - remain);
for (int i = 0; i < toSend; i++)
frameBuffer.Add(mask ? Convert.ToByte(data[i + totalSent] ^ maskingKey[i % maskingKey.Length]) : Convert.ToByte(data[i + totalSent]));
// Finally send it over the network
base.Send(frameBuffer.ToArray());
if (mask) // are we masking this response?
{
// Mask data using key.
for (int i = 0; i < toSend; i++)
frame[i + headerSize] = Convert.ToByte(data[i + totalSent] ^ maskingKey[i % maskingKey.Length]);
}
else if(data.LongLength < Int32.MaxValue) // is out packet *really* bigger than 32 max int??
{
Array.ConstrainedCopy(data, Convert.ToInt32(totalSent), frame, headerSize, toSend);
}
// Finally send complete frame over the network
base.Send(frame);
if (this.Disconnected) return; // are we still here?
}

View file

@ -1,16 +1,19 @@
using HISP.Security;
using HISP.Util;
using System;
using System.Collections.Generic;
using System.Text;
namespace HISP.Server.Network
{
public class XmlSocket : Transport
{
private List<byte> currentPacket = new List<byte>();
private const byte XMLSOCKET_PACKET_TERMINATOR = 0x00;
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
// In XmlSocket Packets are terminates by 0x00 so we have to read until we receive that terminator
for (int i = 0; i < available; i++)
{
if (buffer[i] == XMLSOCKET_PACKET_TERMINATOR) // Read until \0...
@ -38,10 +41,13 @@ namespace HISP.Server.Network
public override void Send(byte[] data)
{
int oldLength = data.Length;
// Resize the array to be 1 extra byte in size;
Array.Resize(ref data, oldLength + 1);
// add \0 to the end of the buffer
byte[] buffer = new byte[data.Length + 1];
Array.Copy(data, buffer, data.Length); // copy packet to buffer
buffer[buffer.Length - 1] = XMLSOCKET_PACKET_TERMINATOR;
data[oldLength] = XMLSOCKET_PACKET_TERMINATOR;
// send to the server
base.Send(data);

View file

@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.42.0")]
[assembly: AssemblyFileVersion("1.8.42.0")]
[assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.45.0")]