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)求。

Tcp/IP


定義:
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通信過程

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