untiy Socket通信一篇通
文章目錄
- 一 前言
- 二 Socket簡(jiǎn)介
- 什么是socket
- 什么是TCP/UDP
- 三 在untiy中使用Socket需要注意的問(wèn)題
- 兩種不同的socket類
- 線程阻塞問(wèn)題
- 接收到的消息不要在接收線程里直接處理
- 四 前置概念
- 命名空間
- IPAddress類
- IPEndPoint
- socket的同步和異步方法
- 五 TCP的使用
- 1 使用TCPClient
- 1.1同步方法
- 1.2異步方法
一 前言
網(wǎng)上有很多socket的使用教程,但是由于Untiy中使用socket有其特殊性,故整理本文,希望進(jìn)行盡可能從實(shí)用角度介紹socket在untiy中的使用,并但是限于作者經(jīng)驗(yàn)不足,文中有錯(cuò)誤的地方希望大佬可以積極斧正
有任何疑問(wèn)歡迎留言
二 Socket簡(jiǎn)介
什么是socket
Socket即TCP協(xié)議和UDP協(xié)議,可以通過(guò)網(wǎng)絡(luò)(IP地址和端口)實(shí)現(xiàn)不同設(shè)備之間的通信。TCP和UDP是socket的兩種不同實(shí)現(xiàn),我們可以根據(jù)需要選擇一個(gè)或組合使用
什么是TCP/UDP
TCP 在通信前需要建立連接,為此需要區(qū)分監(jiān)聽(tīng)者和連接者,并且可以確保消息按順序可靠的到達(dá),除非網(wǎng)絡(luò)不通或網(wǎng)絡(luò)太差
UDP不需要建立連接,只需要知道對(duì)方UDP的IP端口即可,理論上發(fā)送速度更快,但是如果發(fā)送中出現(xiàn)了消息丟失,那么消息就丟了(當(dāng)然我們可以通過(guò)回執(zhí)來(lái)手動(dòng)重發(fā))
TCP只能連TCP,UDP只能連UDP
三 在untiy中使用Socket需要注意的問(wèn)題
兩種不同的socket類
untiy中實(shí)際上有兩種不同的Socket對(duì)象可以使用,一種類名是XXXClient,分為TcpClient和UdpClient,一種類名是就是Socket,通過(guò)不同的參數(shù)來(lái)區(qū)別是Tcp還是UDP,這兩種對(duì)象功能是相同的,API也是類似的,前者實(shí)際上是對(duì)后者的封裝
這兩種類使用一種即可
線程阻塞問(wèn)題
socket在連接、發(fā)送、接收消息、監(jiān)聽(tīng)連接時(shí)都會(huì)阻塞線程。
對(duì)于發(fā)送,只要不是一個(gè)巨大的文件,正常的消息對(duì)程序的阻塞時(shí)間很短,我們通常直接同步發(fā)送即可。
對(duì)于連接,接收消息和TCP的監(jiān)聽(tīng)連接,由于沒(méi)有收到消息前,程序?qū)⒖ㄗ≈钡绞盏较?#xff0c;所以我們必須開(kāi)一個(gè)額外的線程用于處理接收,開(kāi)線程的方式有兩種,一是手動(dòng)創(chuàng)建一個(gè)新線程,而是使用socket的異步接收方法,自動(dòng)創(chuàng)建新線程。這兩種方法下面都有演示,推薦使用異步方法,因?yàn)榇a更簡(jiǎn)潔
接收到的消息不要在接收線程里直接處理
untiy是單線程的,且所有的對(duì)游戲?qū)ο蟮男薷闹荒茉谥骶€程里進(jìn)行,如果我們?cè)诮邮站€程里試圖修改,就會(huì)拋出異常。解決方法是可以將收到的消息放到一個(gè)列表里,然后由主線程里遍歷列表來(lái)處理消息
四 前置概念
命名空間
using System.Net; using System.Net.Sockets;IPAddress類
即IP,通常我們只使用他的靜態(tài)方法和靜態(tài)屬性,常用如下
IPAddress.Any; //指本機(jī)所有可用的IP,對(duì)于單網(wǎng)卡電腦,就是本機(jī)地址 IPAddress.Broadcast;//廣播地址,用于UDP廣播 IPAddress.Parse("255.255.255.255");//將一個(gè)字符串轉(zhuǎn)為IPAddress, 255.255.255.255是廣播地址,注意不要用127.0.0.1表示本機(jī)地址IPEndPoint
由一個(gè)IPAddress和一個(gè)Port組成,即一個(gè)IP地址,一個(gè)端口,這是確認(rèn)一個(gè)通信地址的最小單位。用于綁定本機(jī)地址和指定要連接的遠(yuǎn)端地址
//聲明一個(gè)新的IPEndPoint,參數(shù)1 IP地址 IPAddress.Any代表本機(jī)IP 參數(shù)2 端口 0代表自動(dòng)選擇一個(gè)可用的端口 //這種聲明方式通常用于綁定本機(jī)地址 IPEndPoint localIPEndPoint = new IPEndPoint(IPAddress.Any,0);socket的同步和異步方法
對(duì)于connect,accept,send,receive這些方法,都有同步和異步兩個(gè)版本,其功能是相同的,同步版本如果在主線程里調(diào)會(huì)卡住線程,而異步版本會(huì)自動(dòng)創(chuàng)建一個(gè)線程。此處以tcp的accept方法為例來(lái)解釋,其余的方法使用方式完全相同
同步版本:
TcpClient client = tcpListener.AcceptTcpClient();//沒(méi)有收到連接請(qǐng)求前,會(huì)一直卡住線程等待異步版本:就是將同步版本拆成了Begin和End兩個(gè)方法,再加一個(gè)回調(diào)函數(shù)
//參數(shù)1 回調(diào)函數(shù) 參數(shù)2 一個(gè)object,也就是可以是任何對(duì)象,通常我們傳入 socket對(duì)象本身,這里我們傳入監(jiān)聽(tīng)對(duì)象,因?yàn)榛卣{(diào)里要用 tcpListener.BeginAcceptTcpClient(AcceptCallBack, tcpListener);//開(kāi)始異步監(jiān)聽(tīng) //接收到連接請(qǐng)求的回調(diào)函數(shù)private void AcceptCallBack(IAsyncResult ar)//參數(shù)的命名空間是 System,這里面存了Begin方法里傳入的參數(shù)2 的object {try{TcpListener listener = ar.AsyncState as TcpListener;//還原回當(dāng)初傳入的參數(shù)tcpClient = listener.EndAcceptTcpClient(ar);//異步結(jié)束連接Debug.Log("已連接");//開(kāi)啟下一個(gè)監(jiān)聽(tīng)連接tcpListener.BeginAcceptTcpClient(AcceptCallBack, tcpListener);}catch(Exception e){Debug.LogError("連接錯(cuò)誤"+e.ToString());} }//需要說(shuō)明的是,Begine方法里的參數(shù)2 我們可以傳入任何自定的類,不一定只傳socket對(duì)象,有時(shí)我們需要在回調(diào)里使用更多信息,就可以自定義一個(gè)類型,將需要的數(shù)據(jù)都放進(jìn)去,然后傳入?yún)?shù)2,再在回調(diào)里還原為自定義的對(duì)象。但是必須要傳入結(jié)束異步線程用的對(duì)象看上去異步方法好像復(fù)雜了很多,但實(shí)際上為了解決同步方法帶來(lái)的阻塞問(wèn)題,我們要付出的代價(jià)遠(yuǎn)比使用異步方法大,因此建議只使用異步方法,可以在下文中“同步方法“ 和“異步方法”中自行體會(huì)
五 TCP的使用
1 使用TCPClient
分為手動(dòng)創(chuàng)建線程和使用異步方法,可以混著用,推薦使用異步方法
1.1同步方法
不論是服務(wù)器還是客戶端,需要先建立連接才能發(fā)消息
以下為建立連接的方法:
監(jiān)聽(tīng)端:監(jiān)聽(tīng)端使用的類是TcpListener,注意不是TcpClient
連接端:
private TcpClient tcpClient = new TcpClient();//連上后,直接用這個(gè)對(duì)象和連接端通信private void Start(){//參數(shù)為服務(wù)器的地址,和服務(wù)器的TcpListener綁定的端口,注意tcpClient本身不需要指定IP端口,會(huì)自動(dòng)分配tcpClient.Connect("192.168.1.36", 10000);//注意這也是一個(gè)阻塞方法,如果連不上服務(wù)器會(huì)導(dǎo)致程序卡住,自己開(kāi)個(gè)連接線程}以下為通信的方法:
發(fā)送消息:
接收消息:
private TcpClient tcpClient = new TcpClient();private tcpReceiveT;private void Start(){//連接到服務(wù)器tcpClient.Connect("192.168.1.36", 10086);//接收消息的方法必須在子線程中執(zhí)行tcpReceiveT= new Thread(RecciveMsg);tcpReceiveT.IsBackground = true;tcpReceiveT.Start();}/// <summary>/// 接收字節(jié)數(shù)組/// </summary>private void RecciveMsg(){//聲明接收緩沖區(qū)的長(zhǎng)度byte[] receiveBuff = new byte[1024];int reviceLength = 0;while (true){try{//返回值為接收到的字節(jié)的數(shù)量 這一步會(huì)阻塞線程,決不能在主線程中執(zhí)行reviceLength = tcpClient.Client.Receive(receiveBuff);//必須指定轉(zhuǎn)換的字節(jié)數(shù)組的長(zhǎng)度,因?yàn)榫彌_區(qū)剩余的空位會(huì)用0填充,這些0我們并不需要msg = Encoding.UTF8.GetString(receiveBuff,0,reviceLength);isReveive = true;print("收到的消息 "+msg);}}}1.2異步方法
顯然手動(dòng)創(chuàng)建線程的代碼又臭又長(zhǎng),好在微軟爸爸為我們提供了方便的異步方法
以下為連接的方法:
監(jiān)聽(tīng)端:
連接端:
TcpClient tcpClient;private void Start(){tcpClient = new TcpClient();//開(kāi)始異步連接 參數(shù)1 監(jiān)聽(tīng)端的ip 參數(shù)2 監(jiān)聽(tīng)端的 port 參數(shù)3 回調(diào) 參數(shù)4 回調(diào)函數(shù)里用的對(duì)象,這里用tcpClient本身tcpClient.BeginConnect(IPAddress.Parse("192.168.1.114"),1000, ConnectCallBack, tcpClient);}private void ConnectCallBack(IAsyncResult ar){try{TcpClient client = ar.AsyncState as TcpClient;client.EndConnect(ar);}catch (Exception e){Debug.LogError("連接錯(cuò)誤" + e.ToString());}}以下為通信的方法:
發(fā)送還用同步發(fā)送即可
異步接收消息
UDP
UDP相對(duì)簡(jiǎn)單,UDP并不需要連接,核心對(duì)象為UdpClinent和IPEndPoint
UdpClient即UDP的對(duì)象,用于收發(fā)消息
IPEndPoint可以理解為Udp通信的端點(diǎn),解決三個(gè)問(wèn)題 我是誰(shuí)?我要發(fā)到哪里去?我從哪里接收?
聲明UdpClient時(shí),需要指定UdpClient的IPEndPoint,即,我是誰(shuí)
通過(guò)UdpClient發(fā)送消息時(shí),需要指定目的地的IPEndPoint,即,我要發(fā)到哪里去
通過(guò)UdpClient接收消息時(shí),需要指定監(jiān)聽(tīng)的IPEndPoint,即,我從哪里收
IPEndPoint常用的構(gòu)造有以下兩種,都需要我們指定IP和端口,在構(gòu)造函數(shù)中使用IPAddress類指定IP
//IP為當(dāng)前可用IP,或任意IP 端口為0表示任何端口, 這種聲明通常在接收消息中使用 new IPEndPoint(IPAddress.Any, 0) //指定一個(gè)IP,255.255.255.255為群發(fā) 端口為40000 指定確定的IP端口通常用于聲明UdpClient和發(fā)送消息 new IPEndPoint(IPAddress.Parse("192.168.1.36"), 40000)發(fā)送
//公共的消息發(fā)送中心private UdpClient udpClient;private void Awake(){ //為UdpClient綁定本機(jī)的IP和端口 我是誰(shuí)udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 40000));}/// <summary>/// 消息發(fā)送 IPEndPoint聲明 IPEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.36"),10000)/// </summary>public void CommonendMsgSender(string msg, IPEndPoint commonIPEndPoint){byte[] commonBuff = Encoding.UTF8.GetBytes(msg);udpClient.Send(commonBuff, commonBuff.Length, commonIPEndPoint);}監(jiān)聽(tīng)
private UdpClient CommonUdpClient;// 公共的消息接收端口private IPEndPoint commonReceiveIPEndPoint;// 公共的消息線程Thread commonReceiveThread;// 公共消息的緩存private byte[] commonMsgBuff;private void Start(){//Udp對(duì)象本身需要綁定一個(gè)端口 Any代表當(dāng)前的IP地址,0代表任意一個(gè)可用的端口CommonUdpClient = new UdpClient(new IPEndPoint(IPAddress.Any,0));//公共消息接收端口 UDP對(duì)象將從這個(gè)端口接收消息commonReceiveIPEndPoint = new IPEndPoint(IPAddress.Any, 0);//啟動(dòng)監(jiān)聽(tīng)線程,和TCP一樣,監(jiān)聽(tīng)會(huì)阻塞線程,不能在主線程中監(jiān)聽(tīng)消息commonReceiveThread = new Thread(ListenCommonMsg);commonReceiveThread.IsBackground = true;commonReceiveThread.Start();}// 監(jiān)聽(tīng)公共消息private void ListenCommonMsg(){while (true){//這里會(huì)阻塞線程commonMsgBuff = CommonUdpClient.Receive(ref commonReceiveIPEndPoint);string temp = Encoding.UTF8.GetString(commonMsgBuff);print("公共消息為" + temp);}}注意事項(xiàng)
1 監(jiān)聽(tīng)操作必須在子線程中執(zhí)行,不要在主線程中執(zhí)行,否則主線程會(huì)被阻塞
2 重復(fù)監(jiān)聽(tīng)消息應(yīng)該使用死循環(huán)
3 子線程可以操作主線程的簡(jiǎn)單屬性,如果 int bool str,但不能直接操作Uinty對(duì)象如Text等,如果需要在接收消息后改變場(chǎng)景內(nèi)容 ,在一個(gè)主線程的腳本中的Update里,使用一個(gè)bool制作一把鎖,將子線程收到的消息,賦值給主線程腳本的string變量,并將鎖置為true,使用這個(gè)腳本來(lái)控制Uinty對(duì)象
例
總結(jié)
以上是生活随笔為你收集整理的untiy Socket通信一篇通的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 视频消重软件百度云 小视频修改md5
- 下一篇: 辰信领创荣获“2016中国IT风云榜”两