TShock/TShockAPI/PacketBufferer.cs
Zack Piispanen 078dca1f40 Add in code to auto kill clients that have large buffers. This is disabled, and set to 5mb.
If enabled, this would silently kill players as their buffer is full, which could have bad consequences in large servers with lag.  This is intended to deal with the issue where servers are running out of memory.
2013-10-19 02:34:15 -04:00

273 lines
No EOL
6.5 KiB
C#

/*
TShock, a server mod for Terraria
Copyright (C) 2011-2013 Nyx Studios (fka. The TShock Team)
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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.Generic;
using System.ComponentModel;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Terraria;
using TerrariaApi.Server;
namespace TShockAPI
{
public class PacketBufferer : IDisposable
{
/// <summary>
/// Maximum number of bytes to send per update per socket
/// </summary>
public int BytesPerUpdate { get; set; }
private readonly TShock plugin;
private PacketBuffer[] buffers = new PacketBuffer[Netplay.serverSock.Length];
private int[] Bytes = new int[52];
private int[] Packets = new int[52];
private int[] Compressed = new int[52];
#if DEBUG_NET
Command dump;
Command flush;
#endif
public PacketBufferer(TShock p)
{
plugin = p;
BytesPerUpdate = 0xFFFF;
for (int i = 0; i < buffers.Length; i++)
buffers[i] = new PacketBuffer();
#if DEBUG_NET
dump = new Command("superadmin", Dump, "netdump");
flush = new Command("superadmin", Flush, "netflush");
Commands.ChatCommands.Add(dump);
Commands.ChatCommands.Add(flush);
#endif
ServerApi.Hooks.NetSendBytes.Register(plugin, ServerHooks_SendBytes);
ServerApi.Hooks.ServerSocketReset.Register(plugin, ServerHooks_SocketReset);
ServerApi.Hooks.GamePostUpdate.Register(plugin, GameHooks_Update);
}
~PacketBufferer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
#if DEBUG_NET
Commands.ChatCommands.Remove(dump);
Commands.ChatCommands.Remove(flush);
#endif
ServerApi.Hooks.NetSendBytes.Deregister(plugin, ServerHooks_SendBytes);
ServerApi.Hooks.ServerSocketReset.Deregister(plugin, ServerHooks_SocketReset);
ServerApi.Hooks.GamePostUpdate.Deregister(plugin, GameHooks_Update);
}
}
private void Dump(CommandArgs args)
{
var sb = new StringBuilder();
sb.AppendLine("{0,-25}{1,-25}{2,-25}{3}".SFormat("Name:", "Packets", "Bytes", "Compression"));
for (int i = 1; i < Bytes.Length; i++)
{
sb.AppendLine("{0,-25}{1,-25}{2,-25}{3}".SFormat(Enum.GetName(typeof (PacketTypes), i) + ":", Packets[i], Bytes[i],
Compressed[i]));
}
File.WriteAllText(Path.Combine(TShock.SavePath, "dmp.txt"), sb.ToString());
}
private void Flush(CommandArgs args)
{
Bytes = new int[52];
Packets = new int[52];
Compressed = new int[52];
}
private void GameHooks_Update(EventArgs args)
{
FlushAll();
}
public void FlushAll()
{
for (int i = 0; i < Netplay.serverSock.Length; i++)
{
Flush(Netplay.serverSock[i]);
}
}
public bool Flush(ServerSock socket)
{
try
{
if (socket == null || !socket.active)
return false;
if (buffers[socket.whoAmI].Count < 1)
return false;
byte[] buff = buffers[socket.whoAmI].GetBytes(BytesPerUpdate);
if (buff == null)
return false;
if (SendBytes(socket, buff))
{
buffers[socket.whoAmI].Pop(buff.Length);
return true;
}
}
catch (Exception e)
{
Log.ConsoleError(e.ToString());
}
return false;
}
private void ServerHooks_SocketReset(SocketResetEventArgs args)
{
buffers[args.Socket.whoAmI] = new PacketBuffer();
}
public bool SendBytes(ServerSock socket, byte[] buffer)
{
return SendBytes(socket, buffer, 0, buffer.Length);
}
public void BufferBytes(ServerSock socket, byte[] buffer)
{
BufferBytes(socket, buffer, 0, buffer.Length);
}
public void BufferBytes(ServerSock socket, byte[] buffer, int offset, int count)
{
lock (buffers[socket.whoAmI])
{
#if DEBUG_NET
int size = (count - offset);
var pt = buffer[offset + 4];
Packets[pt]++;
Bytes[pt] += size;
Compressed[pt] += Compress(buffer, offset, count);
#endif
using (var ms = new MemoryStream(buffer, offset, count))
{
buffers[socket.whoAmI].AddRange(ms.ToArray());
}
if (TShock.Config.EnableMaxBytesInBuffer && buffers[socket.whoAmI].Count > TShock.Config.MaxBytesInBuffer)
{
buffers[socket.whoAmI].Clear();
socket.kill = true;
}
}
}
public bool SendBytes(ServerSock socket, byte[] buffer, int offset, int count)
{
try
{
if (socket.tcpClient.Client != null && socket.tcpClient.Client.Poll(0, SelectMode.SelectWrite))
{
if (Main.runningMono)
socket.networkStream.Write(buffer, offset, count);
else
socket.tcpClient.Client.Send(buffer, offset, count, SocketFlags.None);
return true;
}
}
catch (ObjectDisposedException e)
{
Log.Warn(e.ToString());
}
catch (SocketException e)
{
switch ((uint)e.ErrorCode)
{
case 0x80004005:
break;
default:
Log.Warn(e.ToString());
break;
}
}
catch (IOException e)
{
Log.Warn(e.ToString());
}
return false;
}
private void ServerHooks_SendBytes(SendBytesEventArgs args)
{
args.Handled = true;
BufferBytes(args.Socket, args.Buffer, args.Offset, args.Count);
}
#if DEBUG_NET
static int Compress(byte[] buffer, int offset, int count)
{
using (var ms = new MemoryStream())
{
using (var gzip = new GZipStream(ms, CompressionMode.Compress, true))
{
gzip.Write(buffer, offset, count);
}
return (int)ms.Length;
}
}
#endif
}
public class PacketBuffer : List<byte>
{
public byte[] GetBytes(int max)
{
lock (this)
{
if (this.Count < 1)
return null;
var ret = new byte[Math.Min(max, this.Count)];
this.CopyTo(0, ret, 0, ret.Length);
return ret;
}
}
public void Pop(int count)
{
lock (this)
{
this.RemoveRange(0, count);
}
}
}
}