1. 程式人生 > >C# Socket Tcp 語音通訊程式設計

C# Socket Tcp 語音通訊程式設計

核心關鍵詞:壓縮、序列化、粘包半包、封包拆包
整個流程就是: 一般事先新建一個類庫,做好要傳送的資料類,編寫完成後打包成類庫dll 提供給服務端和客戶端程式封裝使用(為程式新增引用請自行百度)   客戶端u3d:  步驟: 1.獲取聲音資料(byte[]) 用自帶的Microphone元件獲取要傳送的聲音壓縮成byte[] 2.將資料序列化
    利用上面自己封裝好的dll庫中資料類作為物件序列化成buffer位元組陣列,如下:
注意上面的紅色框框處就是我們下一步的封包處理了 3.進行封包處理     首先了解一下為什麼要封包?     基於socket TCP資訊互動的兩個問題:接收方不能主動識別傳送方傳送的訊息型別  接收方也不能主動拆分發送方傳送的多條訊息
    基於以上兩個問題會出現所謂的粘包和半包問題(因為在TCP中只有流的概念,沒有包的概念)
Socket長連線:建立連線後可以連續傳送多個數據包並進行維持 Socket短連線:建立Tcp連線,資料傳送完成後立即斷開Tcp連線 粘包:可能由傳送方造成,也可能由接收方造成。TCP為提高傳輸效率,傳送方往往要收集到足夠多的資料才傳送一包資料,造成多個數據包的粘連。如果接收程序不及時接收資料,已收    到的資料就放在系統接收緩衝區,使用者程序讀取資料就可能督導多個數據包。【長連線會出現】 半包:併發量較大的情況下,接收方可能沒有接收到一個完整的包,只接受了部分,這種情況主要是由於TCP為提高傳輸效率,將一個包分配的足夠大,導致接收方沒有一次接受完
    解決以上的粘包和半包問題,我們可以約定好一個通訊協議,利用協議來進行分包和解包
   應用層資料包格式:
包頭
包體 
資料包長度Len: 資料包內容,長度為Len
包頭儲存的是資料包長度,這樣做的長度是我們接收方接收到資料的時候可以根據包頭的資料長度對資料進行迴圈接收,直到包體長度達到包頭所指定的資料包長度時及判定為一個包。收到包之後就是拆包和反序列化的過程了。 如下封裝好後發往服務端:
服務端: 先用socket建立簡單的監聽連線:大概就是繫結埠和IP,建立監聽Socket等待客戶端接入,一般都是新開一個後臺執行緒接收客戶端連線並建立新的socket,非同步接收訊息。 這裡程式碼略過。。。 服務端開始接收:
程式碼塊:
public void ReceiveMessage(Socket socket)
        {
            while(true){
                if (!socket.Connected)
                    break;
                MessageData md = null;
                //================================================ 接收包頭 ==============================================
int headLength = 4;
                //儲存包頭的所有位元組數(這裡的包頭內容只包含了總資料包的長度)byte[] recvBytesHead = new byte[headLength];
                while (headLength > 0)
                {
                    byte[] recvBytes1 = new byte[4];
                    //將本次傳輸所接收的位元組數置0int iBytesHead = 0;
                    //如果當前需要接收的位元組數大於快取區大小,則按快取區大小進行接收,相反則按剩餘需要接收的位元組數進行接收 
    if (headLength >= recvBytes1.Length)
                    {
                        iBytesHead = socket.Receive(recvBytes1, recvBytes1.Length, 0);
                    }
                    else {
                        iBytesHead = socket.Receive(recvBytes1, headLength, 0);
                    }
                    //將接收到的位元組數儲存recvBytes1.CopyTo(recvBytesHead, recvBytesHead.Length - headLength);
                    //減去已經接收到的位元組數headLength -= iBytesHead;
                }
                //========================================== 接收包體 ==================================================
//從recvBytesHead中獲取包體長度
byte[] bytes = new byte[4];
                Array.Copy(recvBytesHead, 0, bytes, 0, 4);
                int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(bytes, 0));
                //儲存資料內容的位元組陣列byte[] recvBytesBody = new byte[bodyLength];
                //如果當前需要接收的位元組數大於0,則迴圈接收while (bodyLength > 0)
                {
                    byte[] recvBytes2 = new byte[bodyLength < 1024 ? bodyLength : 1024];
                    //將本次傳輸接收到的位元組置0int iBytesBody = 0;
                    //如果當前需要接收的位元組數大於快取區大小,則按快取區大小進行接收,相反則按剩餘需要接收的位元組數進行接收  if (bodyLength >= recvBytes2.Length)
                    {
                        iBytesBody = socket.Receive(recvBytes2, recvBytes2.Length, 0);
                    }
                    else {
                        iBytesBody = socket.Receive(recvBytes2, bodyLength, 0);
                    }
                    //將接收到的位元組數儲存recvBytes2.CopyTo(recvBytesBody, recvBytesBody.Length - bodyLength);
                    //減去已經接收到的位元組數bodyLength -= iBytesBody;
                }
                //一個訊息包接收完畢,開始解析訊息包UnpackData(recvBytesHead, recvBytesBody, out md);
 
                //處理一些邏輯if (md != null)
                    HandleMessage(md, socket);
            }
        }


接收到一個完整包後開始拆包處理
接下來我們就可以根據拆包並反序列化得到的的資料類MessageData進行處理轉發了:
Demo測試: 兩個客戶端連入伺服器
客戶端傳送1條語音訊息100條普通訊息:


還有,部署到伺服器上的時候注意:IPAddress列舉型別為Any,否則客戶端訪問時會被積極拒絕。如果還不行嘗試關閉伺服器的防火牆。