上篇.net平台下C#socket通信(上)介紹了socket通信的基本原理及最基本的通信方式。本文在此基礎上就socket通信時經常遇到的問題做一個簡單總結,都是項目中的一些小問題,拿來此處便於下次使用,同時對在使用socket時出現些許問題的同仁們多一個粗淺建議。不足之處請提出,謝謝。
本文主要講述:
1、正常通信中握手建立
2、一對多的通信
3、發送接收數據格式轉換
4、資源釋放
5、開啟並保持服務監聽
1、握手建立正常的通信通道
項目需要通信的雙方(假設是一個上位機、一個下位機)之間需要建立一個穩定的通道,以便進行通信。本項目中具體操作是:上位機作為服務器,下位機作為客戶端,同時制定通信協議。上位機首先打開監聽等待建立通道,下位機主動連接上位機后發送連接成功的信息到上位機,上位機根據通信協議發送數據到下位機,此時通道已經建立。但為了保險起見(同時遵循三次握手),客戶端再次發送數據到上位機告知通道建立完畢。
2、一對多通信
項目需求是一個上位機多個下位機,這就確定了上位機做為服務器端,下位機作為客戶端主動連接服務器。一對一通信時只有一個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
}
3、處理需要發送和接收到的數據
項目需要是上位機獲取數據進行邏輯處理,然后通過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;
}
4、資源釋放
開發項目使用平台是.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通道中斷時調用
補充:
5、開啟並保持服務監聽
在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方法中使用死循環開啟並保持監聽。

