Unity3D Socket通信的簡(jiǎn)單實(shí)現(xiàn)

Socket的定義

socket英文的含義為插座、孔,在我們的網(wǎng)絡(luò)應(yīng)用中通常稱為套接字,大致理解為在tcp/ip網(wǎng)絡(luò)抽象層中使用套接字ip+端口的網(wǎng)絡(luò)通信協(xié)議,可認(rèn)為是介于傳輸層與應(yīng)用層中抽象出的socket層,我們可以使用它的接口來解決復(fù)雜的網(wǎng)絡(luò)請(qǐng)求。

GitHub

Tcp/IP

GitHub
GitHub
定義:

ransmission Control Protocol/Internet Protocol的簡(jiǎn)寫,中譯名為傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,又名網(wǎng)絡(luò)通訊協(xié)議,是Internet最基本的協(xié)議、Internet國(guó)際互聯(lián)網(wǎng)絡(luò)的基礎(chǔ),由網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP協(xié)議組成。

組成:

由四部分組成,從低至高分別為鏈路層、網(wǎng)絡(luò)層、傳輸層和應(yīng)用層

  • 鏈路層:數(shù)據(jù)鏈路層是負(fù)責(zé)接收IP數(shù)據(jù)包并通過網(wǎng)絡(luò)發(fā)送,或者從網(wǎng)絡(luò)上接收物理幀,抽出IP數(shù)據(jù)包,交給IP層。主要表現(xiàn)為物理驅(qū)動(dòng),使用物理的方式進(jìn)行數(shù)據(jù)交互。

  • 網(wǎng)絡(luò)層:負(fù)責(zé)相鄰計(jì)算機(jī)之間的通信。確定網(wǎng)絡(luò)地址IP,對(duì)網(wǎng)絡(luò)鏈接狀況獲取相關(guān)數(shù)據(jù)。

  • 傳輸層:提供應(yīng)用程序間的通信。使用 tcp 或 udp 形式對(duì)主機(jī)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,客戶端與服務(wù)端使用流各自操作本地?cái)?shù)據(jù)文本,以代理的形式實(shí)現(xiàn)通信。

  • 應(yīng)用層:向用戶提供一組常用的應(yīng)用程序,主機(jī)上對(duì)網(wǎng)絡(luò)獲取的數(shù)據(jù)進(jìn)行交互和效果展示。

Socket通信過程

GitHub

名詞解析:

  • IP:網(wǎng)絡(luò)IP地址,網(wǎng)絡(luò)進(jìn)程唯一標(biāo)識(shí)符。

  • 端口:應(yīng)用進(jìn)程中用來數(shù)據(jù)交互的接口,每個(gè)應(yīng)用都有唯一的端口標(biāo)識(shí)符。

  • IPv4,是互聯(lián)網(wǎng)協(xié)議(Internet Protocol,IP)的第四版,也是第一個(gè)被廣泛使用,構(gòu)成現(xiàn)今互聯(lián)網(wǎng)技術(shù)的基石的協(xié)議。1981年Jon Postel 在RFC791中定義了IP,Ipv4可以運(yùn)行在各種各樣的底層網(wǎng)絡(luò)上,比如端對(duì)端的串行數(shù)據(jù)鏈路(PPP協(xié)議和SLIP協(xié)議) ,衛(wèi)星鏈路等等。局域網(wǎng)中最常用的是以太網(wǎng)。

IPv4中規(guī)定IP地址長(zhǎng)度為32,即有232-1(符號(hào)表示升冪,下同)個(gè)地址

  • IPv6:

IPv6是Internet Protocol Version 6的縮寫,其中Internet Protocol譯為“互聯(lián)網(wǎng)協(xié)議”。IPv6是IETF(互聯(lián)網(wǎng)工程任務(wù)組,Internet Engineering Task Force)設(shè)計(jì)的用于替代現(xiàn)行版本IP協(xié)議(IPv4)的下一代IP協(xié)議。

IPv6中IP地址的長(zhǎng)度為128,即有2^128-1個(gè)地址。

IPv6具有更高的安全性。在使用IPv6網(wǎng)絡(luò)中用戶可以對(duì)網(wǎng)絡(luò)層的數(shù)據(jù)進(jìn)行加密并對(duì)IP報(bào)文進(jìn)行校驗(yàn),極大的增強(qiáng)了網(wǎng)絡(luò)的安全性。

代碼實(shí)現(xiàn)

  • 初始化: 根據(jù)地址和端口創(chuàng)建socket資源

string[]handled=tcp.Split(newChar[]{':'});

stringhost=handled[0];

inthostPort=handled.Length>1?int.Parse(handled[1]):80;

//地址封裝

IPEndPointport=newIPEndPoint(Dns.GetHostAddresses(handled[0])[0],hostPort);

//創(chuàng)建socket資源,新實(shí)例初始化 Socket 類使用指定的地址族、 套接字類型和協(xié)議。

socket=newSocket(

    AddressFamily.InterNetwork,

    System.Net.Sockets.SocketType.Stream,

    ProtocolType.Tcp

);

  • 請(qǐng)求連接: 異步向服務(wù)器發(fā)送連接請(qǐng)求,連接成功向服務(wù)器發(fā)送心跳包(心跳:服務(wù)器與客戶端進(jìn)行通信,客戶端實(shí)現(xiàn)雙向?;?,socket進(jìn)行異步掛載,準(zhǔn)備接收數(shù)據(jù)

...

//連接服務(wù)器

IAsyncResultresult=socket.BeginConnect(

    port, ConnectCallBack, socket

);

//5s內(nèi)判斷是否連接成功

boolisSucced=result.AsyncWaitHandle.WaitOne(5000,true);

...

void ConnectCallBack(IAsyncResultresult){

    Socketsocket=(Socket)result.AsyncState;

    //異步掛載,等待數(shù)據(jù)接收
         socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);

}

  • 接收數(shù)據(jù):客服端開啟異步處理,開啟掛載對(duì)服務(wù)器的數(shù)據(jù)進(jìn)行監(jiān)聽,接收到數(shù)據(jù)進(jìn)行拆包處理,發(fā)送給各個(gè)應(yīng)用模塊。之后再將socket進(jìn)行異步掛載,準(zhǔn)備接收數(shù)據(jù)

void ConnectCallBack(IAsyncResultresult){

    Socketsocket=(Socket)result.AsyncState;

    socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);

}

private void RecivieCallBack(IAsyncResultresult){

    Socketsocket=(Socket)result.AsyncState;

    //接收長(zhǎng)度

    intlength=socket.EndReceive(result);

    if(length<=0){

        Console.Log(LogType.SOCKET,"server does not return data, but not disconnect");

    return;

    }

    //保持?jǐn)?shù)據(jù)同步,對(duì)二進(jìn)制字節(jié)拆包處理

    lock(receiveData){

    SplitePackage(receiveData,length);

    }

    try{

    if(respsonList.Count>0){

          respsonList.ForEach(packet=>{

              sub.OnNext(packet);

          });

          respsonList.Clear();

      }

        socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);

     }

    catch(Exceptione){

    //Debug.LogError (e.ToString());

}

  • 拆包:將從服務(wù)器接收的二進(jìn)制數(shù)據(jù)進(jìn)行拆包處理,根據(jù)包頭獲取數(shù)據(jù)長(zhǎng)度,對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行轉(zhuǎn)化(* 處理粘包->使用循環(huán),輪詢處理 斷包->將斷包處保存再memoryStream中,再次接收數(shù)據(jù)的時(shí)候?qū)匦缕唇?

private void SaveBreak(byte[]data,intindex){

    streamLen=data.Length-index;

    byte[]breakData=newbyte[streamLen];

    Array.Copy(data,index,breakData,0,breakData.Length);

    stream.SetLength(streamLen);

    stream.Write(breakData,0,breakData.Length);

    isBreakPacket=true;

}

private void EndBreak(){

    if(stream!=null){

    stream.Close();

    stream=newMemoryStream();

    streamLen=0;

    isBreakPacket=false;

    }

}

//拆包

