我正在開發使用。NET的Socket異步模型異步遊戲服務器(BeginAccept/EndAccept ...等)。的.Net 3.5異步套接字服務器性能問題
我所面臨的問題是這樣描述的: 當我只有一個客戶端連接時,服務器響應時間非常快,但是一旦第二個客戶端連接,服務器響應時間就會增加太多。
我測量了從客戶端發送消息到服務器的時間,直到它在兩種情況下都得到回覆。我發現一個客戶的平均時間大約是17ms,而在2個客戶的情況下大約是280ms!我真正看到的是:當2個客戶端連接並且只有其中一個正在移動(即向服務器請求服務)時,它等同於只有一個客戶端連接(即快速響應)的情況。但是,當兩個客戶端同時移動時(即同時向服務器請求服務),他們的動作變得非常慢(就好像服務器按照順序而不是同時迴應它們中的每一個)。
基本上,我在做什麼是:
當客戶端請求許可運動從服務器和服務器授予他的請求,服務器然後廣播客戶端的新位置,所有的球員。因此,如果兩個客戶端在同一時間移動,服務器最終將同時向兩個客戶端廣播每個客戶端的新位置。
EX:
- 客戶端1詢問去位置(2,2)
- 客戶機2請求去位置(5,5)
- 服務器發送到客戶端1的每個客戶機2 &相同的兩個消息:
- MESSAGE1: 「客戶端1在(2,2)」
- 消息2: 「客戶機2在(5,5)」
我相信問題來自Socket類是線程安全的,根據MSDN文檔http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.aspx。 (不知道這是問題)
下面是該服務器的代碼:
///
/// This class is responsible for handling packet receiving and sending
///
public class NetworkManager
{
///
/// An integer to hold the server port number to be used for the connections. Its default value is 5000.
///
private readonly int port = 5000;
///
/// hashtable contain all the clients connected to the server.
/// key: player Id
/// value: socket
///
private readonly Hashtable connectedClients = new Hashtable();
///
/// An event to hold the thread to wait for a new client
///
private readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
///
/// keeps track of the number of the connected clients
///
private int clientCount;
///
/// The socket of the server at which the clients connect
///
private readonly Socket mainSocket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
///
/// The socket exception that informs that a client is disconnected
///
private const int ClientDisconnectedErrorCode = 10054;
///
/// The only instance of this class.
///
private static readonly NetworkManager networkManagerInstance = new NetworkManager();
///
/// A delegate for the new client connected event.
///
/// the sender object
/// the event args
public delegate void NewClientConnected(Object sender, SystemEventArgs e);
///
/// A delegate for the position update message reception.
///
/// the sender object
/// the event args
public delegate void PositionUpdateMessageRecieved(Object sender, PositionUpdateEventArgs e);
///
/// The event which fires when a client sends a position message
///
public PositionUpdateMessageRecieved PositionUpdateMessageEvent
{
get;
set;
}
///
/// keeps track of the number of the connected clients
///
public int ClientCount
{
get
{
return clientCount;
}
}
///
/// A getter for this class instance.
///
/// only instance.
public static NetworkManager NetworkManagerInstance
{
get
{
return networkManagerInstance;
}
}
private NetworkManager()
{}
/// Starts the game server and holds this thread alive
///
public void StartServer()
{
//Bind the mainSocket to the server IP address and port
mainSocket.Bind(new IPEndPoint(IPAddress.Any, port));
//The server starts to listen on the binded socket with max connection queue //1024
mainSocket.Listen(1024);
//Start accepting clients asynchronously
mainSocket.BeginAccept(OnClientConnected, null);
//Wait until there is a client wants to connect
resetEvent.WaitOne();
}
///
/// Receives connections of new clients and fire the NewClientConnected event
///
private void OnClientConnected(IAsyncResult asyncResult)
{
Interlocked.Increment(ref clientCount);
ClientInfo newClient = new ClientInfo
{
WorkerSocket = mainSocket.EndAccept(asyncResult),
PlayerId = clientCount
};
//Add the new client to the hashtable and increment the number of clients
connectedClients.Add(newClient.PlayerId, newClient);
//fire the new client event informing that a new client is connected to the server
if (NewClientEvent != null)
{
NewClientEvent(this, System.EventArgs.Empty);
}
newClient.WorkerSocket.BeginReceive(newClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), newClient);
//Start accepting clients asynchronously again
mainSocket.BeginAccept(OnClientConnected, null);
}
/// Waits for the upcoming messages from different clients and fires the proper event according to the packet type.
///
///
private void WaitForData(IAsyncResult asyncResult)
{
ClientInfo sendingClient = null;
try
{
//Take the client information from the asynchronous result resulting from the BeginReceive
sendingClient = asyncResult.AsyncState as ClientInfo;
// If client is disconnected, then throw a socket exception
// with the correct error code.
if (!IsConnected(sendingClient.WorkerSocket))
{
throw new SocketException(ClientDisconnectedErrorCode);
}
//End the pending receive request
sendingClient.WorkerSocket.EndReceive(asyncResult);
//Fire the appropriate event
FireMessageTypeEvent(sendingClient.ConvertBytesToPacket() as BasePacket);
// Begin receiving data from this client
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
catch (SocketException e)
{
if (e.ErrorCode == ClientDisconnectedErrorCode)
{
// Close the socket.
if (sendingClient.WorkerSocket != null)
{
sendingClient.WorkerSocket.Close();
sendingClient.WorkerSocket = null;
}
// Remove it from the hash table.
connectedClients.Remove(sendingClient.PlayerId);
if (ClientDisconnectedEvent != null)
{
ClientDisconnectedEvent(this, new ClientDisconnectedEventArgs(sendingClient.PlayerId));
}
}
}
catch (Exception e)
{
// Begin receiving data from this client
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
}
///
/// Broadcasts the input message to all the connected clients
///
///
public void BroadcastMessage(BasePacket message)
{
byte[] bytes = message.ConvertToBytes();
foreach (ClientInfo client in connectedClients.Values)
{
client.WorkerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendAsync, client);
}
}
///
/// Sends the input message to the client specified by his ID.
///
///
/// The message to be sent.
/// The id of the client to receive the message.
public void SendToClient(BasePacket message, int id)
{
byte[] bytes = message.ConvertToBytes();
(connectedClients[id] as ClientInfo).WorkerSocket.BeginSend(bytes, 0, bytes.Length,
SocketFlags.None, SendAsync, connectedClients[id]);
}
private void SendAsync(IAsyncResult asyncResult)
{
ClientInfo currentClient = (ClientInfo)asyncResult.AsyncState;
currentClient.WorkerSocket.EndSend(asyncResult);
}
/// Fires the event depending on the type of received packet
///
/// The received packet.
void FireMessageTypeEvent(BasePacket packet)
{
switch (packet.MessageType)
{
case MessageType.PositionUpdateMessage:
if (PositionUpdateMessageEvent != null)
{
PositionUpdateMessageEvent(this, new PositionUpdateEventArgs(packet as PositionUpdatePacket));
}
break;
}
}
}
發射在不同的類中處理的事件,這裏是事件處理代碼的PositionUpdateMessage(其他處理程序重要):
private readonly Hashtable onlinePlayers = new Hashtable();
///
/// Constructor that creates a new instance of the GameController class.
///
private GameController()
{
//Start the server
server = new Thread(networkManager.StartServer);
server.Start();
//Create an event handler for the NewClientEvent of networkManager
networkManager.PositionUpdateMessageEvent += OnPositionUpdateMessageReceived;
}
///
/// this event handler is called when a client asks for movement.
///
private void OnPositionUpdateMessageReceived(object sender, PositionUpdateEventArgs e)
{
Point currentLocation = ((PlayerData)onlinePlayers[e.PositionUpdatePacket.PlayerId]).Position;
Point locationRequested = e.PositionUpdatePacket.Position;
((PlayerData)onlinePlayers[e.PositionUpdatePacket.PlayerId]).Position = locationRequested;
// Broadcast the new position
networkManager.BroadcastMessage(new PositionUpdatePacket
{
Position = locationRequested,
PlayerId = e.PositionUpdatePacket.PlayerId
});
}
看起來你可能只能處理你在單線程上收到的數據包。 – sblom 2010-05-23 20:22:40
我還沒有看過代碼,但有幾個問題?客戶是否忽視他們自己的移動信息 - 他們應該忽略他們?你多久發一次廣播消息? – 2010-05-23 20:31:19
@ sblom-我不相信,事件是從回調函數「WaitForData」觸發的。我假設這個回調函數在數據包同時到達服務器時在另一個線程中執行 – iBrAaAa 2010-05-23 20:51:01