C#异步服务器套接字-线程安全/性能(MMO游戏)
本文关键字:性能 MMO 游戏 安全 服务器 异步 套接字 线程 | 更新日期: 2023-09-27 18:20:30
我正在为我的小型2D MMO游戏编写这个游戏服务器。
以下是我的问题:
- 你认为代码的线程安全性如何?你能告诉我问题在哪里,如何解决/修补吗
这是我的代码:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
public class Constanti
{
public const int CLASS_DARKELF_MAGICIAN = 1;
public const int CLASS_HUMAN_MAGICIAN = 2;
public const int CLASS_WARRIOR = 3;
public const int CLASS_MODERN_GUNMAN = 4;
public const int SUIT_1 = 1;
public const int SUIT_2 = 2;
public const int SUIT_3 = 3;
public const int SUIT_4 = 4;
public const int SUIT_Admin = 5;
//MAX/MIN
public const int MAX_LEVEL = 100;
public const int MAX_SKILL_LEVEL = 1000;
//SERVER MAX/MIN
public const int MAX_CONNECTIONS = 300;
public const int MAX_CONNECTIONS_IP = 4;
}
// State object for reading client data asynchronously
public class Player
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
//Player-Info
public int PlayerStats_Health = 0;
public int PlayerStats_Energy = 0;
public int PlayerInfo_Class = 0;
public int PlayerInfo_Suit = 0;
public int PlayerInfo_Level = 0;
public int PlayerInfo_SkillLevel = 0;
public void SetDefaults()
{
PlayerStats_Health = 100;
PlayerStats_Energy = 200;
PlayerInfo_Class = Constanti.CLASS_DARKELF_MAGICIAN;
PlayerInfo_Suit = Constanti.SUIT_1;
PlayerInfo_Level = 1;
PlayerInfo_SkillLevel = 1;
}
public Player()
{
}
public String pIPAddress;
}
public class GameObjectLists
{
public static List<Player> PlayersList = new List<Player>();
}
public class AsynchronousSocketListener
{
// Thread signal.
public static ManualResetEvent allDone = new ManualResetEvent(false);
public static int PlayersOnline = 0;
public AsynchronousSocketListener()
{}
public static void InitializeMySQL()
{
//TODO MySQLI/MySQL Connection
}
public static void MysqlUpdateQuery()
{
//Mysql UPDATE, no return stmt
}
public static String MySQLSelect()
{
//TODO MySQL Select
String retdata="test";
return retdata;
}
public static void StartListening()
{
// Data buffer for incoming data.
byte[] bytes = new Byte[1024];
// Establish the local endpoint for the socket.
// The DNS name of the computer
/*
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];*/
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 86);
// Create a TCP/IP socket.
Socket listener = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
// Bind the socket to the local endpoint and listen for incoming connections.
try
{
listener.Bind(localEndPoint);
listener.Listen(50);
Console.WriteLine("Server Started, waiting for connections...");
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
// Start an asynchronous socket to listen for connections.
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
//Console.WriteLine("'nPress ENTER to continue...");
Console.Read();
}
public static void AcceptCallback(IAsyncResult ar)
{
// Get the socket that handles the client request.
Socket listener = (Socket)ar.AsyncState;
Socket clientsocket = listener.EndAccept(ar);
// Signal the main thread to continue.
allDone.Set();
clientsocket.Blocking = false; //set to non-blocking
// Create the state object.
Player PlayerInfo = new Player();
PlayerInfo.workSocket = clientsocket;
IPEndPoint thisIpEndPoint = PlayerInfo.workSocket.RemoteEndPoint as IPEndPoint; //Get Local Ip Address
PlayerInfo.pIPAddress = thisIpEndPoint.Address.ToString();
GameObjectLists.PlayersList.Add(PlayerInfo);
PlayersOnline++;
int numconnsofip = 0;
GameObjectLists.PlayersList.ForEach(delegate(Player PlayerInfoCheck)
{
//Console.WriteLine(name);
if (PlayerInfoCheck.pIPAddress == PlayerInfo.pIPAddress)
{
numconnsofip++;
}
});
if (PlayersOnline > Constanti.MAX_CONNECTIONS || numconnsofip > Constanti.MAX_CONNECTIONS_IP)
{
Disconnect(clientsocket, PlayerInfo);
}
else
{
Console.WriteLine("Player with IP:[{0}] has [{1}] Connections", thisIpEndPoint.Address.ToString(), numconnsofip);
PlayerInfo.SetDefaults();
//clientsocket.LingerState = new LingerOption(true, 2); // give it up to 2 seconds for send
Console.WriteLine("New Connection Total:[{0}]", PlayersOnline);
clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0, new AsyncCallback(ReadCallback),
PlayerInfo);
}
}
public static void ProtocolCore(Player PlayerInfo, String data)
{
Console.WriteLine("Procesing Packet:{0}",data);
//if data == bla bla then send something to everyone:
GameObjectLists.PlayersList.ForEach(delegate(Player ObjPlayerInfo)
{
Send(data,ObjPlayerInfo);
});
}
public static void ReadCallback(IAsyncResult ar)
{
// TEST #1 - IF WE HANG HERE, THERE WILL BE STILL OTHER CONNECTIONS COMING HERE, BUT NO MULTI THREADING??
// Retrieve the state object and the clientsocket socket
// from the asynchronous state object.
Player PlayerInfo = (Player)ar.AsyncState;
Socket clientsocket = PlayerInfo.workSocket;
try
{
String content = String.Empty; //content buffer
// Read data from the client socket.
// IF THIS FAILS, WE CATCH / ASSUMING THAT:
// THE CLIENT FORCE-CLOSED THE CONNECTION OR OTHER REASON.
int bytesRead = clientsocket.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
PlayerInfo.sb.Append(Encoding.ASCII.GetString(
PlayerInfo.buffer, 0, bytesRead));
// Check for end-of-file tag. If it is not there, read
// more data.
content = PlayerInfo.sb.ToString();
int eofindex = content.IndexOf("<EOF>");
if (eofindex > -1)
{
// All the data has been read from the
// client. Display it on the console.
content = content.Substring(0,eofindex); //remove THE <EOF>
Console.WriteLine("Read {0} bytes from socket. Data : {1}",content.Length, content);
//PROCESS THE PACKET/DATA (PROTOCOL CORE)
ProtocolCore(PlayerInfo, content);
//Echo the data back to the client.
Send(content, PlayerInfo);
// CLEAR THE BUFFERS
PlayerInfo.sb.Remove(0, PlayerInfo.sb.Length);
Array.Clear(PlayerInfo.buffer, 0, PlayerInfo.buffer.Length);
// GO TO LISTEN FOR NEW DATA
clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0,
new AsyncCallback(ReadCallback), PlayerInfo);
}
else
{
// Not all data received. Get more.
clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0,
new AsyncCallback(ReadCallback), PlayerInfo);
}
}
else
{
//ASSUMING WE RECEIVED 0 SIZED PACKET or CLIENT DISCONNECT / THEREFORE CLOSE THE CONNECTION
Disconnect(clientsocket, PlayerInfo);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Disconnect(clientsocket, PlayerInfo);
}
}
private static void Send(String data,Player PlayerInfo)
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
// Begin sending the data to the remote device.
PlayerInfo.workSocket.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), PlayerInfo);
}
private static void Disconnect(Socket clientsocket, Player PlayerInfo)
{
try
{
PlayersOnline--; //Is this Thread-Safe also?
GameObjectLists.PlayersList.Remove(PlayerInfo);
Console.WriteLine("Socket Disconnected, PlayerObjects:[{0}]", GameObjectLists.PlayersList.Count);
clientsocket.Shutdown(SocketShutdown.Both);
clientsocket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void SendCallback(IAsyncResult ar)
{
// Retrieve the socket from the state object.
Player PlayerInfo = (Player)ar.AsyncState;
Socket clientsocket = PlayerInfo.workSocket;
try
{
// Complete sending the data to the remote device.
int bytesSent = clientsocket.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Disconnect(clientsocket, PlayerInfo);
}
}
public static int Main(String[] args)
{
InitializeMySQL();
StartListening();
return 0;
}
}
我想提到的第一件事是,由于我相信这是你问题的根源,你的性能(延迟、并发连接容量等)将在很大程度上由你运行该软件的硬件和每个特定客户端的网络性能来定义。软件可以改进一些东西,但总的来说,如果你的代码写得很好,别人可以理解,并且不包含任何错误,它就会表现得很好。
您的代码会同时处理300个连接吗?是的,很有可能。不过,我确实看到了线程方面的一些潜在问题。一个是,当你接受新客户时,你会有很多争论。您还通过在接受之间等待每个客户端被完全接受而创建了一个漏洞,这可能会引发拒绝服务攻击。客户端可以通过要求每个数据包的数据重传来暂停连接,每个数据包最多重传三次,并且可以等待发送每个消息,直到您超时(10秒?)。数据处理也会有很多问题,除非您拥有的方法存根在实现后本身是线程安全的。
您使用的是较旧的异步套接字模型。这有点复杂。我想你会更好地理解事件驱动模型,因为在我看来它更自然。我知道,根据我的经验,任何一个表现都很好。然而,我还发现,新的事件驱动模型要快一点,因为您不会因为IAsyncResult
对象的过度分配而进行大量垃圾收集。较新的模型使用诸如Socket.AcceptAsync
和Socket.Completed
事件之类的方法。
由于您是C#的新手,我建议您应该将精力集中在编写一个具有异步元素的简单干净的客户端/服务器应用程序上。您可以对此进行负载测试,看看它是否满足硬件原始吞吐量方面的性能标准。这将减少分析中的因素。我建议从一些可以来回交流简单短信的东西开始。随着对.NET.中线程和网络通信的细微差别有了更深入的理解,您可以增加复杂性
我建议你查看的一个网站是http://www.albahari.com/threading/,这很好地解释了编写多线程代码的各种构建块。它针对的是有编程经验的人,正如你提到的那样。"高级线程"部分是我经常提到的。
AndrewTroelson的书"ProC#2010和.NET4平台"(ISBN1430225491)也将是一个良好的开端。它涵盖了很多语言,并展示了C#和其他语言之间的一些相似之处。它还涵盖了很多.NET,这是大多数人在深入研究有趣的东西之前真正需要了解的。