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(); 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) 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) public static void LogStdout(bool error, string type, string text)
{ {
@ -69,10 +83,10 @@ namespace HISP.Cli
LogToFile(error, type, text); LogToFile(error, type, text);
if (error) 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 else
Console.Out.WriteAsync(DateTime.Now.ToString("MM-dd-yyyy HH:mm:dd") + ": [" + type + "] " + text + Console.Out.NewLine); Console.Out.WriteAsync(formatMessage(type, text));
} }
public static void Main(string[] args) public static void Main(string[] args)
@ -92,12 +106,12 @@ namespace HISP.Cli
switch (arg) switch (arg)
{ {
case "--install-service": case "--install-service":
#if OS_LINUX #if OS_LINUX
File.WriteAllBytes("/etc/systemd/system/HISP.service", Resources.HISPService); File.WriteAllBytes("/etc/systemd/system/HISP.service", Resources.HISPService);
LogStdout(false, "INFO", "Crreated Service! enable it with \"sudo systemctl enable HISP\""); 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"); LogStdout(true, "ERROR", "Installing as a service unsupported on this platform");
#endif #endif
break; break;
default: default:
if (arg.Contains("=")) 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.8.42.0")] [assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.42.0")] [assembly: AssemblyFileVersion("1.8.45.0")]

View file

@ -1,5 +1,5 @@
Package: hisp Package: hisp
Version: 1.8.42 Version: 1.8.45
Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev
Maintainer: Li Maintainer: Li
Homepage: https://islehorse.com 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.42.0")] [assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.42.0")] [assembly: AssemblyFileVersion("1.8.45.0")]

View file

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

View file

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

View file

@ -1,16 +1,19 @@
using HISP.Security; using HISP.Security;
using HISP.Util; using HISP.Util;
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace HISP.Server.Network namespace HISP.Server.Network
{ {
public class XmlSocket : Transport public class XmlSocket : Transport
{ {
private List<byte> currentPacket = new List<byte>();
private const byte XMLSOCKET_PACKET_TERMINATOR = 0x00; private const byte XMLSOCKET_PACKET_TERMINATOR = 0x00;
public override void ProcessReceivedPackets(int available, byte[] buffer) 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 // In XmlSocket Packets are terminates by 0x00 so we have to read until we receive that terminator
for (int i = 0; i < available; i++) for (int i = 0; i < available; i++)
{ {
if (buffer[i] == XMLSOCKET_PACKET_TERMINATOR) // Read until \0... if (buffer[i] == XMLSOCKET_PACKET_TERMINATOR) // Read until \0...
@ -38,10 +41,13 @@ namespace HISP.Server.Network
public override void Send(byte[] data) 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 // add \0 to the end of the buffer
byte[] buffer = new byte[data.Length + 1]; data[oldLength] = XMLSOCKET_PACKET_TERMINATOR;
Array.Copy(data, buffer, data.Length); // copy packet to buffer
buffer[buffer.Length - 1] = XMLSOCKET_PACKET_TERMINATOR;
// send to the server // send to the server
base.Send(data); 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.42.0")] [assembly: AssemblyVersion("1.8.45.0")]
[assembly: AssemblyFileVersion("1.8.42.0")] [assembly: AssemblyFileVersion("1.8.45.0")]