public void SplitePackage(byte[]data,intbufferLen){

    //包頭位置

    intindex=0;

    //斷包判斷

    byte[]bytes=newbyte[bufferLen];

    Array.Copy(data,bytes,bufferLen);

    //處于斷包狀態(tài)

    if(isBreakPacket){

    byte[]packetData=stream.ToArray();

    byte[]newData=newbyte[streamLen+bufferLen];

    Array.Copy(packetData,0,newData,0,streamLen);

    index+=streamLen;

    Array.Copy(bytes,0,newData,index,bufferLen);

    bytes=newData;

    index=0;

    EndBreak();

    }

    //循環(huán)拆包(粘包處理)

    while(index

    //斷包頭

    if((index+4)>=bytes.Length){

    SaveBreak(bytes,index);

    return;

    }

    //拆包頭

    byte[]head=newbyte[4];

    Array.Copy(bytes,index,head,0,4);

    index+=4;

    Array.Reverse(head);

    intcount=BitConverter.ToInt32(head,0);

    //異常處理

    if(count>8192){

    EndBreak();

    HandleError("包頭過長(zhǎng)");

    return;

    }

    //斷內(nèi)容處理

    if((index+count)>bytes.Length){

    index-=4;

    SaveBreak(bytes,index);

    return;

    }

    //正常包處理

    byte[]callbackData=newbyte[count];
  
    Array.Copy(bytes,index,callbackData,0,count);

    stringiContent=Encoding.UTF8.GetString(callbackData);

    if(count==0){

    EndBreak();

    HandleError("接收數(shù)據(jù)為空");

    }

    respsonList.Add(iContent);

    index+=callbackData.Length;

    }

}

  • 發(fā)送請(qǐng)求:客戶端將發(fā)送的數(shù)據(jù)轉(zhuǎn)化成二進(jìn)制字節(jié),進(jìn)行裝包處理發(fā)送給服務(wù)器

public void SendMessage(stringstr)

{

    if(socket!=null&&!socket.Connected){

    EndBreak();

    HandleError("socket is not connecting || socket is not existed");

    return;

    }

    byte[]msg=Encoding.UTF8.GetBytes(str);

    intmesgLength=msg.Length;

    byte[]head=BitConverter.GetBytes(mesgLength);

    byte[]sendData=newbyte[mesgLength+head.Length];
  
    Array.Reverse(head);//服務(wù)器與客戶端的包頭的解析順序是相反的

    Array.Copy(head,sendData,head.Length);

    Array.Copy(msg,0,sendData,head.Length,msg.Length);

    try{

        IAsyncResultasyncSend=socket.BeginSend(sendData,0,sendData.Length,SocketFlags.None,newAsyncCallback(sendCallback),socket);

        boolsuccess=asyncSend.AsyncWaitHandle.WaitOne(7000,true);

        if(!success){

        throw new ApplicationException();

    }

    else{

    Console.Log(LogType.SOCKET_SEND,str);

    }

    catch{

    Console.Log(LogType.SOCKET,"send message exceptionally");

    }

}

//請(qǐng)求成功

private void sendCallback(IAsyncResultasyncSend)

{

...

}

  • 關(guān)閉:斷開連接,關(guān)閉socket資源

public void Closed(){

    try{

        if(socket!=null&&socket.Connected){

        //先停止接收和發(fā)送的服務(wù)

        socket.Shutdown(SocketShutdown.Both);

        //關(guān)閉socket資源

        socket.Close();

        if(disposable!=null){

        disposable.Dispose();

        }

        socket=null;

      }

    }

    //必須加上,客戶端使用輪詢處理斷線重連時(shí)候有可能多次調(diào)用,異步導(dǎo)致報(bào)錯(cuò)使程序直接卡死

    catch{

    }

}

常見問題

  • 數(shù)據(jù)同步: unity引擎中MonBehaviour只能處理主線程的數(shù)據(jù),而我們socket請(qǐng)求數(shù)據(jù)是異步請(qǐng)求的

  • 拆包:當(dāng)出現(xiàn)粘包、斷包

  • 重連:應(yīng)用保活與斷線重連

  • 異常處理:socket異步訪問異常導(dǎo)致程序卡死

優(yōu)化改進(jìn)

  • buffer:創(chuàng)建緩沖類,將每次的數(shù)據(jù)進(jìn)行管理

  • 創(chuàng)建包體實(shí)體類,對(duì)游戲數(shù)據(jù)進(jìn)行封裝

參考鏈接

  • 百度TCP/IP協(xié)議:

百度

  • 百度Socket:

百度

  • Socket_API:

微軟官網(wǎng)

  • Socket拆包處理:

Socket/TCP粘包、多包和少包, 斷包


第一次寫技術(shù)文章,可能有些地方表達(dá)并不具體,請(qǐng)小伙伴們諒解。本人unity菜鳥一枚,如果由不懂的地方可以留言,我們可以一起分享心得。如果效果不錯(cuò)我會(huì)在閑暇之余繼續(xù)修仙寫文章,如果覺得還不錯(cuò)就順手點(diǎn)個(gè)贊吧~


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容