using Island.StandardLib.Exceptions;
using Island.StandardLib.Storage;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Island.StandardLib.Utils;
namespace Island.StandardLib
{
///
/// 可继承此类,封装服务器操作
///
/// 为每个连接者提供一个唯一的TPlayer,此类需继承自并提供无参数构造函数
public abstract class ConnectionServer
where TPlayer : ConnectionPlayerBase, new()
where LoginOrRegisterRequestType : ILoginOrRegisterRequest, new()
{
public int ServerPort { get; private set; }
public string ServerAddress { get; private set; }
public int MaxBitSize { get; private set; }
public uint ServerVersion { get; private set; }
public ConcurrentDictionary OnlinePlayers;
public LockerList PlayersLocker;
public TPlayer FindOnlinePlayer(string nick)
{
if (OnlinePlayers.TryGetValue(nick, out TPlayer player))
return player;
else return null;
}
public ConnectionServer(string addr, int port, uint version = 1, int maxbitsz = 524288)
{
Logger.InitLoggerOnce();
PlayersLocker = new LockerList();
ConsoleColor bkup = Console.ForegroundColor;
Console.Write("Island.StandardLib.");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("ServerConnection");
Console.ForegroundColor = bkup;
Console.WriteLine("()");
Console.WriteLine(" __ ______ _ _ ____ \n \\ \\/ / ___| | \\ | | __ ) \n \\ / | | \\| | _ \\ \n / \\ |___ | |\\ | |_) |\n /_/\\_\\____| |_| \\_|____/\n");
Console.Write("-------------------- ");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("StandardLib ");
Console.ForegroundColor = bkup;
Console.WriteLine("Version 21Y0125 --------------------\n");
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("github.com/XCBOSA/Island.StandardLib");
Console.ForegroundColor = bkup;
OnlinePlayers = new ConcurrentDictionary();
ServerAddress = addr;
MaxBitSize = maxbitsz;
ServerPort = port;
ServerVersion = version;
IPAddress serverip = IPAddress.Parse(addr);
Socket serverSocket = new Socket(serverip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(serverip, ServerPort));
serverSocket.Listen(1024);
Thread acceptThread = new Thread(AcceptLoop);
acceptThread.IsBackground = true;
acceptThread.Start(serverSocket);
Logger.WriteLine(LogLevel.Default, $"Server start at {addr}:{ServerPort}");
}
void AcceptLoop(object obj)
{
Socket serverSocket = (Socket)obj;
OnConnectionBegin();
Exception exitWith;
while (true)
{
try
{
Socket client = serverSocket.Accept();
//Logger.WriteLine(LogLevel.Info, "{0}:{1} Connected.", ((IPEndPoint)client.RemoteEndPoint).Address.ToString(),
// ((IPEndPoint)client.RemoteEndPoint).Port.ToString());
new Thread(sock => OnReceiveLoginOrRegisterRequest((Socket)sock)) { IsBackground = true }.Start(client);
}
catch (Exception e)
{
exitWith = e;
Logger.LogError(e);
break;
}
}
OnConnectionBreaked(exitWith);
}
protected abstract LoginResult PassLogin(LoginOrRegisterRequestType request, IPAddress visitIp);
protected abstract RegisterResult PassReg(LoginOrRegisterRequestType request, IPAddress visitIp);
protected virtual void OnConnectionBegin() { }
protected virtual void OnConnectionBreaked(Exception reason) { }
protected void KeepConnect(Socket client, string name)
{
if (OnlinePlayers.ContainsKey(name))
throw new PlayerSocketFatalException(name, PlayerSocketFatalExceptionType.InternalDuplicatePlayerObject);
PlayersLocker.Lock(name);
TPlayer isp = new TPlayer();
isp.Init(client, 0, name, MaxBitSize, InternalDestroy);
OnlinePlayers.TryAdd(isp.Nick, isp);
Logger.WriteLine(LogLevel.Info, "{0} Joined.", isp.Nick);
PlayersLocker.Unlock(name);
}
protected virtual void OnReceiveLoginOrRegisterRequest(Socket client)
{
try
{
LoginOrRegisterRequestType request = client.ReceiveOnce(2048);
bool cont = false;
byte[] callbackd = null;
if (request.IsLogin)
{
LoginCallback callback = new LoginCallback();
if (request.ClientVersion != ServerVersion)
callback.Code = request.ClientVersion < ServerVersion ? LoginResult.VersionLower : LoginResult.VersionHigher;
else callback.Code = PassLogin(request, ((IPEndPoint)client.RemoteEndPoint).Address);
if (callback.Code == LoginResult.Success)
cont = true;
callbackd = callback.GetBytes();
}
else
{
RegisterCallback callback = new RegisterCallback();
if (request.ClientVersion != ServerVersion)
callback.Username = (int)(request.ClientVersion < ServerVersion ? RegisterResult.VersionLower : RegisterResult.VersionHigher);
else callback.Username = (int)PassReg(request, ((IPEndPoint)client.RemoteEndPoint).Address);
callbackd = callback.GetBytes();
}
client.SendOnce(callbackd);
if (cont)
{
KeepConnect(client, request.UserName);
}
else
{
//Logger.WriteLine(LogLevel.Info, "{0}:{1} Disconnected.", ((IPEndPoint)client.RemoteEndPoint).Address.ToString(),
// ((IPEndPoint)client.RemoteEndPoint).Port.ToString());
client.Close();
client.Dispose();
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error, ((IPEndPoint)client.RemoteEndPoint).Address.ToString() + ":");
Logger.LogError(e);
client.Close();
client.Dispose();
}
}
void InternalDestroy(ConnectionPlayerBase player)
{
if (!OnlinePlayers.TryRemove(player.Nick, out TPlayer _))
throw new Exception($"InternalDestroy: OnlinePlayers.TryRemove return false, player = {player.Nick}");
}
}
public abstract class ConnectionPlayerBase
{
int max_bitsize;
public int Id { get; protected set; }
public string Nick { get; private set; }
public bool IsOnline { get; private set; }
public ConnectObjectFromServer CommandSendPool;
public Thread SocketLoopThread;
public ConnectionPlayerBase() { }
Action selfDestroy;
internal void Init(Socket vaildPlayerSocket, int uid, string nick, int mbsz, Action selfDest)
{
CommandSendPool = new ConnectObjectFromServer();
Id = uid;
Nick = nick;
SocketLoopThread = new Thread(SocketLoop);
SocketLoopThread.IsBackground = true;
SocketLoopThread.Start(vaildPlayerSocket);
max_bitsize = mbsz;
selfDestroy = selfDest;
}
Socket socket;
public void SocketLoop(object objVaildPlayerSocket)
{
IsOnline = true;
socket = (Socket)objVaildPlayerSocket;
bool isFirstSend = true;
OnConnectionBegin();
Exception breakWith;
socket.ReceiveTimeout = 5000;
socket.SendTimeout = 5000;
while (true)
{
try
{
ConnectObjectFromClient clientData = null;
if (isFirstSend)
{
clientData = new ConnectObjectFromClient();
isFirstSend = false;
}
else clientData = socket.ReceiveOnce(max_bitsize, Nick);
lock (CommandSendPool)
{
InnerCommandPass(clientData);
socket.SendOnce(CommandSendPool);
CommandSendPool.ClearCommands();
}
}
catch (PlayerSocketFatalException e) { breakWith = e; Logger.LogError(e); break; }
catch (SocketException e) { breakWith = e; Logger.Log(LogLevel.Info, "Player {0}: Stopped Connection", Nick); break; }
catch (Exception e) { breakWith = e; Logger.Log(LogLevel.Warning, "Player {0}: Exception: {1}", Nick, e); break; }
}
try
{
socket.Close();
socket.Dispose();
}
catch { }
OnConnectionBreaked(breakWith);
selfDestroy(this);
}
void InnerCommandPass(ConnectObjectFromClient clientData)
{
for (int i = 0; i < clientData.Commands.Length; i++)
{
ConnectCommand command = clientData.Commands[i];
PassCommand(command);
}
}
public void ForceEndConnect()
{
socket.Close();
socket.Dispose();
}
protected abstract void PassCommand(ConnectCommand command);
protected virtual void OnConnectionBegin() { }
protected virtual void OnConnectionBreaked(Exception reason) { }
}
}