1. 程式人生 > >.net平臺下C#socket通信(中)

.net平臺下C#socket通信(中)

copy 成功 int 原因 獲取數據 ++ etl any repl

本文主要講述:

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類已經被使用多次,寫在下面:

技術分享圖片 View Code

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方法中使用死循環開啟並保持監聽。

.net平臺下C#socket通信(中)