Replacing Networking (#271)
- [X] Removing Kestrel & Libuv - [X] Cleaning up NetState - [X] Removing System.IO.Pipelines - [X] Cleaning up packet reading - [X] Adds a maximum of 5000 sockets (configurable) to prevent OOM - [X] Replaces the AsyncState with a thread-safe wrapped boolean called NetworkState - [X] Removes Parallel.ForEach (no perf gain) - [X] Removes custom houses compression on another thread - [X] Test high load scenarios Bumps release version
This commit is contained in:
parent
8603e31023
commit
369a27b800
47 changed files with 1780 additions and 1569 deletions
|
|
@ -18,9 +18,9 @@ namespace Server.Network
|
|||
: new Point3D(m_Reader.ReadInt16(), m_Reader.ReadInt16(), m_Reader.ReadByte());
|
||||
|
||||
public string ReadUnicodeStringSafe() =>
|
||||
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeStringSafe(m_Reader.ReadUInt16());
|
||||
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUniSafe(m_Reader.ReadUInt16());
|
||||
|
||||
public string ReadUnicodeString() =>
|
||||
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeString(m_Reader.ReadUInt16());
|
||||
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUni(m_Reader.ReadUInt16());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MessagePumpService.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public interface IMessagePumpService
|
||||
{
|
||||
void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive);
|
||||
void DoWork();
|
||||
}
|
||||
|
||||
public class MessagePumpService : IMessagePumpService
|
||||
{
|
||||
private readonly ConcurrentQueue<Work> m_WorkQueue = new ConcurrentQueue<Work>();
|
||||
|
||||
public void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
|
||||
{
|
||||
m_WorkQueue.Enqueue(new Work(ns, memOwner, length, onReceive));
|
||||
Core.Set();
|
||||
}
|
||||
|
||||
public void DoWork()
|
||||
{
|
||||
var count = 0;
|
||||
while (!m_WorkQueue.IsEmpty && count++ < 250)
|
||||
{
|
||||
if (!m_WorkQueue.TryDequeue(out var work))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var seq = new ReadOnlySequence<byte>(work.MemoryOwner.Memory.Slice(0, work.Length));
|
||||
work.OnReceive(work.State, new PacketReader(seq));
|
||||
work.MemoryOwner.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private class Work
|
||||
{
|
||||
public readonly int Length;
|
||||
public readonly IMemoryOwner<byte> MemoryOwner;
|
||||
public readonly OnPacketReceive OnReceive;
|
||||
public readonly NetState State;
|
||||
|
||||
public Work(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
|
||||
{
|
||||
State = ns;
|
||||
MemoryOwner = memOwner;
|
||||
OnReceive = onReceive;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: NetState.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Server.Accounting;
|
||||
using Server.Exceptions;
|
||||
using Server.Gumps;
|
||||
using Server.HuePickers;
|
||||
using Server.Items;
|
||||
|
|
@ -17,41 +32,66 @@ namespace Server.Network
|
|||
{
|
||||
public delegate void NetStateCreatedCallback(NetState ns);
|
||||
|
||||
public class AsyncState
|
||||
{
|
||||
public AsyncState(bool paused) => Paused = paused;
|
||||
public bool Paused { get; set; }
|
||||
}
|
||||
|
||||
public partial class NetState : IComparable<NetState>
|
||||
{
|
||||
private static readonly AsyncState m_PauseState = new AsyncState(true);
|
||||
private static readonly AsyncState m_ResumeState = new AsyncState(false);
|
||||
|
||||
private static AsyncState m_AsyncState = m_ResumeState;
|
||||
private static int IncomingPipeSize = 1024 * 64;
|
||||
private static int OutgoingPipeSize = 1024 * 256;
|
||||
private static int GumpCap = 512;
|
||||
private static int HuePickerCap = 512;
|
||||
private static int MenuCap = 512;
|
||||
|
||||
private static readonly ConcurrentQueue<NetState> m_Disposed = new ConcurrentQueue<NetState>();
|
||||
private static NetworkState m_NetworkState = NetworkState.ResumeState;
|
||||
|
||||
public static NetStateCreatedCallback CreatedCallback { get; set; }
|
||||
|
||||
private readonly string m_ToString;
|
||||
internal int m_AuthID;
|
||||
|
||||
private int m_Disposing;
|
||||
|
||||
internal int m_Seed;
|
||||
private ClientVersion m_Version;
|
||||
private byte[] m_IncomingBuffer;
|
||||
private Pipe<byte> m_IncomingPipe;
|
||||
private byte[] m_OutgoingBuffer;
|
||||
private Pipe<byte> m_OutgoingPipe;
|
||||
private long m_NextCheckActivity;
|
||||
private volatile bool m_Running;
|
||||
|
||||
public NetState(ConnectionContext connection)
|
||||
internal int m_AuthID;
|
||||
internal int m_Seed;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
IncomingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.incomingPipeSize", IncomingPipeSize);
|
||||
OutgoingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.outgoingPipeSize", OutgoingPipeSize);
|
||||
GumpCap = ServerConfiguration.GetOrUpdateSetting("netstate.gumpCap", GumpCap);
|
||||
HuePickerCap = ServerConfiguration.GetOrUpdateSetting("netstate.huePickerCap", HuePickerCap);
|
||||
MenuCap = ServerConfiguration.GetOrUpdateSetting("netstate.menuCap", MenuCap);
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
var checkAliveDuration = TimeSpan.FromMinutes(1.5);
|
||||
Timer.DelayCall(checkAliveDuration, checkAliveDuration, CheckAllAlive);
|
||||
}
|
||||
|
||||
public NetState(Socket connection)
|
||||
{
|
||||
m_Running = false;
|
||||
Connection = connection;
|
||||
Seeded = false;
|
||||
Gumps = new List<Gump>();
|
||||
HuePickers = new List<HuePicker>();
|
||||
Menus = new List<IMenu>();
|
||||
Trades = new List<SecureTrade>();
|
||||
m_IncomingBuffer = new byte[IncomingPipeSize];
|
||||
m_IncomingPipe = new Pipe<byte>(m_IncomingBuffer);
|
||||
m_OutgoingBuffer = new byte[OutgoingPipeSize];
|
||||
m_OutgoingPipe = new Pipe<byte>(m_OutgoingBuffer);
|
||||
m_NextCheckActivity = Core.TickCount + 30000;
|
||||
|
||||
try
|
||||
{
|
||||
Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address);
|
||||
m_ToString = Address.ToString();
|
||||
Address = Utility.Intern((Connection?.RemoteEndPoint as IPEndPoint)?.Address);
|
||||
m_ToString = Address?.ToString() ?? "(error)";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -62,14 +102,6 @@ namespace Server.Network
|
|||
|
||||
ConnectedOn = DateTime.UtcNow;
|
||||
|
||||
connection.ConnectionClosed.Register(
|
||||
() =>
|
||||
{
|
||||
TcpServer.Instances.Remove(this);
|
||||
Dispose();
|
||||
}
|
||||
);
|
||||
|
||||
CreatedCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
|
|
@ -80,21 +112,20 @@ namespace Server.Network
|
|||
public DateTime ThrottledUntil { get; set; }
|
||||
|
||||
public IPAddress Address { get; }
|
||||
public static AsyncState AsyncState => m_AsyncState;
|
||||
|
||||
public IPacketEncoder PacketEncoder { get; set; }
|
||||
|
||||
public static NetStateCreatedCallback CreatedCallback { get; set; }
|
||||
|
||||
public bool SentFirstPacket { get; set; }
|
||||
|
||||
public bool BlockAllPackets { get; set; }
|
||||
|
||||
public List<SecureTrade> Trades { get; }
|
||||
|
||||
public bool Running => m_Running;
|
||||
|
||||
public bool Seeded { get; set; }
|
||||
|
||||
public ConnectionContext Connection { get; private set; }
|
||||
public Socket Connection { get; private set; }
|
||||
|
||||
public bool CompressionEnabled { get; set; }
|
||||
|
||||
|
|
@ -106,12 +137,6 @@ namespace Server.Network
|
|||
|
||||
public List<IMenu> Menus { get; private set; }
|
||||
|
||||
public static int GumpCap { get; set; } = 512;
|
||||
|
||||
public static int HuePickerCap { get; set; } = 512;
|
||||
|
||||
public static int MenuCap { get; set; } = 512;
|
||||
|
||||
public CityInfo[] CityInfo { get; set; }
|
||||
|
||||
public Mobile Mobile { get; set; }
|
||||
|
|
@ -208,11 +233,13 @@ namespace Server.Network
|
|||
return newTrade.From.Container;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteConsole(string text)
|
||||
{
|
||||
Console.WriteLine("Client: {0}: {1}", this, text);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteConsole(string format, params object[] args)
|
||||
{
|
||||
WriteConsole(string.Format(format, args));
|
||||
|
|
@ -318,15 +345,15 @@ namespace Server.Network
|
|||
|
||||
public static void Pause()
|
||||
{
|
||||
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_PauseState);
|
||||
NetworkState.Pause(ref m_NetworkState);
|
||||
}
|
||||
|
||||
public static void Resume()
|
||||
{
|
||||
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_ResumeState);
|
||||
NetworkState.Resume(ref m_NetworkState);
|
||||
}
|
||||
|
||||
public virtual async void Send(Packet p)
|
||||
public virtual void Send(Packet p)
|
||||
{
|
||||
if (Connection == null || BlockAllPackets)
|
||||
{
|
||||
|
|
@ -334,83 +361,101 @@ namespace Server.Network
|
|||
return;
|
||||
}
|
||||
|
||||
var outPipe = Connection.Transport.Output;
|
||||
var currentThread = Thread.CurrentThread;
|
||||
|
||||
if (currentThread != Core.Thread)
|
||||
{
|
||||
Console.Error.WriteLine("Core: Attempted to send packet outside core thread! [{0}]", currentThread.ManagedThreadId);
|
||||
#if DEBUG
|
||||
throw new InvalidThreadException(nameof(Send));
|
||||
#endif
|
||||
}
|
||||
|
||||
var writer = m_OutgoingPipe.Writer;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Rented memory
|
||||
ReadOnlyMemory<byte> buffer = p.Compile(CompressionEnabled, out var length);
|
||||
byte[] buffer = p.Compile(CompressionEnabled, out var length);
|
||||
|
||||
if (buffer.Length > 0 && length > 0)
|
||||
{
|
||||
var result = await outPipe.WriteAsync(buffer.Slice(0, length));
|
||||
var result = writer.GetAvailable();
|
||||
|
||||
if (result.IsCanceled || result.IsCompleted)
|
||||
if (result.Length >= length)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
result.CopyFrom(buffer.AsSpan(0, length));
|
||||
writer.Advance((uint)length);
|
||||
|
||||
// Flush at the end of the game loop
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteConsole("Too much data pending, disconnecting...");
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteConsole("Didn't write anything!");
|
||||
}
|
||||
|
||||
p.OnSend();
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
TraceException(ex);
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
TraceException(ex);
|
||||
#endif
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessIncoming(IMessagePumpService messagePumpService)
|
||||
internal void Start()
|
||||
{
|
||||
var inPipe = Connection.Transport.Input;
|
||||
if (Connection == null || m_Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Running = true;
|
||||
|
||||
ThreadPool.UnsafeQueueUserWorkItem(RecvTask, null);
|
||||
ThreadPool.UnsafeQueueUserWorkItem(SendTask, null);
|
||||
}
|
||||
|
||||
private async void SendTask(object state)
|
||||
{
|
||||
var reader = m_OutgoingPipe.Reader;
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
while (m_Running)
|
||||
{
|
||||
if (AsyncState.Paused)
|
||||
var result = await reader.Read();
|
||||
|
||||
if (result.Length <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = await inPipe.ReadAsync();
|
||||
if (result.IsCanceled || result.IsCompleted)
|
||||
var buffer = result.Buffer;
|
||||
|
||||
var bytesWritten = await Connection.SendAsync(buffer, SocketFlags.None);
|
||||
|
||||
if (bytesWritten > 0)
|
||||
{
|
||||
return;
|
||||
m_NextCheckActivity = Core.TickCount + 90000;
|
||||
reader.Advance((uint)bytesWritten);
|
||||
}
|
||||
|
||||
var seq = result.Buffer;
|
||||
|
||||
if (seq.IsEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq);
|
||||
|
||||
if (pos <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
inPipe.AdvanceTo(seq.Slice(0, pos).End);
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
TraceException(ex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Console.WriteLine(ex);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -418,12 +463,165 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
private async void RecvTask(object state)
|
||||
{
|
||||
var socket = Connection;
|
||||
var writer = m_IncomingPipe.Writer;
|
||||
|
||||
try
|
||||
{
|
||||
while (m_Running)
|
||||
{
|
||||
if (m_NetworkState == NetworkState.PauseState)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = writer.GetAvailable();
|
||||
|
||||
if (result.Length <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
|
||||
var bytesWritten = await socket.ReceiveAsync(buffer, SocketFlags.None);
|
||||
|
||||
if (bytesWritten <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
writer.Advance((uint)bytesWritten);
|
||||
m_NextCheckActivity = Core.TickCount + 90000;
|
||||
|
||||
// No need to flush
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
TraceException(ex);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleAllReceives()
|
||||
{
|
||||
var clients = TcpServer.Instances;
|
||||
|
||||
for (int i = 0; i < clients.Count; ++i)
|
||||
{
|
||||
clients[i].HandleReceive();
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleReceive()
|
||||
{
|
||||
if (Connection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reader = m_IncomingPipe.Reader;
|
||||
|
||||
// Process as many packets as we can synchronously
|
||||
while (true)
|
||||
{
|
||||
var result = reader.TryRead();
|
||||
|
||||
if (result.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bytesProcessed = PacketHandlers.ProcessPacket(this, result.Buffer);
|
||||
|
||||
if (bytesProcessed <= 0)
|
||||
{
|
||||
// Error
|
||||
// TODO: Throw exception instead?
|
||||
if (bytesProcessed < 0)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
reader.Advance((uint)bytesProcessed);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
TraceException(ex);
|
||||
#endif
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (Connection != null)
|
||||
{
|
||||
m_OutgoingPipe.Writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public static void FlushAll()
|
||||
{
|
||||
var clients = TcpServer.Instances;
|
||||
|
||||
for (int i = 0; i < clients.Count; ++i)
|
||||
{
|
||||
clients[i].Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckAlive(long curTicks)
|
||||
{
|
||||
if (Connection != null && m_NextCheckActivity - curTicks < 0)
|
||||
{
|
||||
WriteConsole("Disconnecting due to inactivity...");
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckAllAlive()
|
||||
{
|
||||
try
|
||||
{
|
||||
long curTicks = Core.TickCount;
|
||||
|
||||
var clients = TcpServer.Instances;
|
||||
|
||||
for (int i = 0; i < clients.Count; ++i)
|
||||
{
|
||||
clients[i].CheckAlive(curTicks);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TraceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckEncrypted(int packetID)
|
||||
{
|
||||
if (!SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 &&
|
||||
packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF)
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", this);
|
||||
WriteConsole("Encrypted client detected, disconnecting");
|
||||
Dispose();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -456,26 +654,25 @@ namespace Server.Network
|
|||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
var disposing = Interlocked.Exchange(ref m_Disposing, 1);
|
||||
if (disposing == 1)
|
||||
if (Connection == null || Interlocked.Exchange(ref m_Disposing, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_OutgoingPipe.Writer.Close();
|
||||
|
||||
try
|
||||
{
|
||||
Connection.Transport.Input.Complete();
|
||||
Connection.Transport.Output.Complete();
|
||||
Connection.Abort();
|
||||
Task.Run(Connection.DisposeAsync).Wait();
|
||||
Connection.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TraceException(ex);
|
||||
}
|
||||
|
||||
Connection = null;
|
||||
m_Disposed.Enqueue(this);
|
||||
finally
|
||||
{
|
||||
m_Disposed.Enqueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessDisposedQueue()
|
||||
|
|
@ -498,6 +695,12 @@ namespace Server.Network
|
|||
ns.Mobile = null;
|
||||
}
|
||||
|
||||
ns.m_Running = false;
|
||||
ns.Connection = null;
|
||||
ns.m_IncomingBuffer = null;
|
||||
ns.m_IncomingPipe = null;
|
||||
ns.m_OutgoingBuffer = null;
|
||||
ns.m_OutgoingPipe = null;
|
||||
ns.Gumps.Clear();
|
||||
ns.Menus.Clear();
|
||||
ns.HuePickers.Clear();
|
||||
|
|
@ -505,6 +708,8 @@ namespace Server.Network
|
|||
ns.ServerInfo = null;
|
||||
ns.CityInfo = null;
|
||||
|
||||
TcpServer.Instances.Remove(ns);
|
||||
|
||||
if (a != null)
|
||||
{
|
||||
ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerStartup.cs *
|
||||
* File: NetworkState.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
|
|
@ -13,25 +13,26 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public class ServerStartup
|
||||
public class NetworkState
|
||||
{
|
||||
private readonly IMessagePumpService _messagePumpService;
|
||||
public ServerStartup(IMessagePumpService messagePumpService) => _messagePumpService = messagePumpService;
|
||||
public static readonly NetworkState PauseState = new NetworkState(true);
|
||||
public static readonly NetworkState ResumeState = new NetworkState(false);
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
public bool Paused { get; }
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
// Run async?
|
||||
Task.Run(() => Core.RunEventLoop(_messagePumpService));
|
||||
}
|
||||
internal NetworkState(bool paused) => Paused = paused;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Pause(ref NetworkState state) =>
|
||||
Interlocked.Exchange(ref state, PauseState)?.Paused != true;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Resume(ref NetworkState state) =>
|
||||
Interlocked.Exchange(ref state, ResumeState)?.Paused == true;
|
||||
}
|
||||
}
|
||||
|
|
@ -193,9 +193,8 @@ namespace Server.Network
|
|||
|
||||
if (compress)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
|
||||
|
||||
NetworkCompression.Compress(m_CompiledBuffer, 0, length, buffer, out length);
|
||||
var compressorBuffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
|
||||
NetworkCompression.Compress(m_CompiledBuffer, 0, length, compressorBuffer, out var compressedLength);
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
|
|
@ -214,28 +213,23 @@ namespace Server.Network
|
|||
length
|
||||
);
|
||||
op.WriteLine(new StackTrace());
|
||||
|
||||
ArrayPool<byte>.Shared.Return(compressorBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CompiledLength = length;
|
||||
|
||||
if ((m_State & State.Static) != 0)
|
||||
{
|
||||
m_CompiledBuffer = new byte[length];
|
||||
Buffer.BlockCopy(buffer, 0, m_CompiledBuffer, 0, length);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CompiledBuffer = buffer;
|
||||
m_State |= State.Buffered;
|
||||
}
|
||||
m_CompiledBuffer = compressorBuffer;
|
||||
m_CompiledLength = compressedLength;
|
||||
}
|
||||
}
|
||||
else if (length > 0)
|
||||
else
|
||||
{
|
||||
m_CompiledLength = length;
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
var old = m_CompiledBuffer;
|
||||
m_CompiledLength = length;
|
||||
|
||||
if ((m_State & State.Static) != 0)
|
||||
{
|
||||
|
|
@ -243,11 +237,17 @@ namespace Server.Network
|
|||
}
|
||||
else
|
||||
{
|
||||
// Release it later using Release()
|
||||
m_CompiledBuffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
m_State |= State.Buffered;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length);
|
||||
|
||||
if (compress)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(old);
|
||||
}
|
||||
}
|
||||
|
||||
PacketWriter.ReleaseInstance(Stream);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: PacketHandlers.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.ContextMenus;
|
||||
|
|
@ -34,9 +48,6 @@ namespace Server.Network
|
|||
{
|
||||
public delegate void PlayCharCallback(NetState state, bool val);
|
||||
|
||||
private const int BadFood = unchecked((int)0xBAADF00D);
|
||||
private const int BadUOTD = unchecked((int)0xFFCEFFCE);
|
||||
|
||||
private const int m_AuthIDWindowSize = 128;
|
||||
private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100];
|
||||
|
||||
|
|
@ -55,8 +66,6 @@ namespace Server.Network
|
|||
private static readonly Dictionary<int, AuthIDPersistence> m_AuthIDWindow =
|
||||
new Dictionary<int, AuthIDPersistence>(m_AuthIDWindowSize);
|
||||
|
||||
private static readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
|
||||
|
||||
static PacketHandlers()
|
||||
{
|
||||
Register(0x00, 104, false, CreateCharacter);
|
||||
|
|
@ -272,15 +281,11 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public static int ProcessPacket(IMessagePumpService pump, NetState ns, in ReadOnlySequence<byte> seq)
|
||||
public static int ProcessPacket(NetState ns, ArraySegment<byte>[] segments)
|
||||
{
|
||||
var r = new PacketReader(seq);
|
||||
var reader = new PacketReader(segments);
|
||||
|
||||
if (!r.TryReadByte(out var packetId))
|
||||
{
|
||||
ns.Dispose();
|
||||
return -1;
|
||||
}
|
||||
var packetId = reader.ReadByte();
|
||||
|
||||
if (!ns.Seeded)
|
||||
{
|
||||
|
|
@ -292,12 +297,11 @@ namespace Server.Network
|
|||
}
|
||||
else
|
||||
{
|
||||
var seed = (packetId << 24) | (r.ReadByte() << 16) | (r.ReadByte() << 8) | r.ReadByte();
|
||||
var seed = (packetId << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte();
|
||||
|
||||
if (seed == 0)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns);
|
||||
ns.Dispose();
|
||||
ns.WriteConsole("Invalid client detected, disconnecting");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +314,6 @@ namespace Server.Network
|
|||
|
||||
if (ns.CheckEncrypted(packetId))
|
||||
{
|
||||
ns.Dispose();
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -319,34 +322,29 @@ namespace Server.Network
|
|||
|
||||
if (handler == null)
|
||||
{
|
||||
r.Trace(ns);
|
||||
reader.Trace(ns);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var packetLength = handler.Length;
|
||||
if (handler.Length <= 0 && r.Length >= 3)
|
||||
if (handler.Length <= 0 && reader.Length >= 3)
|
||||
{
|
||||
packetLength = r.ReadUInt16();
|
||||
packetLength = reader.ReadUInt16();
|
||||
if (packetLength < 3)
|
||||
{
|
||||
ns.Dispose();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (r.Length < packetLength)
|
||||
// Not enough data, let's wait for more to come in
|
||||
if (reader.Length < packetLength)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (handler.Ingame && ns.Mobile?.Deleted != false)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Client: {0}: Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.",
|
||||
ns,
|
||||
packetId
|
||||
);
|
||||
ns.Dispose();
|
||||
ns.WriteConsole("Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", ns, packetId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -357,14 +355,7 @@ namespace Server.Network
|
|||
ns.ThrottledUntil = DateTime.UtcNow + throttled;
|
||||
}
|
||||
|
||||
var packet = seq.Slice(r.Position);
|
||||
var length = (int)packet.Length;
|
||||
var memOwner = _memoryPool.Rent(length);
|
||||
|
||||
// TODO: This is slow, find another way
|
||||
packet.CopyTo(memOwner.Memory.Span);
|
||||
|
||||
pump.QueueWork(ns, memOwner, length, handler.OnReceive);
|
||||
handler.OnReceive(ns, reader);
|
||||
|
||||
return packetLength;
|
||||
}
|
||||
|
|
@ -403,9 +394,8 @@ namespace Server.Network
|
|||
{
|
||||
if (ph.Ingame && state.Mobile == null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile",
|
||||
state,
|
||||
state.WriteConsole(
|
||||
"Sent ingame packet (0xD7x{0:X2}) before having been attached to a mobile",
|
||||
packetId
|
||||
);
|
||||
state.Dispose();
|
||||
|
|
@ -432,7 +422,7 @@ namespace Server.Network
|
|||
|
||||
if (targ != null)
|
||||
{
|
||||
EventSink.InvokeRenameRequest(from, targ, reader.ReadStringSafe());
|
||||
EventSink.InvokeRenameRequest(from, targ, reader.ReadAsciiSafe());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,7 +516,7 @@ namespace Server.Network
|
|||
|
||||
if (flag == 0x02)
|
||||
{
|
||||
var msgSize = (int)reader.Remaining;
|
||||
var msgSize = reader.Remaining;
|
||||
|
||||
if (msgSize / 7 > 100)
|
||||
{
|
||||
|
|
@ -616,7 +606,7 @@ namespace Server.Network
|
|||
|
||||
Serial serial = reader.ReadUInt32();
|
||||
int unk = reader.ReadByte();
|
||||
var lang = reader.ReadString(3);
|
||||
var lang = reader.ReadAscii(3);
|
||||
|
||||
if (serial.IsItem)
|
||||
{
|
||||
|
|
@ -692,10 +682,10 @@ namespace Server.Network
|
|||
int v1 = reader.ReadByte();
|
||||
int v2 = reader.ReadUInt16();
|
||||
int v3 = reader.ReadByte();
|
||||
var s1 = reader.ReadString(32);
|
||||
var s2 = reader.ReadString(32);
|
||||
var s3 = reader.ReadString(32);
|
||||
var s4 = reader.ReadString(32);
|
||||
var s1 = reader.ReadAscii(32);
|
||||
var s2 = reader.ReadAscii(32);
|
||||
var s3 = reader.ReadAscii(32);
|
||||
var s4 = reader.ReadAscii(32);
|
||||
int v4 = reader.ReadUInt16();
|
||||
int v5 = reader.ReadUInt16();
|
||||
var v6 = reader.ReadInt32();
|
||||
|
|
@ -710,7 +700,7 @@ namespace Server.Network
|
|||
public static void TextCommand(NetState state, PacketReader reader)
|
||||
{
|
||||
int type = reader.ReadByte();
|
||||
var command = reader.ReadString();
|
||||
var command = reader.ReadAscii();
|
||||
|
||||
var m = state.Mobile;
|
||||
|
||||
|
|
@ -794,7 +784,7 @@ namespace Server.Network
|
|||
}
|
||||
default:
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Unknown text-command type 0x{1:X2}: {2}", state, type, command);
|
||||
state.WriteConsole("Unknown text-command type 0x{0:X2}: {1}", state, type, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -805,7 +795,7 @@ namespace Server.Network
|
|||
var serial = reader.ReadUInt32();
|
||||
var prompt = reader.ReadInt32();
|
||||
var type = reader.ReadInt32();
|
||||
var text = reader.ReadStringSafe();
|
||||
var text = reader.ReadAsciiSafe();
|
||||
|
||||
if (text.Length > 128)
|
||||
{
|
||||
|
|
@ -835,8 +825,8 @@ namespace Server.Network
|
|||
var serial = reader.ReadUInt32();
|
||||
var prompt = reader.ReadInt32();
|
||||
var type = reader.ReadInt32();
|
||||
var lang = reader.ReadString(4);
|
||||
var text = reader.ReadUnicodeStringLESafe();
|
||||
var lang = reader.ReadAscii(4);
|
||||
var text = reader.ReadLittleUniSafe();
|
||||
|
||||
if (text.Length > 128)
|
||||
{
|
||||
|
|
@ -922,7 +912,7 @@ namespace Server.Network
|
|||
return;
|
||||
}
|
||||
|
||||
var text = reader.ReadUnicodeString(length);
|
||||
var text = reader.ReadBigUni(length);
|
||||
|
||||
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
|
||||
|
||||
|
|
@ -1259,7 +1249,7 @@ namespace Server.Network
|
|||
return;
|
||||
}
|
||||
|
||||
var text = reader.ReadUnicodeStringSafe(textLength);
|
||||
var text = reader.ReadBigUniSafe(textLength);
|
||||
textEntries[j] = new TextRelay(entryID, text);
|
||||
}
|
||||
|
||||
|
|
@ -1336,7 +1326,7 @@ namespace Server.Network
|
|||
var type = (MessageType)reader.ReadByte();
|
||||
int hue = reader.ReadInt16();
|
||||
reader.ReadInt16(); // font
|
||||
var text = reader.ReadStringSafe().Trim();
|
||||
var text = reader.ReadAsciiSafe().Trim();
|
||||
|
||||
if (text.Length <= 0 || text.Length > 128)
|
||||
{
|
||||
|
|
@ -1358,7 +1348,7 @@ namespace Server.Network
|
|||
var type = (MessageType)reader.ReadByte();
|
||||
int hue = reader.ReadInt16();
|
||||
reader.ReadInt16(); // font
|
||||
var lang = reader.ReadString(4);
|
||||
var lang = reader.ReadAscii(4);
|
||||
string text;
|
||||
|
||||
var isEncoded = (type & MessageType.Encoded) != 0;
|
||||
|
|
@ -1401,13 +1391,13 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
text = reader.ReadUTF8StringSafe();
|
||||
text = reader.ReadUTF8Safe();
|
||||
|
||||
keywords = keyList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
text = reader.ReadUnicodeStringSafe();
|
||||
text = reader.ReadBigUniSafe();
|
||||
|
||||
keywords = m_EmptyInts;
|
||||
}
|
||||
|
|
@ -1603,9 +1593,8 @@ namespace Server.Network
|
|||
{
|
||||
if (state.Mobile == null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Client: {0}: Sent in-game packet (0xBFx{1:X2}) before having been attached to a mobile",
|
||||
state,
|
||||
state.WriteConsole(
|
||||
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
|
||||
packetID
|
||||
);
|
||||
}
|
||||
|
|
@ -1804,13 +1793,13 @@ namespace Server.Network
|
|||
PartyCommands.Handler?.OnPrivateMessage(
|
||||
state.Mobile,
|
||||
World.FindMobile(reader.ReadUInt32()),
|
||||
reader.ReadUnicodeStringSafe()
|
||||
reader.ReadBigUniSafe()
|
||||
);
|
||||
}
|
||||
|
||||
public static void PartyMessage_PublicMessage(NetState state, PacketReader reader)
|
||||
{
|
||||
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadUnicodeStringSafe());
|
||||
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe());
|
||||
}
|
||||
|
||||
public static void PartyMessage_SetCanLoot(NetState state, PacketReader reader)
|
||||
|
|
@ -1980,7 +1969,7 @@ namespace Server.Network
|
|||
|
||||
public static void Language(NetState state, PacketReader reader)
|
||||
{
|
||||
var lang = reader.ReadString(4);
|
||||
var lang = reader.ReadAscii(4);
|
||||
|
||||
if (state.Mobile != null)
|
||||
{
|
||||
|
|
@ -1991,12 +1980,12 @@ namespace Server.Network
|
|||
public static void AssistVersion(NetState state, PacketReader reader)
|
||||
{
|
||||
var unk = reader.ReadInt32();
|
||||
var av = reader.ReadString();
|
||||
var av = reader.ReadAscii();
|
||||
}
|
||||
|
||||
public static void ClientVersion(NetState state, PacketReader reader)
|
||||
{
|
||||
var version = state.Version = new CV(reader.ReadString());
|
||||
var version = state.Version = new CV(reader.ReadAscii());
|
||||
|
||||
EventSink.InvokeClientVersionReceived(state, version);
|
||||
}
|
||||
|
|
@ -2006,7 +1995,7 @@ namespace Server.Network
|
|||
reader.ReadUInt16();
|
||||
|
||||
int type = reader.ReadUInt16();
|
||||
var version = state.Version = new CV(reader.ReadString());
|
||||
var version = state.Version = new CV(reader.ReadAscii());
|
||||
|
||||
EventSink.InvokeClientVersionReceived(state, version);
|
||||
}
|
||||
|
|
@ -2046,7 +2035,7 @@ namespace Server.Network
|
|||
{
|
||||
reader.ReadInt32(); // 0xEDEDEDED
|
||||
|
||||
var name = reader.ReadString(30);
|
||||
var name = reader.ReadAscii(30);
|
||||
|
||||
reader.Seek(2, SeekOrigin.Current);
|
||||
|
||||
|
|
@ -2074,7 +2063,7 @@ namespace Server.Network
|
|||
|
||||
if (check != null && check.Map != Map.Internal && check != m)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Account in use", state);
|
||||
state.WriteConsole("Account in use");
|
||||
state.Send(new PopupMessage(PMMessage.CharInWorld));
|
||||
return;
|
||||
}
|
||||
|
|
@ -2217,7 +2206,7 @@ namespace Server.Network
|
|||
var unk1 = reader.ReadInt32();
|
||||
var unk2 = reader.ReadInt32();
|
||||
int unk3 = reader.ReadByte();
|
||||
var name = reader.ReadString(30);
|
||||
var name = reader.ReadAscii(30);
|
||||
|
||||
reader.Seek(2, SeekOrigin.Current);
|
||||
var flags = reader.ReadInt32();
|
||||
|
|
@ -2292,7 +2281,7 @@ namespace Server.Network
|
|||
|
||||
if (check != null && check.Map != Map.Internal)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Account in use", state);
|
||||
state.WriteConsole("Account in use");
|
||||
state.Send(new PopupMessage(PMMessage.CharInWorld));
|
||||
return;
|
||||
}
|
||||
|
|
@ -2353,7 +2342,7 @@ namespace Server.Network
|
|||
var unk1 = reader.ReadInt32();
|
||||
var unk2 = reader.ReadInt32();
|
||||
int unk3 = reader.ReadByte();
|
||||
var name = reader.ReadString(30);
|
||||
var name = reader.ReadAscii(30);
|
||||
|
||||
reader.Seek(2, SeekOrigin.Current);
|
||||
var flags = reader.ReadInt32();
|
||||
|
|
@ -2417,7 +2406,7 @@ namespace Server.Network
|
|||
|
||||
if (check != null && check.Map != Map.Internal)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Account in use", state);
|
||||
state.WriteConsole("Account in use");
|
||||
state.Send(new PopupMessage(PMMessage.CharInWorld));
|
||||
return;
|
||||
}
|
||||
|
|
@ -2530,27 +2519,27 @@ namespace Server.Network
|
|||
}
|
||||
else if (ClientVerification)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
|
||||
state.WriteConsole("Invalid client detected, disconnecting");
|
||||
state.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.m_AuthID != 0 && authID != state.m_AuthID)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
|
||||
state.WriteConsole("Invalid client detected, disconnecting");
|
||||
state.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.m_AuthID == 0 && authID != state.m_Seed)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
|
||||
state.WriteConsole("Invalid client detected, disconnecting");
|
||||
state.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var username = reader.ReadString(30);
|
||||
var password = reader.ReadString(30);
|
||||
var username = reader.ReadAscii(30);
|
||||
var password = reader.ReadAscii(30);
|
||||
|
||||
var e = new GameLoginEventArgs(state, username, password);
|
||||
|
||||
|
|
@ -2559,7 +2548,7 @@ namespace Server.Network
|
|||
if (e.Accepted)
|
||||
{
|
||||
state.CityInfo = e.CityInfo;
|
||||
state.CompressionEnabled = true;
|
||||
// state.CompressionEnabled = true;
|
||||
|
||||
state.Send(SupportedFeatures.Instantiate(state));
|
||||
|
||||
|
|
@ -2606,7 +2595,7 @@ namespace Server.Network
|
|||
|
||||
if (state.m_Seed == 0)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
|
||||
state.WriteConsole("Invalid client detected, disconnecting");
|
||||
state.Dispose();
|
||||
return;
|
||||
}
|
||||
|
|
@ -2631,15 +2620,15 @@ namespace Server.Network
|
|||
var z = reader.ReadSByte();
|
||||
var map = reader.ReadByte();
|
||||
|
||||
var account = reader.ReadString(32);
|
||||
var character = reader.ReadString(32);
|
||||
var ip = reader.ReadString(15);
|
||||
var account = reader.ReadAscii(32);
|
||||
var character = reader.ReadAscii(32);
|
||||
var ip = reader.ReadAscii(15);
|
||||
|
||||
var unk1 = reader.ReadInt32();
|
||||
var exception = reader.ReadInt32();
|
||||
|
||||
var process = reader.ReadString(100);
|
||||
var report = reader.ReadString(100);
|
||||
var process = reader.ReadAscii(100);
|
||||
var report = reader.ReadAscii(100);
|
||||
|
||||
reader.ReadByte(); // 0x00
|
||||
|
||||
|
|
@ -2663,8 +2652,8 @@ namespace Server.Network
|
|||
|
||||
state.SentFirstPacket = true;
|
||||
|
||||
var username = reader.ReadString(30);
|
||||
var password = reader.ReadString(30);
|
||||
var username = reader.ReadAscii(30);
|
||||
var password = reader.ReadAscii(30);
|
||||
|
||||
var e = new AccountLoginEventArgs(state, username, password);
|
||||
|
||||
|
|
@ -2707,7 +2696,7 @@ namespace Server.Network
|
|||
state.Dispose();
|
||||
}
|
||||
|
||||
public static void EquipMacro(NetState ns, PacketReader reader)
|
||||
public static void EquipMacro(NetState state, PacketReader reader)
|
||||
{
|
||||
int count = reader.ReadByte();
|
||||
var serialList = new List<Serial>(count);
|
||||
|
|
@ -2716,10 +2705,10 @@ namespace Server.Network
|
|||
serialList.Add(reader.ReadUInt32());
|
||||
}
|
||||
|
||||
EventSink.InvokeEquipMacro(ns.Mobile, serialList);
|
||||
EventSink.InvokeEquipMacro(state.Mobile, serialList);
|
||||
}
|
||||
|
||||
public static void UnequipMacro(NetState ns, PacketReader reader)
|
||||
public static void UnequipMacro(NetState state, PacketReader reader)
|
||||
{
|
||||
int count = reader.ReadByte();
|
||||
var layers = new List<Layer>(count);
|
||||
|
|
@ -2728,30 +2717,30 @@ namespace Server.Network
|
|||
layers.Add((Layer)reader.ReadUInt16());
|
||||
}
|
||||
|
||||
EventSink.InvokeUnequipMacro(ns.Mobile, layers);
|
||||
EventSink.InvokeUnequipMacro(state.Mobile, layers);
|
||||
}
|
||||
|
||||
public static void TargetedSpell(NetState ns, PacketReader reader)
|
||||
public static void TargetedSpell(NetState state, PacketReader reader)
|
||||
{
|
||||
var spellId = (short)(reader.ReadInt16() - 1); // zero based;
|
||||
|
||||
EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
|
||||
EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
|
||||
}
|
||||
|
||||
public static void TargetedSkillUse(NetState ns, PacketReader reader)
|
||||
public static void TargetedSkillUse(NetState state, PacketReader reader)
|
||||
{
|
||||
var skillId = reader.ReadInt16();
|
||||
|
||||
EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
|
||||
EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
|
||||
}
|
||||
|
||||
public static void TargetByResourceMacro(NetState ns, PacketReader reader)
|
||||
public static void TargetByResourceMacro(NetState state, PacketReader reader)
|
||||
{
|
||||
Serial serial = reader.ReadUInt32();
|
||||
|
||||
if (serial.IsItem)
|
||||
{
|
||||
EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), reader.ReadInt16());
|
||||
EventSink.InvokeTargetByResourceMacro(state.Mobile, World.FindItem(serial), reader.ReadInt16());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +1,68 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: PacketReader.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public ref struct PacketReader
|
||||
{
|
||||
private SequenceReader<byte> m_Reader;
|
||||
public Span<byte> First;
|
||||
public Span<byte> Second;
|
||||
|
||||
public SequencePosition Position => m_Reader.Position;
|
||||
public long Length => m_Reader.Length;
|
||||
public long Consumed => m_Reader.Consumed;
|
||||
public long Remaining => m_Reader.Remaining;
|
||||
public int Length { get; }
|
||||
public int Position { get; private set; }
|
||||
public int Remaining => Length - Position;
|
||||
|
||||
public PacketReader(ReadOnlySequence<byte> seq) => m_Reader = new SequenceReader<byte>(seq);
|
||||
public PacketReader(ArraySegment<byte>[] buffers)
|
||||
{
|
||||
First = buffers[0];
|
||||
Second = buffers[1];
|
||||
Position = 0;
|
||||
Length = First.Length + Second.Length;
|
||||
}
|
||||
|
||||
public byte Peek() => m_Reader.TryPeek(out var value) ? value : (byte)0;
|
||||
public PacketReader(Span<byte> first, Span<byte> second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Position = 0;
|
||||
Length = first.Length + second.Length;
|
||||
}
|
||||
|
||||
public void Trace(NetState state)
|
||||
{
|
||||
// We don't have data, so nothing to trace
|
||||
if (First.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var sw = new StreamWriter("Packets.log", true);
|
||||
var buffer = m_Reader.Sequence.ToArray();
|
||||
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, buffer[0]);
|
||||
}
|
||||
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, First[0]);
|
||||
|
||||
using (var ms = new MemoryStream(buffer))
|
||||
{
|
||||
Utility.FormatBuffer(sw, ms, buffer.Length);
|
||||
}
|
||||
Utility.FormatBuffer(sw, First.ToArray(), new Memory<byte>(Second.ToArray()));
|
||||
|
||||
sw.WriteLine();
|
||||
sw.WriteLine();
|
||||
|
|
@ -44,319 +73,279 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public SequencePosition Seek(long offset, SeekOrigin origin)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte ReadByte()
|
||||
{
|
||||
switch (origin)
|
||||
if (Position < First.Length)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
if (offset < m_Reader.Consumed)
|
||||
{
|
||||
m_Reader.Rewind(m_Reader.Consumed - Math.Max(offset, 0L));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Reader.Advance(offset - m_Reader.Consumed);
|
||||
}
|
||||
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
if (offset < 0)
|
||||
{
|
||||
m_Reader.Rewind(Math.Min(m_Reader.Consumed, offset * -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Reader.Advance(Math.Min(m_Reader.Remaining, offset));
|
||||
}
|
||||
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
var count = m_Reader.Remaining - offset;
|
||||
if (count < 0)
|
||||
{
|
||||
m_Reader.Rewind(count * -1);
|
||||
}
|
||||
else if (count > 0)
|
||||
{
|
||||
m_Reader.Advance(count);
|
||||
}
|
||||
|
||||
break;
|
||||
return First[Position++];
|
||||
}
|
||||
|
||||
return m_Reader.Position;
|
||||
if (Position < Length)
|
||||
{
|
||||
return Second[Position++ - First.Length];
|
||||
}
|
||||
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
public bool TryReadByte(out byte value) => m_Reader.TryRead(out value);
|
||||
|
||||
public int ReadInt32() => m_Reader.TryReadBigEndian(out int value) ? value : 0;
|
||||
|
||||
public short ReadInt16() => m_Reader.TryReadBigEndian(out short value) ? value : (short)0;
|
||||
|
||||
public byte ReadByte() => m_Reader.TryRead(out var value) ? value : (byte)0;
|
||||
|
||||
public uint ReadUInt32() => (uint)ReadInt32();
|
||||
|
||||
public ushort ReadUInt16() => (ushort)ReadInt16();
|
||||
|
||||
public sbyte ReadSByte() => (sbyte)ReadByte();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ReadBoolean() => ReadByte() > 0;
|
||||
|
||||
public string ReadUnicodeStringLE()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public sbyte ReadSByte() => (sbyte)ReadByte();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public short ReadInt16()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
short value;
|
||||
|
||||
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
|
||||
if (Position < First.Length)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadUnicodeStringLE(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
{
|
||||
m_Reader.Advance(fixedLength);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadUnicodeStringLESafe(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
if (!BinaryPrimitives.TryReadInt16BigEndian(First.Slice(Position), out value))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
// Not enough space. Split the spans
|
||||
return (short)((ReadByte() >> 8) | ReadByte());
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
else if (!BinaryPrimitives.TryReadInt16BigEndian(Second.Slice(Position - First.Length), out value))
|
||||
{
|
||||
m_Reader.Advance(fixedLength * 2);
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
Position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
public string ReadUnicodeStringLESafe()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
ushort value;
|
||||
|
||||
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
|
||||
if (Position < First.Length)
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
if (!BinaryPrimitives.TryReadUInt16BigEndian(First.Slice(Position), out value))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
// Not enough space. Split the spans
|
||||
return (ushort)((ReadByte() >> 8) | ReadByte());
|
||||
}
|
||||
}
|
||||
else if (!BinaryPrimitives.TryReadUInt16BigEndian(Second.Slice(Position - First.Length), out value))
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
Position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
public string ReadUnicodeStringSafe()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ReadInt32()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
int value;
|
||||
|
||||
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
|
||||
if (Position < First.Length)
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
if (!BinaryPrimitives.TryReadInt32BigEndian(First.Slice(Position), out value))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
// Not enough space. Split the spans
|
||||
return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadUnicodeString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
|
||||
else if (!BinaryPrimitives.TryReadInt32BigEndian(Second.Slice(Position - First.Length), out value))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
Position += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE;
|
||||
|
||||
public string ReadUTF8StringSafe(int fixedLength)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
string s;
|
||||
uint value;
|
||||
|
||||
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
|
||||
if (Position < First.Length)
|
||||
{
|
||||
s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span);
|
||||
if (!BinaryPrimitives.TryReadUInt32BigEndian(First.Slice(Position), out value))
|
||||
{
|
||||
// Not enough space. Split the spans
|
||||
return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte());
|
||||
}
|
||||
}
|
||||
else if (!BinaryPrimitives.TryReadUInt32BigEndian(Second.Slice(Position - First.Length), out value))
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
Position += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadString<T>(Encoding encoding, bool safeString = false, int fixedLength = -1) where T : struct, IEquatable<T>
|
||||
{
|
||||
int sizeT = Unsafe.SizeOf<T>();
|
||||
|
||||
if (sizeT > 2)
|
||||
{
|
||||
throw new InvalidConstraintException("ReadString only accepts byte, sbyte, char, short, and ushort as a constraint");
|
||||
}
|
||||
|
||||
bool isFixedLength = fixedLength > -1;
|
||||
|
||||
var remaining = Remaining;
|
||||
int size;
|
||||
|
||||
// Not fixed length
|
||||
if (isFixedLength)
|
||||
{
|
||||
size = fixedLength * sizeT;
|
||||
if (fixedLength > Remaining)
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = Math.Min(m_Reader.Remaining, fixedLength);
|
||||
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, size).ToArray());
|
||||
m_Reader.Advance(size);
|
||||
size = remaining - (remaining & (sizeT - 1));
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(s.Length);
|
||||
Span<byte> span;
|
||||
int index;
|
||||
|
||||
for (var i = 0; i < s.Length; ++i)
|
||||
if (Position < First.Length)
|
||||
{
|
||||
if (IsSafeChar(s[i]))
|
||||
// Find terminator
|
||||
index = MemoryMarshal
|
||||
.Cast<byte, T>(First.Slice(Position, Math.Min(size, First.Length)))
|
||||
.IndexOf(default(T)) * sizeT;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
sb.Append(s[i]);
|
||||
remaining = size - First.Length;
|
||||
// We don't have a terminator, but a fixed size to the end of the first span, so stop there
|
||||
if (remaining <= 0)
|
||||
{
|
||||
index = First.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = MemoryMarshal
|
||||
.Cast<byte, T>(Second.Slice(0, remaining))
|
||||
.IndexOf(default(T)) * sizeT;
|
||||
|
||||
int secondLength = index < 0 ? remaining : index;
|
||||
int length = First.Length + secondLength;
|
||||
|
||||
// Assume no strings should be too long for the stack
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
First.Slice(Position).CopyTo(bytes);
|
||||
Second.Slice(0, secondLength).CopyTo(bytes.Slice(First.Length));
|
||||
|
||||
Position += length;
|
||||
return GetString(bytes, encoding, safeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadUTF8StringSafe()
|
||||
{
|
||||
string s;
|
||||
|
||||
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
|
||||
{
|
||||
s = Utility.UTF8.GetString(span);
|
||||
span = First.Slice(Position, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray());
|
||||
m_Reader.Advance(m_Reader.Remaining);
|
||||
}
|
||||
span = Second.Slice( Position - First.Length, Math.Min(remaining, size));
|
||||
index = MemoryMarshal.Cast<byte, T>(span).IndexOf(default(T)) * sizeT;
|
||||
|
||||
var sb = new StringBuilder(s.Length);
|
||||
|
||||
for (var i = 0; i < s.Length; ++i)
|
||||
{
|
||||
if (IsSafeChar(s[i]))
|
||||
if (index >= 0)
|
||||
{
|
||||
sb.Append(s[i]);
|
||||
span = span.Slice(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
Position += isFixedLength ? size : index;
|
||||
return GetString(span, encoding, safeString);
|
||||
}
|
||||
|
||||
public string ReadUTF8String() =>
|
||||
Utility.UTF8.GetString(
|
||||
m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0')
|
||||
? span
|
||||
: m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()
|
||||
);
|
||||
|
||||
public string ReadString()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string GetString(Span<byte> span, Encoding encoding, bool safeString = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
string s = encoding.GetString(span);
|
||||
|
||||
while (m_Reader.TryRead(out var c))
|
||||
if (!safeString)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
return s;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
ReadOnlySpan<char> chars = s.AsSpan();
|
||||
|
||||
public string ReadStringSafe()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
StringBuilder stringBuilder = null;
|
||||
|
||||
while (m_Reader.TryRead(out var c))
|
||||
for (int i = 0, last = 0; i < chars.Length; i++)
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
(stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last));
|
||||
last = i + 1; // Skip the unsafe char
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
return stringBuilder?.ToString() ?? s;
|
||||
}
|
||||
|
||||
public string ReadUnicodeStringSafe(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadLittleUniSafe(int fixedLength) => ReadString<char>(Utility.UnicodeLE, true, fixedLength);
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadLittleUniSafe() => ReadString<char>(Utility.UnicodeLE, true);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadLittleUni(int fixedLength) => ReadString<char>(Utility.UnicodeLE, false, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadLittleUni() => ReadString<char>(Utility.UnicodeLE);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadBigUniSafe(int fixedLength) => ReadString<char>(Utility.Unicode, true, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadBigUniSafe() => ReadString<char>(Utility.Unicode, true);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadBigUni(int fixedLength) => ReadString<char>(Utility.Unicode, false, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadBigUni() => ReadString<char>(Utility.Unicode);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadUTF8Safe(int fixedLength) => ReadString<byte>(Utility.UTF8, true, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadUTF8Safe() => ReadString<byte>(Utility.UTF8, true);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadUTF8() => ReadString<byte>(Utility.UTF8);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadAsciiSafe(int fixedLength) => ReadString<byte>(Encoding.ASCII, true, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadAsciiSafe() => ReadString<byte>(Encoding.ASCII, true);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadAscii(int fixedLength) => ReadString<byte>(Encoding.ASCII, false, fixedLength);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadAscii() => ReadString<byte>(Encoding.ASCII);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Seek(int offset, SeekOrigin origin) =>
|
||||
Position = origin switch
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
{
|
||||
m_Reader.Advance(fixedLength * 2);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadUnicodeString(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
{
|
||||
m_Reader.Advance(fixedLength * 2);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadStringSafe(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
|
||||
{
|
||||
if (IsSafeChar(c))
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
{
|
||||
m_Reader.Advance(fixedLength);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ReadString(int fixedLength)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
|
||||
{
|
||||
sb.Append((char)c);
|
||||
}
|
||||
|
||||
if (fixedLength > 0)
|
||||
{
|
||||
m_Reader.Advance(fixedLength);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.End => Length - offset,
|
||||
_ => Position + offset // Current
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ namespace Server.Network
|
|||
private static readonly MessageLocalized[] m_Cache_CliLocCmp = new MessageLocalized[5000];
|
||||
|
||||
public MessageLocalized(
|
||||
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
|
||||
string args
|
||||
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, string args
|
||||
) : base(0xC1)
|
||||
{
|
||||
name ??= "";
|
||||
|
|
|
|||
356
Projects/Server/Network/Pipe.cs
Normal file
356
Projects/Server/Network/Pipe.cs
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Pipe.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public interface IPipeTask<T> : INotifyCompletion
|
||||
{
|
||||
public IPipeTask<T> GetAwaiter();
|
||||
|
||||
public bool IsCompleted { get; }
|
||||
|
||||
public T GetResult();
|
||||
|
||||
public void OnCompleted(Action continuation);
|
||||
}
|
||||
|
||||
public class Pipe<T>
|
||||
{
|
||||
public struct Result<T>
|
||||
{
|
||||
public ArraySegment<T>[] Buffer { get; }
|
||||
public bool IsClosed { get; set; }
|
||||
|
||||
public int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var length = 0;
|
||||
for (int i = 0; i < Buffer.Length; i++)
|
||||
{
|
||||
length += Buffer[i].Count;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyFrom(ReadOnlySpan<T> bytes)
|
||||
{
|
||||
var remaining = bytes.Length;
|
||||
var offset = 0;
|
||||
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Buffer.Length; i++)
|
||||
{
|
||||
var buffer = Buffer[i];
|
||||
var sz = Math.Min(remaining, buffer.Count);
|
||||
bytes.Slice(offset, sz).CopyTo(buffer);
|
||||
|
||||
remaining -= sz;
|
||||
offset += sz;
|
||||
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
public Result(int segments)
|
||||
{
|
||||
IsClosed = false;
|
||||
Buffer = new ArraySegment<T>[segments];
|
||||
}
|
||||
}
|
||||
|
||||
public class PipeWriter<T>
|
||||
{
|
||||
private readonly Pipe<T> _pipe;
|
||||
|
||||
public PipeWriter(Pipe<T> pipe) => _pipe = pipe;
|
||||
|
||||
public Result<T> GetAvailable()
|
||||
{
|
||||
var read = _pipe._readIdx;
|
||||
var write = _pipe._writeIdx;
|
||||
|
||||
var result = new Result<T>(2) { IsClosed = _pipe._closed };
|
||||
|
||||
if (read <= write)
|
||||
{
|
||||
var readZero = read == 0;
|
||||
var sz = _pipe.Size - write - (readZero ? 1 : 0);
|
||||
|
||||
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
|
||||
result.Buffer[1] = readZero ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)read - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sz = read - write - 1;
|
||||
|
||||
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
|
||||
result.Buffer[1] = ArraySegment<T>.Empty;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Advance(uint bytes)
|
||||
{
|
||||
var read = _pipe._readIdx;
|
||||
var write = _pipe._writeIdx;
|
||||
|
||||
if (bytes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes > _pipe.Size - 1)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (read <= write)
|
||||
{
|
||||
if (bytes > read + _pipe.Size - write - 1)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var sz = Math.Min(bytes, _pipe.Size - write);
|
||||
|
||||
write += sz;
|
||||
if (write > _pipe.Size - 1)
|
||||
{
|
||||
write = 0;
|
||||
}
|
||||
bytes -= sz;
|
||||
|
||||
if (bytes > 0)
|
||||
{
|
||||
if (bytes >= read)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
write = bytes;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bytes > read - write - 1)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
write += bytes;
|
||||
}
|
||||
|
||||
// It's never valid to advance the write pointer to become equal to
|
||||
// the read pointer. Check that here.
|
||||
if (write == read)
|
||||
{
|
||||
throw new InvalidOperationException("Write index equals read index after advance");
|
||||
}
|
||||
|
||||
_pipe._writeIdx = write;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_pipe._closed = true;
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
var waiting = _pipe._awaitBeginning;
|
||||
|
||||
if (!waiting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action continuation;
|
||||
|
||||
do
|
||||
{
|
||||
continuation = _pipe._readerContinuation;
|
||||
} while (continuation == null);
|
||||
|
||||
_pipe._readerContinuation = null;
|
||||
_pipe._awaitBeginning = false;
|
||||
|
||||
ThreadPool.UnsafeQueueUserWorkItem(state => continuation(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public class PipeReader<T> : IPipeTask<Result<T>>
|
||||
{
|
||||
private readonly Pipe<T> _pipe;
|
||||
|
||||
internal PipeReader(Pipe<T> pipe) => _pipe = pipe;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint GetRemaining()
|
||||
{
|
||||
var read = _pipe._readIdx;
|
||||
var write = _pipe._writeIdx;
|
||||
|
||||
if (read <= write)
|
||||
{
|
||||
return write - read;
|
||||
}
|
||||
|
||||
return write + _pipe.Size - read;
|
||||
}
|
||||
|
||||
public Result<T> TryRead()
|
||||
{
|
||||
var read = _pipe._readIdx;
|
||||
var write = _pipe._writeIdx;
|
||||
|
||||
var result = new Result<T>(2) { IsClosed = _pipe._closed };
|
||||
|
||||
if (read <= write)
|
||||
{
|
||||
result.Buffer[0] = write - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(write - read));
|
||||
result.Buffer[1] = ArraySegment<T>.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Buffer[0] = _pipe.Size - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(_pipe.Size - read));
|
||||
result.Buffer[1] = write == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)write);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IPipeTask<Result<T>> Read()
|
||||
{
|
||||
if (_pipe._awaitBeginning)
|
||||
{
|
||||
throw new Exception("Double await on reader");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Advance(uint bytes)
|
||||
{
|
||||
var read = _pipe._readIdx;
|
||||
var write = _pipe._writeIdx;
|
||||
|
||||
if (read <= write)
|
||||
{
|
||||
if (bytes > write - read)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
read += bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sz = Math.Min(bytes, _pipe.Size - read);
|
||||
|
||||
read += sz;
|
||||
if (read > _pipe.Size - 1)
|
||||
{
|
||||
read = 0;
|
||||
}
|
||||
bytes -= sz;
|
||||
|
||||
if (bytes > 0)
|
||||
{
|
||||
if (bytes > write)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
read = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
_pipe._readIdx = read;
|
||||
}
|
||||
|
||||
#region Awaitable
|
||||
|
||||
// The following makes it possible to await the reader. Do not use any of this directly.
|
||||
|
||||
public IPipeTask<Result<T>> GetAwaiter() => this;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetRemaining() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_pipe._awaitBeginning = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<T> GetResult() => TryRead();
|
||||
|
||||
public void OnCompleted(Action continuation) => _pipe._readerContinuation = continuation;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
private readonly T[] _buffer;
|
||||
private volatile uint _writeIdx;
|
||||
private volatile uint _readIdx;
|
||||
private bool _closed;
|
||||
|
||||
public PipeWriter<T> Writer { get; }
|
||||
public PipeReader<T> Reader { get; }
|
||||
|
||||
public uint Size => (uint)_buffer.Length;
|
||||
|
||||
public Pipe(T[] buf)
|
||||
{
|
||||
_buffer = buf;
|
||||
_writeIdx = 0;
|
||||
_readIdx = 0;
|
||||
_closed = false;
|
||||
|
||||
Writer = new PipeWriter<T>(this);
|
||||
Reader = new PipeReader<T>(this);
|
||||
}
|
||||
|
||||
#region Awaitable
|
||||
private volatile bool _awaitBeginning;
|
||||
private volatile Action _readerContinuation;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerConnectionHandler.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public class ServerConnectionHandler : ConnectionHandler
|
||||
{
|
||||
private readonly ILogger<ServerConnectionHandler> _logger;
|
||||
private readonly IMessagePumpService _messagePumpService;
|
||||
|
||||
public ServerConnectionHandler(
|
||||
IMessagePumpService messagePumpService,
|
||||
ILogger<ServerConnectionHandler> logger
|
||||
)
|
||||
{
|
||||
_messagePumpService = messagePumpService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync(ConnectionContext connection)
|
||||
{
|
||||
if (!VerifySocket(connection))
|
||||
{
|
||||
Release(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
var ns = new NetState(connection);
|
||||
TcpServer.Instances.Add(ns);
|
||||
Console.WriteLine($"Client: {ns}: Connected. [{TcpServer.Instances.Count} Online]");
|
||||
|
||||
await ns.ProcessIncoming(_messagePumpService).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool VerifySocket(ConnectionContext connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
var args = new SocketConnectEventArgs(connection);
|
||||
|
||||
EventSink.InvokeSocketConnect(args);
|
||||
|
||||
return args.AllowConnection;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetState.TraceException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Release(ConnectionContext connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.Abort(new ConnectionAbortedException("Failed socket verification."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetState.TraceException(ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Is this needed?
|
||||
connection.DisposeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetState.TraceException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,79 +14,211 @@
|
|||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net.Sockets;
|
||||
using Server.Exceptions;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public static class TcpServer
|
||||
{
|
||||
private static IPAddress[] m_ListeningAddresses;
|
||||
private static NetworkState m_NetworkState = NetworkState.ResumeState;
|
||||
|
||||
// Make this thread safe
|
||||
public static List<NetState> Instances { get; } = new List<NetState>();
|
||||
// Sanity. 256 * 1024 * 5000 = ~1.3GB of ram
|
||||
public static int MaxConnections { get; set; } = 5000;
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args = null) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True")
|
||||
.ConfigureServices(services => { services.AddSingleton<IMessagePumpService>(new MessagePumpService()); })
|
||||
.UseKestrel(
|
||||
options =>
|
||||
{
|
||||
foreach (var ipep in ServerConfiguration.Listeners)
|
||||
{
|
||||
options.Listen(ipep, builder => { builder.UseConnectionHandler<ServerConnectionHandler>(); });
|
||||
m_ListeningAddresses = GetListeningAddresses(ipep);
|
||||
DisplayListener(ipep);
|
||||
}
|
||||
private const long _listenerErrorMessageDelay = 10000; // 10 seconds
|
||||
private static long _nextMaximumSocketsReachedMessage;
|
||||
|
||||
// Webservices here
|
||||
}
|
||||
)
|
||||
.UseLibuv()
|
||||
.UseStartup<ServerStartup>();
|
||||
// AccountLoginReject BadComm
|
||||
private static readonly byte[] socketRejected = { 0x82, 0xFF };
|
||||
|
||||
public static IPAddress[] GetListeningAddresses(IPEndPoint ipep)
|
||||
public static IPEndPoint[] ListeningAddresses { get; private set; }
|
||||
public static TcpListener[] Listeners { get; private set; }
|
||||
public static List<NetState> Instances { get; } = new List<NetState>(128);
|
||||
|
||||
public static ConcurrentQueue<NetState> m_ConnectedQueue = new ConcurrentQueue<NetState>();
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
if (m_ListeningAddresses != null)
|
||||
{
|
||||
return m_ListeningAddresses;
|
||||
}
|
||||
|
||||
var list = new List<IPAddress>();
|
||||
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
var properties = adapter.GetIPProperties();
|
||||
foreach (var unicast in properties.UnicastAddresses)
|
||||
{
|
||||
if (ipep.AddressFamily == unicast.Address.AddressFamily)
|
||||
{
|
||||
list.Add(unicast.Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections);
|
||||
}
|
||||
|
||||
private static void DisplayListener(IPEndPoint ipep)
|
||||
public static void Start()
|
||||
{
|
||||
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
|
||||
HashSet<IPEndPoint> listeningAddresses = new HashSet<IPEndPoint>();
|
||||
List<TcpListener> listeners = new List<TcpListener>();
|
||||
|
||||
foreach (var ipep in ServerConfiguration.Listeners)
|
||||
{
|
||||
foreach (var ip in m_ListeningAddresses)
|
||||
var listener = CreateListener(ipep);
|
||||
if (listener == null)
|
||||
{
|
||||
Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
|
||||
{
|
||||
listeningAddresses.UnionWith(GetListeningAddresses(ipep));
|
||||
}
|
||||
else
|
||||
{
|
||||
listeningAddresses.Add(ipep);
|
||||
}
|
||||
|
||||
listeners.Add(listener);
|
||||
listener.BeginAcceptingSockets();
|
||||
}
|
||||
else
|
||||
|
||||
foreach (var ipep in listeningAddresses)
|
||||
{
|
||||
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
|
||||
}
|
||||
|
||||
ListeningAddresses = listeningAddresses.ToArray();
|
||||
Listeners = listeners.ToArray();
|
||||
}
|
||||
|
||||
public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) =>
|
||||
NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter =>
|
||||
adapter.GetIPProperties().UnicastAddresses
|
||||
.Where(uip => ipep.AddressFamily == uip.Address.AddressFamily)
|
||||
.Select(uip => new IPEndPoint(uip.Address, ipep.Port))
|
||||
);
|
||||
|
||||
public static TcpListener CreateListener(IPEndPoint ipep)
|
||||
{
|
||||
var listener = new TcpListener(ipep);
|
||||
listener.Server.ExclusiveAddressUse = false;
|
||||
|
||||
try
|
||||
{
|
||||
listener.Start(8);
|
||||
return listener;
|
||||
}
|
||||
catch (SocketException se)
|
||||
{
|
||||
// WSAEADDRINUSE
|
||||
if (se.ErrorCode == 10048)
|
||||
{
|
||||
Console.WriteLine("Listener: {0}:{1}: Failed (In Use)", ipep.Address, ipep.Port);
|
||||
}
|
||||
// WSAEADDRNOTAVAIL
|
||||
else if (se.ErrorCode == 10049)
|
||||
{
|
||||
Console.WriteLine("Listener {0}:{1}: Failed (Unavailable)", ipep.Address, ipep.Port);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Listener Exception:");
|
||||
Console.WriteLine(se);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the TcpServer and stops accepting new sockets.
|
||||
* This is thread-safe without using locks.
|
||||
*/
|
||||
public static void Pause()
|
||||
{
|
||||
NetworkState.Pause(ref m_NetworkState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes accepting sockets on the TcpServer.
|
||||
* This is thread-safe using a lock on the listeners
|
||||
*/
|
||||
public static void Resume()
|
||||
{
|
||||
if (!NetworkState.Resume(ref m_NetworkState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (Listeners)
|
||||
{
|
||||
foreach (var listener in Listeners)
|
||||
{
|
||||
listener.BeginAcceptingSockets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Slice()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (count++ < 250)
|
||||
{
|
||||
if (!m_ConnectedQueue.TryDequeue(out var ns))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Instances.Add(ns);
|
||||
ns.WriteConsole("Connected. [{0} Online]", Instances.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private static async void BeginAcceptingSockets(this TcpListener listener)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (m_NetworkState.Paused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Socket socket = null;
|
||||
|
||||
try
|
||||
{
|
||||
socket = await listener.AcceptSocketAsync();
|
||||
if (Instances.Count >= MaxConnections)
|
||||
{
|
||||
socket.Send(socketRejected, SocketFlags.None);
|
||||
socket.Shutdown(SocketShutdown.Both);
|
||||
socket.Close();
|
||||
throw new MaxConnectionsException();
|
||||
}
|
||||
|
||||
var args = new SocketConnectEventArgs(socket);
|
||||
EventSink.InvokeSocketConnect(args);
|
||||
|
||||
if (!args.AllowConnection)
|
||||
{
|
||||
socket.Send(socketRejected, SocketFlags.None);
|
||||
socket.Shutdown(SocketShutdown.Both);
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
catch (MaxConnectionsException)
|
||||
{
|
||||
var ticks = Core.TickCount;
|
||||
|
||||
if (_nextMaximumSocketsReachedMessage <= ticks)
|
||||
{
|
||||
var ipep = (IPEndPoint)socket!.RemoteEndPoint;
|
||||
Console.WriteLine("Listener {0}:{1}: Failed (Maximum connections reached)", ipep.Address, ipep.Port);
|
||||
_nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var ns = new NetState(socket);
|
||||
m_ConnectedQueue.Enqueue(ns);
|
||||
ns.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue