网络传输的七层结构:
其中TCP和UDP协议在传输层。
TCP/IP协议
TCP/IP中包含了四层架构中的多个协议,取其中两个进行了命名:
TCP
TCP的特点
粘包问题处理
TCP一次性接收过多数据必然出现粘包,即不同时发送的数据黏连在一起成为一个包,这是TCP的打包机制决定的。
处理粘包问题的方法是发送时在每一个数据前加上固定长度的包头,例如两个字节,表示数据的长度,在接收时按照长度把包一一解出来。解包的过程中有可能最后一个包的数据长度比包头显示的长度短或包头不完整,这是因为拆分成了两个包发送,只需将最后一个包的数据缓存,然后拼到下一个包的头部即可。
代码
客户端
using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading; public class MyTcp
{private static MyTcp singleInstance;private static readonly object padlock = new object();private byte[] result = new byte[1024];private Socket clientSocket;public bool isRun = false;private Action<bool> ac_connect;public static MyTcp Instance{get{lock (padlock) // 加锁保证单例唯一{if (singleInstance == null){singleInstance = new MyTcp();}return singleInstance;}}}public void ConnectServer(string _ip, Action<bool> _result){//设定服务器IP地址 ac_connect = _result;IPAddress ip;bool _isRight = IPAddress.TryParse(_ip, out ip);if (!_isRight){Debug.Log("无效地址......" + _ip);_result(false);return;}clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint _endpoint = new IPEndPoint(ip, 13001);Debug.Log("开始连接tcp~");clientSocket.BeginConnect(_endpoint, requestConnectCallBack, clientSocket);}private void requestConnectCallBack(IAsyncResult iar){try{//还原原始的TcpClient对象Socket client = (Socket)iar.AsyncState; client.EndConnect(iar);Debug.Log("连接服务器成功:" + client.RemoteEndPoint.ToString());isRun = true;ac_connect(true);}catch (Exception e){ac_connect(false);Debug.Log("tcp连接异常:" + e.Message);}finally{}}public void SendMessage(byte[] _mes){if (isRun){try{clientSocket.Send(_mes);}catch (Exception ex){EndClient();Debug.Log("发送数据异常:" + ex.Message);}}}public void EndClient(){isRun = false;if (clientSocket != null){try{clientSocket.Close();clientSocket = null;Debug.Log("关闭tcp连接");}catch (Exception ex){Debug.Log("关闭tcp连接异常111:" + ex.Message);}}}
}
主程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;public class Client : MonoBehaviour {// Use this for initializationvoid Start () {string _ip = "192.168.1.18";MyTcp.Instance.ConnectServer(_ip, (_result) => {if (_result){Debug.Log("连接成功"); string data = "aaaabbbbcccc";byte[] packheadByte = BitConverter.GetBytes((short)data.Length);byte[] message = System.Text.Encoding.UTF8.GetBytes(data);List<byte> sendMessage = new List<byte>();包头信息sendMessage.AddRange(packheadByte);sendMessage.AddRange(message);for (int i = 0; i < 100; i++){MyTcp.Instance.SendMessage(sendMessage.ToArray());}}else{Debug.Log("连接失败"); }}); }void OnApplicationQuit(){ MyTcp.Instance.EndClient(); }}
服务器
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Linq;public class ServerTcp {static Socket serverSocket; private bool isRun = false;private Dictionary<string,Socket> dic_clientSocket = new Dictionary<string, Socket>();private static readonly object stLockObj = new object ();private static ServerTcp instance;int headSize = 2;//包头长度 固定2byte[] saveBuffer = null;//不完整的数据包,即用户自定义缓冲区public static ServerTcp Instance{get{ lock (stLockObj) {if (instance == null){instance = new ServerTcp();} }return instance;}}private ServerTcp(){}public void Destory(){instance = null;}public void StartServer(){try {IPAddress ip = IPAddress.Parse("192.168.1.18");serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(ip, 13001)); //绑定IP地址:端口 serverSocket.Listen(1000); //设定最多10个排队连接请求 Debug.Log("启动监听" + serverSocket.LocalEndPoint.ToString() + "成功");isRun = true;//通过Clientsoket发送数据 Thread myThread = new Thread(ListenClientConnect); myThread.Start(); } catch (Exception ex) {Debug.Log ("服务器启动失败:" + ex.Message);} }private void ListenClientConnect() { while (isRun) { try {Socket clientSocket = serverSocket.Accept(); Thread receiveThread = new Thread(ReceiveMessage); receiveThread.Start(clientSocket); } catch (Exception ex) {Debug.Log ("监听失败:" + ex.Message);}} }// clientSocket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);public void EndServer(){if (!isRun) {return;}isRun = false;try {foreach (var item in dic_clientSocket) {item.Value.Close ();}dic_clientSocket.Clear ();if (serverSocket != null) {serverSocket.Close ();serverSocket = null; } } catch (Exception ex) {Debug.Log("tcp服务器关闭失败:" + ex.Message);}}public void CloseClientTcp(string _socketIp){try {if (dic_clientSocket.ContainsKey(_socketIp)) {if (dic_clientSocket [_socketIp] != null) {dic_clientSocket [_socketIp].Close();}dic_clientSocket.Remove (_socketIp);} } catch (Exception ex) {Debug.Log ("关闭客户端..." + ex.Message);}}public int GetClientCount(){return dic_clientSocket.Count;}public List<string> GetAllClientIp(){return new List<string> (dic_clientSocket.Keys);}private void ReceiveMessage(object clientSocket) { Socket myClientSocket = (Socket)clientSocket;// Debug.Log(myClientSocket.RemoteEndPoint.ToString());string _socketIp = myClientSocket.RemoteEndPoint.ToString().Split(':')[0]; Debug.Log ("有客户端连接:" + _socketIp);dic_clientSocket[_socketIp] = myClientSocket; bool _flag = true;byte[] resultData = new byte[1048];while (isRun && _flag) { try { Debug.Log("_socketName是否连接:" + myClientSocket.Connected);int _size = myClientSocket.Receive(resultData); if (_size <= 0) {throw new Exception("客户端关闭了222~");} OnReceive(0, resultData);}catch (Exception ex) { Debug.Log(_socketIp + "接收客户端数据异常: " + ex.Message); _flag = false;break; } } CloseClientTcp (_socketIp);} public void SendMessage(string _socketName,byte[] _mes){Debug.Log("SendMessage aaa ----- _socketName " + _socketName); if (isRun) {try {dic_clientSocket [_socketName].Send (_mes); } catch (Exception ex) {Debug.Log ("发数据给异常:" + ex.Message);} }}private bool OnReceive(int connId, byte[] bytes){// 系统缓冲区长度int bytesRead = bytes.Length;if (bytesRead > 0){if (saveBuffer == null)//第一次接收saveBuffer = bytes;//把系统缓冲区数据放在自定义缓冲区里面elsesaveBuffer = saveBuffer.Concat(bytes).ToArray();//拼接上次尾包int haveRead = 0; //已经完成读取的数据包长度int totalLen = saveBuffer.Length; //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的saveBuffer 是系统缓冲区+不完整的数据包)while (haveRead <= totalLen){//如果在N次拆解后剩余的数据包 小于 包头的长度 //则剩下的是非完整的数据包if (totalLen - haveRead < headSize){byte[] byteSub = new byte[totalLen - haveRead];//把剩下不够一个完整的数据包存起来Buffer.BlockCopy(saveBuffer, haveRead, byteSub, 0, totalLen - haveRead);saveBuffer = byteSub;totalLen = 0;break;}//如果够了一个完整包,则读取包头的数据byte[] headByte = new byte[headSize];Buffer.BlockCopy(saveBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节int bodySize = BitConverter.ToInt16(headByte, 0);//从包头里面分析出包体的长度//这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N//如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存if (haveRead + headSize + bodySize > totalLen){byte[] byteSub = new byte[totalLen - haveRead];Buffer.BlockCopy(saveBuffer, haveRead, byteSub, 0, totalLen - haveRead);saveBuffer = byteSub;break;}else{if (bodySize == 0){ saveBuffer = null;break;}//挨个分解每个包,解析成实际文字 String strc = Encoding.UTF8.GetString(saveBuffer, haveRead + headSize, bodySize);Debug.Log("得到包" + strc); //依次累加当前的数据包的长度haveRead = haveRead + headSize + bodySize;if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0{saveBuffer = null;//设置空 回到原始状态totalLen = 0;//清0}}}}return true;}
}
UDP
UDP的特点
代码
客户端
using UnityEngine;
using System;
using System.Text;
using System.Threading;using System.Net;
using System.Net.Sockets;public class MyUdp { private UdpClient sendClient = null;// private IPEndPoint sendEndPort;private bool isRun;private bool isRecv;public void StartClientUdp(string _ip){// if (sendEndPort != null) {// Debug.Log ("客户端udp已经启动~");// return;// }if (isRun) {Debug.Log ("客户端udp已经启动~");return;}isRun = true;sendClient = UdpManager.Instance.GetClient();// sendEndPort = new IPEndPoint(IPAddress.Parse(_ip), NetConfig.UdpSendPort);// StartRecvMessage ();}private void StartRecvMessage(){Thread t = new Thread(new ThreadStart(RecvThread));t.Start();}public void StopRecvMessage(){isRecv = false;}public void EndClientUdp(){try {isRun = false;isRecv = false;// if (sendEndPort != null) {// UdpManager.Instance.CloseUdpClient();// sendClient = null;// sendEndPort = null;// }UdpManager.Instance.CloseUdpClient();sendClient = null; } catch (Exception ex) {Debug.Log ("udp连接关闭异常:" + ex.Message);}}public void SendMessage(byte[] _mes){if (isRun) {try {sendClient.Send(_mes,_mes.Length); } catch (Exception ex) {Debug.Log ("udp发送失败:" + ex.Message);}} }private void RecvThread(){isRecv = true;IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse("192.169.1.18"), UdpManager.Instance.localPort);while (isRecv){try {byte[] buf = sendClient.Receive(ref endpoint);// Debug.Log("发送量:" + buf.Length.ToString() + "," + GameData.Instance().recvNum.ToString());} catch (Exception ex) {Debug.Log ("udpClient接收数据异常:" + ex.Message);}}Debug.Log ("udp接收线程退出~~~~~");}void OnDestroy(){EndClientUdp ();}
}
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;public class UdpManager {private static UdpManager singleInstance;private static readonly object padlock = new object();public UdpClient _udpClient = null;public int localPort;public static UdpManager Instance{get{lock (padlock){if (singleInstance==null){singleInstance = new UdpManager();}return singleInstance;}}}private UdpManager(){CreatUpd ();}public void Creat(){}void CreatUpd(){_udpClient = new UdpClient ();Debug.Log("CreatUpd " + localPort);IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse("192.168.1.18"), 10011);_udpClient.Connect (endpoint);IPEndPoint _localEnd = (IPEndPoint)_udpClient.Client.LocalEndPoint;localPort = _localEnd.Port;Debug.Log ("udp参数:" + _localEnd.Address + "," + _localEnd.Port);}public void Destory(){CloseUdpClient ();singleInstance = null;}public void CloseUdpClient(){if (_udpClient != null) {_udpClient.Close ();_udpClient = null;}}public UdpClient GetClient(){if (_udpClient == null) {CreatUpd ();}return _udpClient;}}
主程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Uclient : MonoBehaviour {private MyUdp _upd;// Use this for initializationvoid Start () {StartClientUdp();}public void StartClientUdp(){_upd = new MyUdp();_upd.StartClientUdp("192.168.1.18");string data = "aaaabbbbcccc";byte[] message = System.Text.Encoding.UTF8.GetBytes(data);for (int i =0; i<20;i++){_upd.SendMessage(message);}}}
服务器
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System;
public class UdpManager {private static UdpManager singleInstance;private static readonly object padlock = new object();public UdpClient _udpClient = null;public int recvPort;public static UdpManager Instance{get{lock (padlock){if (singleInstance==null){singleInstance = new UdpManager();}return singleInstance;}}}private UdpManager(){CreatUdp ();}public void Creat(){}void CreatUdp(){_udpClient = new UdpClient (10011); // uint IOC_IN = 0x80000000;// uint IOC_VENDOR = 0x18000000;// uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;//byte[] optionOutValue = new byte[4];//byte[] optionInValue = { Convert.ToByte(false) };//_udpClient.Client.IOControl((int)SIO_UDP_CONNRESET, optionInValue, optionOutValue);IPEndPoint _localip = (IPEndPoint)_udpClient.Client.LocalEndPoint;Debug.Log ("udp端口:" + _localip.Port);recvPort = _localip.Port;}public void Destory(){CloseUdpClient ();singleInstance = null;}public void CloseUdpClient(){if (_udpClient != null) {Debug.Log("CloseUdpClient **************** ");_udpClient.Close ();_udpClient = null;}}public UdpClient GetClient(){if (_udpClient == null) {CreatUdp ();}return _udpClient;}}
主程序:
using UnityEngine;
using System;
using System.Text;
using System.Threading;using System.Net;
using System.Net.Sockets;public class ClientUdp {public int userUid;private int sendPortNum;private UdpClient sendClient = null;private IPEndPoint sendEndPort;private bool isRun;private string serverIp;public void StartClientUdp(string _ip,int _uid){if (sendEndPort != null) {Debug.Log ("客户端udp已经启动~");return;}userUid = _uid;serverIp = _ip;isRun = true;sendClient = UdpManager.Instance.GetClient();// sendClient = new UdpClient(NormalData.recvPort);// sendEndPort = new IPEndPoint(IPAddress.Parse(_ip), ServerConfig.udpRecvPort); Thread t = new Thread(new ThreadStart(RecvThread));t.Start();}public void EndClientUdp(){try {isRun = false;UdpManager.Instance.CloseUdpClient();sendClient = null;sendEndPort = null;} catch (Exception ex) {Debug.Log ("udp连接关闭异常:" + ex.Message);}}private void CreatSendEndPort(int _port){sendEndPort = new IPEndPoint(IPAddress.Parse(serverIp), _port);}public void SendMessage(byte[] _mes){if (isRun) {try {sendClient.Send (_mes,_mes.Length,sendEndPort); // GameData.Instance().sendNum+=_mes.Length;// Debug.Log("发送量:" + _mes.Length.ToString() + "," + GameData.Instance().sendNum.ToString());} catch (Exception ex) {Debug.Log ("udp发送失败:" + ex.Message);}}}public void RecvClientReady(int _userUid){ if (_userUid == userUid && sendEndPort == null) {CreatSendEndPort(sendPortNum);}}// 接收线程不断 监听客户端的消息private void RecvThread(){IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(serverIp), UdpManager.Instance.recvPort);while (isRun){try {byte[] buf = sendClient.Receive(ref endpoint);if (sendEndPort == null) {//Debug.Log("接收客户端udp信息:" + endpoint.Port);sendPortNum = endpoint.Port;}string str = System.Text.Encoding.UTF8.GetString(buf);Debug.Log("str *** " + str);//byte packMessageId = buf[PackageConstant.PackMessageIdOffset]; //消息id (1个字节)//Int16 packlength = BitConverter.ToInt16(buf,PackageConstant.PacklengthOffset); //消息包长度 (2个字节)//int bodyDataLenth = packlength - PackageConstant.PacketHeadLength;//byte[] bodyData = new byte[bodyDataLenth];//Array.Copy(buf, PackageConstant.PacketHeadLength, bodyData, 0, bodyDataLenth);//delegate_analyze_message((PBCommon.CSID)packMessageId,bodyData);//是客户端,统计接收量// GameData.Instance().recvNum+=buf.Length;// Debug.Log("发送量:" + buf.Length.ToString() + "," + GameData.Instance().recvNum.ToString());} catch (Exception ex) {Debug.Log (endpoint.Address+ "udpClient接收数据异常:" + ex.Message);}}Debug.Log ("udp接收线程退出~~~~~");}
}