上篇.net平臺下C#socket通訊(上)介紹了socket通訊的基本原理及最基本的通訊方式。本文在此基礎上就socket通訊時常常遇到的問題作一個簡單總結,都是項目中的一些小問題,拿來此處便於下次使用,同時對在使用socket時出現些許問題的同仁們多一個粗淺建議。不足之處請提出,謝謝。html
本文主要講述:數組
一、正常通訊中握手創建服務器
二、一對多的通訊session
三、發送接收數據格式轉換socket
四、資源釋放tcp
五、開啓並保持服務監聽ide
一、握手創建正常的通訊通道函數
項目須要通訊的雙方(假設是一個上位機、一個下位機)之間須要創建一個穩定的通道,以便進行通訊。本項目中具體操做是:上位機做爲服務器,下位機做爲客戶端,同時制定通訊協議。上位機首先打開監聽等待創建通道,下位機主動鏈接上位機後發送鏈接成功的信息到上位機,上位機根據通訊協議發送數據到下位機,此時通道已經創建。但爲了保險起見(同時遵循三次握手),客戶端再次發送數據到上位機告知通道創建完畢。工具
二、一對多通訊this
項目需求是一個上位機多個下位機,這就肯定了上位機作爲服務器端,下位機做爲客戶端主動鏈接服務器。一對一通訊時只有一個socket通道,所以不管是上位機仍是下位機在發送和接收數據的時候都不會存在數據亂髮亂收的狀況。一對多意味着上位機和下位機會創建起多個通道,所以在發送數據時須要記錄哪個下位機處於哪一個socket通道中,以便進行邏輯處理。本文處理一對多通訊的過程是:
1)首先創建一個對話類Session:
public class Session { public Socket ClientSocket { get; set; }//客戶端的socket public string IP;//客戶端的ip public Session(Socket clientSocket) { this.ClientSocket = clientSocket; this.IP = GetIPString(); } public string GetIPString() { string result = ((IPEndPoint)ClientSocket.RemoteEndPoint).Address.ToString(); return result; } }
2)在服務端socket監聽時:
IPEndPoint loaclEndPoint = new IPEndPoint(IPAddress.Any, Port); SocketLister = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); SocketLister.Bind(loaclEndPoint); try { SocketLister.Listen(MaxConnection); while (IsRunning) { ClientSocket = SocketLister.Accept(); //保存socket Session newSession = new Session(ClientSocket); lock (sessionLock) { sessionTable.Add(newSession.IP, newSession); } SocketConnection socketConnection = new SocketConnection(ClientSocket); socketConnection.ReceiveDatagram();//接收數據 } } catch (SocketException ex) { }
//聲明
public Hashtable sessionTable = new Hashtable ();//包括客戶端會話
private object sessionLock = new object();
爲了便於理解,把整個服務端socket的創建都寫在上面。
3)發送數據到不一樣的客戶端
Hashtable ht = serverSocket.sessionTable; foreach (Session session in ht.Values) { if (session.IP == "127.0.0.1")//example { SocketConnection socketConnection = new SocketConnection(session.ClientSocket); string str = "C300010002D2"; byte[] sendBytes = StrToHexByte(str); socketConnection.Send(sendBytes); } }
SocketConnection類已經被使用屢次,寫在下面:
public class SocketConnection:IDisposable { public ServerSocket Server { get; set; } public Byte[] MsgBuffer = null; private int totalLength = 0; public int CurrentBufferLength; private Socket _ClientSocket = null; public Socket ClientSock { get{ return this._ClientSocket; } } public SocketConnectionType Type { get; private set; } #region Constructor public SocketConnection(ServerSocket server, Socket sock) { this.Server = server; this._ClientSocket = sock; this.Type = SocketConnectionType.Server; } public SocketConnection(Socket sock) { this._ClientSocket = sock; this.Type = SocketConnectionType.Client; } #endregion #region Events public SocketConnectionDelegate OnConnect = null;//是否鏈接 public SocketConnectionDelegate OnLostConnect = null;//中斷鏈接 public ReceiveDataDelegate OnReceiveData = null;//接收數據 #endregion #region Connect public void Connect(IPAddress ip, int port) { this.ClientSock.BeginConnect(ip, port, ConnectCallback, this.ClientSock); } private void ConnectCallback(IAsyncResult ar) { try { Socket handler = (Socket)ar.AsyncState; handler.EndConnect(ar); if (OnConnect != null) { OnConnect(this); } ReceiveDatagram(); } catch (SocketException ex) { } } #endregion #region Send public void Send(string data) { Send(System.Text.Encoding.UTF8.GetBytes(data)); } public void Send(byte[] byteData) { try { int length = byteData.Length; byte[] head = BitConverter.GetBytes(length); byte[] data = new byte[head.Length + byteData.Length]; Array.Copy(head, data, head.Length); Array.Copy(byteData, 0, data, head.Length, byteData.Length); this.ClientSock.BeginSend(data, 0, data.Length, 0, new AsyncCallback(SendCallback), this.ClientSock); } catch (SocketException ex) { } } private void SendCallback(IAsyncResult ar) { try { Socket handler = (Socket)ar.AsyncState; handler.EndSend(ar); } catch (SocketException ex) { } } #endregion #region ReceiveDatagram public void ReceiveDatagram() { SocketStateObject state = new SocketStateObject(); state.workSocket = _ClientSocket; _ClientSocket.BeginReceive(state.buffer, 0, SocketStateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } private void ReceiveCallback(IAsyncResult ar) { SocketStateObject state = (SocketStateObject)ar.AsyncState; Socket handler = state.workSocket; if (handler.Connected) { try { state.bytesRead = handler.EndReceive(ar); if (state.bytesRead > 0) { OnDataRecivedCallback(state.buffer, state.bytesRead); Array.Clear(state.buffer, 0, state.buffer.Length); state.bytesRead = 0; handler.BeginReceive(state.buffer, 0, SocketStateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } else { //if (OnDisconnect != null) //{ // OnDisconnect(this); //} Dispose(); } } catch (SocketException ex) { } } } private void ReceiveCallBack00(IAsyncResult ar) { try { int REnd = _ClientSocket.EndReceive(ar); if (REnd > 0) { OnDataRecivedCallback(MsgBuffer, REnd ); Array.Clear(MsgBuffer, 0, MsgBuffer.Length); REnd = 0; _ClientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallBack00), null); } else { if (OnLostConnect != null) { OnLostConnect(this); } Dispose(); } } catch (Exception ex) { } } private void OnDataRecivedCallback(byte[] data, int length) { if (length > 0) { if (this.MsgBuffer == null) { byte[] bytelength = new byte[4]; Array.Copy(data, bytelength, 4); this.totalLength = BitConverter.ToInt32(bytelength, 0); this.MsgBuffer = new byte[this.totalLength]; this.CurrentBufferLength = 0; } if (this.totalLength > 0) { int offset = 0; if (CurrentBufferLength == 0) { offset = 4; } if (length + this.CurrentBufferLength >= this.totalLength + offset) { int len = this.totalLength - CurrentBufferLength; Array.Copy(data, offset, this.MsgBuffer, this.CurrentBufferLength, len); byte[] tmp = this.MsgBuffer.Clone() as byte[]; OnReceiveData(new MessageData(this, tmp)); this.MsgBuffer = null; if (length - len - offset > 0) { tmp = new byte[length - len - offset]; Array.Copy(data, offset + len, tmp, 0, tmp.Length); OnDataRecivedCallback(tmp, tmp.Length); } } else { Array.Copy(data, offset, this.MsgBuffer, this.CurrentBufferLength, length - offset); this.CurrentBufferLength += length - offset; } } else { } } } public void Dispose() { try { this.ClientSock.Shutdown(SocketShutdown.Both); this.ClientSock.Close(); this.Server = null; //if (OnLostConnect != null) //{ // OnLostConnect(this); //} } catch { } } #endregion }
三、處理須要發送和接收到的數據
項目須要是上位機獲取數據進行邏輯處理,而後經過tcp/ip協議發送給下位機,下位機在接收到數據的同時發送確認信息到上位機。項目過程當中,上位機在開發過程當中須要調試其發送數據、接收數據是否成功,此處藉助於USR- TCP232小工具。可是涉及到一個問題,下位機發送和接收都是byte字節數組,那麼開發的上位機應該如何發送和接收數據?在.net平臺下C#socket通訊(上),有服務器端的發送和接收函數,發送數據時將要發送的字符串轉換爲byte數組,接收時再將字節數組轉換爲16進制字符串。以下:
//字節數組轉換爲16進制字符串 public string ByteToHexStr(byte[] bytes) { string str = ""; if (bytes != null) { for (int i = 0; i < bytes.Length; i++) { str += bytes[i].ToString("X2"); } } return str; } //字符串轉換爲16進制byte數組 public byte[] StrToHexByte(string data) { data = data.Replace(" ", ""); if ((data.Length % 2) != 0) { data += " "; } byte[] bytes = new byte[data.Length / 2]; for (int i = 0; i < bytes.Length; i++) { bytes[i] = Convert .ToByte (data.Substring (i * 2,2),16); } return bytes; }
四、資源釋放
開發項目使用平臺是.net,工具vs2010,語言是C#,由於.net有垃圾回收機制,所以在實際開發中產生的託管資源都是系統自動釋放完成。在作本項目時採用winform進行開發的,在此過程當中發現一個問題:在關閉Form窗體是運行的系統並無徹底關閉。查找緣由,應該是有資源沒有被釋放。而socket套接字產生的資源剛好是非託管資源,此現象代表系統中有socket資源沒有被徹底釋放掉。所以寫了一個資源釋放函數:
public void Dispose() { try { this.ClientSocket.Shutdown(SocketShutdown.Both); this.ClientSocket.Dispose(); this.ClientSocket.Close(); this.ClientSocket = null; } catch { } }
上述函數的功能就是釋放socket所產生的資源,調用後發現仍是存在此問題,幾經調試發現雖然把產生socket通道的監聽客戶端資源釋放完畢,服務器端的serversocket並無被釋放,因而有了下一個函數:
public void CloseSocket() { if (serverSocket != null) { serverSocket.SocketLister.Dispose(); serverSocket.SocketLister = null; serverSocket.Dispose();//調用的上一個函數 serverSocket = null; } }
在上述函數完成後,套接字socket所產生的資源確實被釋放完畢,系統在form關閉後能真正關閉。到此資源好像已經被釋放掉,但緊接着新的問題產生了:
在何時什麼地方調用釋放資源的函數?
我的簡單見解:
1)系統停止時調用
2)socket通道中斷時調用
補充:
五、開啓並保持服務監聽
在socket通訊中,服務端的socket監聽實際上是須要一直打開而且保持的,只有這樣才能隨時監聽鏈接的客戶端。項目中示例:
private void button1_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(new ThreadStart(StartServer)); thread.Start(); } public void StartServer() { int port = Convert.ToInt32(GetText(this.tbPort)); string ipStr = GetText (this.tbServerIPStr); if (serverSocket == null) { serverSocket = new ServerSocket(port); serverSocket.Start(ipStr);// } else { MessageBox.Show("監聽已開啓"); } } public void Start(string ipStr) { IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, Port); //IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse(ipStr), Port); SocketLister = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); SocketLister.Bind(localEndPoint); try { SocketLister.Listen(MaxConnection); while (IsRunning) { ClientSocket = SocketLister.Accept(); //保存socket Session newSession = new Session(ClientSocket); lock (sessionLock) { sessionTable.Add(newSession.IP, newSession); } SocketConnection socketConnection = new SocketConnection(ClientSocket); socketConnection.ReceiveDatagram(); } } catch (SocketException ex) { } }
解釋:點擊按鈕開啓新的線程thread,執行方法StartServer,StartServer調用方法Start,Start方法中使用死循環開啓並保持監聽。