私は小さな 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;
}
}