最近在對項目中Socket通訊中的服務端代碼進行優化,保證能接受盡可能多的客戶端的連接,並且不會丟掉連接,不會掉數據包。經過一段時間的反復測試和修改,終於達到了這一要求。服務端代碼采用了異步通訊的方式,並使用ManualResetEvent來對線程進行控制。在程序中,ManualResetEvent 的使用很關鍵。 ManualResetEvent 允許線程通過發信號互相通信。通常,此通信涉及一個線程在其他線程進行之前必須完成的任務。當一個線程開始一個活動(此活動必須完成後,其他線程才能開始)時,它調用 Reset 以將 ManualResetEvent 置於非終止狀態,此線程可被視為控制 ManualResetEvent。調用 ManualResetEvent 上的 WaitOne 的線程將阻止,並等待信號。當控制線程完成活動時,它調用 Set 以發出等待線程可以繼續進行的信號。並釋放所有等待線程。一旦它被終止,ManualResetEvent 將保持終止狀態(即對 WaitOne 的調用的線程將立即返回,並不阻塞),直到它被手動重置。可以通過將布爾值傳遞給構造函數來控制 ManualResetEvent 的初始狀態,如果初始狀態處於終止狀態,為 true;否則為 false。現在貼出主要的代碼,歡迎大家指正,代碼如下所示:
void ButtonStartListenClick(object sender, System.EventArgs e)
{
try
{
// Check the port value
if(textBoxPort.Text == "")
{
MessageBox.Show("Please enter a Port Number");
return;
}
if (txtConnectNum.Text.Trim() != "")
{
iConnectNum = int.Parse(txtConnectNum.Text.Trim());
Flage = 0;
}
else
{
MessageBox.Show("Please enter a Connect Number");
return;
}
string portStr = textBoxPort.Text;
int port = System.Convert.ToInt32(portStr);
// Create the listening socket...
m_mainSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint (IPAddress.Any, port);
// Bind to local IP Address...
m_mainSocket.Bind( ipLocal );
// Start listening...
m_mainSocket.Listen(10000);
// Set the event to nonsignaled state.設置無信號狀態的事件
allDone.Reset();
// Create the call back for any client connections...
m_mainSocket.BeginAccept(new AsyncCallback (OnClientConnect), null);
// Wait until a connection is made before continuing.等待連接創建後繼續
allDone.WaitOne();
UpdateControls(true);
}
catch(SocketException se)
{
MessageBox.Show ( se.Message );
}
}
// This is the call back function, which will be invoked when a client is connected
public void OnClientConnect(IAsyncResult asyn)
{
try
{
//讓它繼續處理並建立與客戶端的連接
allDone.Set();
// Here we complete/end the BeginAccept() asynchronous call
// by calling EndAccept() - which returns the reference to
// a new Socket object
Socket workerSocket = m_mainSocket.EndAccept (asyn);
// Now increment the client count for this client
// in a thread safe manner
Interlocked.Increment(ref m_clientCount);
if (m_clientCount == 1)
{
lock (this)
{
stopwatch.Start();
dtStart = DateTime.Now;
writeLog("Server Start Socket Connect Time"+dtStart.ToString("yyyy-MM-dd HH:mm:ss fff"));
}
}
// Add the workerSocket reference to our ArrayList
m_workerSocketList.Add(workerSocket);
// Send a welcome message to client
string msg = "Welcome client " + m_clientCount + "\n";
SendMsgToClient(msg, m_clientCount);
// Update the list box showing the list of clients (thread safe call)
UpdateClientListControl();
// Let the worker Socket do the further processing for the
// just connected client
WaitForData(workerSocket, m_clientCount);
// Since the main Socket is now free, it can go back and wait for
// other clients who are attempting to connect
m_mainSocket.BeginAccept(new AsyncCallback ( OnClientConnect ),null);
// Wait until a connection is made before continuing.等待連接創建後繼續
allDone.WaitOne();
}
catch(ObjectDisposedException)
{
System.Diagnostics.Debugger.Log(0,"1","\n OnClientConnection: Socket has been closed\n");
}
catch(SocketException se)
{
MessageBox.Show ( se.Message );
}
}
public class SocketPacket
{
// Constructor which takes a Socket and a client number
public SocketPacket(System.Net.Sockets.Socket socket, int clientNumber)
{
m_currentSocket = socket;
m_clientNumber = clientNumber;
}
public System.Net.Sockets.Socket m_currentSocket;
public int m_clientNumber;
// Buffer to store the data sent by the client
public byte[] dataBuffer = new byte[1024];
}
// Start waiting for data from the client
public void WaitForData(System.Net.Sockets.Socket soc, int clientNumber)
{
try
{
if ( pfnWorkerCallBack == null )
{
// Specify the call back function which is to be
// invoked when there is any write activity by the
// connected client
pfnWorkerCallBack = new AsyncCallback (OnDataReceived);
}
SocketPacket theSocPkt = new SocketPacket (soc, clientNumber);
//receiveDone.Reset();
soc.BeginReceive (theSocPkt.dataBuffer, 0,
theSocPkt.dataBuffer.Length,
SocketFlags.None,
pfnWorkerCallBack,
theSocPkt);
//receiveDone.WaitOne();
}
catch(SocketException se)
{
MessageBox.Show (se.Message );
}
}
// This the call back function which will be invoked when the socket
// detects any client writing of data on the stream
public void OnDataReceived(IAsyncResult asyn)
{
SocketPacket socketData = (SocketPacket)asyn.AsyncState ;
try
{
// Complete the BeginReceive() asynchronous call by EndReceive() method
// which will return the number of characters written to the stream
// by the client
//receiveDone.Set();
int iRx = socketData.m_currentSocket.EndReceive (asyn);
char[] chars = new char[iRx + 1];
// Extract the characters as a buffer
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(socketData.dataBuffer,
0, iRx, chars, 0);
System.String szData = new System.String(chars);
string msg = "" + socketData.m_clientNumber + ":";
AppendToRichEditControl(msg + szData);
//writeFromClientsMsgLog(msg + szData);
// Send back the reply to the client
string replyMsg = "Server Reply:" + szData.ToUpper();
// Convert the reply to byte array
byte[] byData = System.Text.Encoding.ASCII.GetBytes(replyMsg);
Socket workerSocket = (Socket)socketData.m_currentSocket;
workerSocket.Send(byData);
if (m_clientCount == iConnectNum && Flage == 0)
{
Interlocked.Increment(ref Flage);
string msgTime = "Server End Socket Action Time:";
lock(this)
{
stopwatch.Stop();
//lblTime.Text = stopwatch.Elapsed.Seconds.ToString();
int itime = stopwatch.Elapsed.Milliseconds;
//msgTime += stopwatch.Elapsed.Seconds.ToString()+"--"+itime.ToString();
msgTime += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff");
}
writeLog(msgTime);
writeClientConnectionLog();
//UpdateLabelTime(msgTime);
//byData = System.Text.Encoding.ASCII.GetBytes(msgTime);
//workerSocket.Send(byData);
}
// Continue the waiting for data on the Socket
WaitForData(socketData.m_currentSocket, socketData.m_clientNumber );
}
catch (ObjectDisposedException )
{
System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived: Socket has been closed\n");
}
catch(SocketException se)
{
if(se.ErrorCode == 10054) // Error code for Connection reset by peer
{
string msg = "Client " + socketData.m_clientNumber + " Disconnected" + "\n";
AppendToRichEditControl(msg);
//writeFromClientsMsgLog(msg);
// Remove the reference to the worker socket of the closed client
// so that this object will get garbage collected
m_workerSocketList[socketData.m_clientNumber - 1] = null;
UpdateClientListControl();
}
else
{
MessageBox.Show (se.Message );
}
}
}
void CloseSockets()
{
if(m_mainSocket != null)
{
m_mainSocket.Close();
}
Socket workerSocket = null;
for(int i = 0; i < m_workerSocketList.Count; i++)
{
workerSocket = (Socket)m_workerSocketList[i];
if(workerSocket != null)
{
workerSocket.Close();
workerSocket = null;
}
}
}
void SendMsgToClient(string msg, int clientNumber)
{
// Convert the reply to byte array
byte[] byData = System.Text.Encoding.ASCII.GetBytes(msg);
Socket workerSocket = (Socket)m_workerSocketList[clientNumber - 1];
//workerSocket.Send(byData);
workerSocket.BeginSend(byData, 0, byData.Length, 0,
new AsyncCallback(SendCallback), workerSocket);
}
private void SendCallback(IAsyncResult asyn)
{
Socket client = (Socket)asyn.AsyncState;
// 完成數據發送.
int bytesSent = client.EndSend(asyn);
}
完整的代碼可以在我的資源中下載到,資源的名稱為C#中Socket服務端通訊的代碼。
摘自 weizhiai12的專欄