当前位置: 首页 > news >正文

Unity网络开发--套接字Socket(2)

分包 黏包

什么是分包、黏包?

分包、黏包指在网络通信中由于各种因素(网络环境、API 规则等)造成的消息与消息之间出现的两种状态

分包:一个消息分成了多个消息进行发送

黏包:一个消息和另一个消息黏在了一起

注意:分包和黏包可能同时发生

如何判断消息是否出现分包或者黏包,可以添加一个消息头部,消息头部是消息的长度,判断长度是否合理

HandleReceiveMsg函数处理分包黏包

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.SocialPlatforms;public class lesson4_1 : MonoBehaviour
{private static lesson4_1 instance;public static lesson4_1 Instance => instance;private Socket socket;private Queue<BaseMsg> sendMsgQueue = new Queue<BaseMsg>();private Queue<BaseMsg> receiveMsgQueue = new Queue<BaseMsg>();//private Thread sendThread;// private Thread receiveThread;private byte[] reveiveBytes = new byte[1024 * 1024];private int reveiveNum;private bool isClose = false;private byte[] cacheBytes = new byte[1024 * 1024];private int cacheNum = 0;private void Awake(){instance = this;}// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if (receiveMsgQueue.Count > 0){PlayerMsg player = receiveMsgQueue.Dequeue() as PlayerMsg;print(player.playerID);print(player.playerData.name);}}public void Connect(string ip, int port){socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip),port);try{socket.Connect(iPEndPoint);ThreadPool.QueueUserWorkItem(SendMsg);ThreadPool.QueueUserWorkItem(ReceiveMsg);//sendThread = new Thread(SendMsg);//sendThread.Start();//receiveThread = new Thread(ReceiveMsg);//receiveThread.Start();}catch (SocketException e){Console.WriteLine(e.Message);}}public void Send(BaseMsg info){sendMsgQueue.Enqueue(info);}private void SendMsg(object obj){while (!isClose){if (sendMsgQueue.Count > 0){socket.Send(sendMsgQueue.Dequeue().Writing());}}}private void ReceiveMsg(object obj){while (!isClose){if (socket.Available > 0){reveiveNum = socket.Receive(reveiveBytes);HandleReceiveMsg(reveiveBytes, reveiveNum);//int id = BitConverter.ToInt32(reveiveBytes, 0);//switch (id)//{ //    case 1://        PlayerMsg playerMsg = new PlayerMsg();//        playerMsg.Reading(reveiveBytes,4);//        receiveMsgQueue.Enqueue(playerMsg);//        break;//}    }}}private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum){int msgId = 0;int msgLength = 0;int nowIndex = 0;//收到消息应该先看看有没有缓存的 如果有直接拼接到后面receiveBytes.CopyTo(cacheBytes, cacheNum);cacheNum += receiveNum;while (true){msgLength = -1;//处理解析一条消息if (cacheNum - nowIndex >= 8){//解析IDmsgId = BitConverter.ToInt32(cacheBytes, nowIndex);nowIndex += 4;//解析长度msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);nowIndex += 4;}if (cacheNum - nowIndex >= msgLength && msgLength != -1){//解析消息体BaseMsg msg = null;switch (msgId){case 1:PlayerMsg playerMsg = new PlayerMsg();playerMsg.Reading(cacheBytes, nowIndex);msg = playerMsg;break;}if (msg != null)receiveMsgQueue.Enqueue(msg);nowIndex += msgLength;if (nowIndex == cacheNum){cacheNum = 0;break;}}else{//如果不满足证明有分包,需要将当前收到的内容记录下来,等待下次接受消息后再处理//receiveBytes.CopyTo(cacheBytes,0);//cacheNum = receiveNum;if (msgLength != -1)nowIndex -= 8;Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);cacheNum = cacheNum - nowIndex;break;}}}public void Close(){if (socket != null){isClose = true;socket.Shutdown(SocketShutdown.Both);socket.Close();}}private void OnDestroy(){Close();}
}
心跳消息

客户端主动断开连接

目前在客户端主动退出时我们会调用socket的ShutDown和Close方法

但这两个方法调用后服务端无法得知客户端已经主动退出

客户端可以通过Disconnect方法主动和服务器断开连接

服务端可以通过Conected属性判断连接状态决定是否释放socket

但是由于服务端Conected变量标识的是上一次收发消息是否成功

所以服务端无法准确判断客户端的连接状态

因此我们需要定义一条退出消息用于准确断开和客户端之间的连接

定义退出消息

using System.Collections;
using System.Collections.Generic;public class QuitMsg : BaseMsg
{public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteInt(bytes, GetID(), ref index);WriteInt(bytes, GetBytesNum() - 8, ref index);return bytes;}public override int Reading(byte[] bytes, int beginIndex){return 0;}public override int GetBytesNum(){return 4 + 4;}public override int GetID(){return 1001;}
}

心跳消息

什么是心跳消息?

所谓心跳消息,就是在长连接中,客户端和服务端之间定期发送的一种特殊的数据包

用于通知对方自己还在线,以确保长连接的有效性

由于其发送的时间间隔往往是固定的持续的,就像是心跳一样一直存在

所以我们称之为心跳消息

为什么需要心跳消息?

  1. 避免非正常关闭客户端时,服务端无法正常收到关闭连接消息,通过心跳消息我们可以自定义超时判断,如果超时没有收到客户端消息,证明客户端已经断开连接

  2. 避免客户端长期不发送消息,防火墙或者路由器会断开连接,我们可以通过心跳消息一直保持活跃状态

心跳消息结构

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HeartMsg : BaseMsg
{public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteInt(bytes, GetID(), ref index);WriteInt(bytes, GetBytesNum() - 8, ref index);return bytes;}public override int Reading(byte[] bytes, int beginIndex){return 0;}public override int GetBytesNum(){return 4 + 4;}public override int GetID(){return 1000;}
}

客户端定时发送心跳消息

    private void Awake(){instance = this;InvokeRepeating("SendHeartMsg", 0 , 2);}private void SendHeartMsg(){if (socket.Connected)Send(heartbeatMsg);}

服务端定时检测是否超时

        private void CheckTimeOut(){if (frontTime != -1 &&DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME){Program.serverSocket.AddDelSocket(this);}Thread.Sleep(5000); }
异步

服务器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp3
{internal class ClientSocket{public Socket socket;public int ClientID;private static int CLIENT_BEGIN_ID = 1;private byte[] cacheBytes = new byte[1024];private int cacheNum = 0;public ClientSocket(Socket socket){ this.ClientID = CLIENT_BEGIN_ID++;this.socket = socket;this.socket.BeginReceive(cacheBytes, cacheNum, cacheBytes.Length, SocketFlags.None, ReceiveCallBack, this.socket);}private void ReceiveCallBack(IAsyncResult result){try{cacheNum = this.socket.EndReceive(result);Console.WriteLine(Encoding.UTF8.GetString(cacheBytes,0,cacheNum));cacheNum = 0;if(this.socket.Connected)this.socket.BeginReceive(cacheBytes, cacheNum, cacheBytes.Length, SocketFlags.None, ReceiveCallBack, this.socket);}catch (SocketException ex){Console.WriteLine("接受失败"+ex.ErrorCode + ex.Message);}}public void Send(string str){if (this.socket.Connected){byte[] bytes = Encoding.UTF8.GetBytes(str);this.socket.BeginSend(bytes,0,bytes.Length,SocketFlags.None, SendCallBack, this.socket);}else { }}private void SendCallBack(IAsyncResult result) {try{this.socket.EndSend(result);}catch (SocketException ex){Console.WriteLine("发送失败"+ex.Message);}}}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp3
{internal class ServerSocket{private Socket socket;private Dictionary<int,ClientSocket> clientDic = new Dictionary<int,ClientSocket>();public void Start(string ip, int port, int num){ socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);try{socket.Bind(iPEndPoint);socket.Listen(num);socket.BeginAccept(AcceptCallBack, socket);}catch (Exception ex){Console.WriteLine(ex.Message);}}private void AcceptCallBack(IAsyncResult result){try{ Socket clentSocket = socket?.EndAccept(result);if (clentSocket != null){ClientSocket client = new ClientSocket(clentSocket);clientDic.Add(client.ClientID,client);}socket.BeginAccept(AcceptCallBack, socket);}catch (SocketException EX){Console.WriteLine(EX.Message);}}public void Broadcast(string str){foreach (ClientSocket client in clientDic.Values){client.Send(str);}}}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp3
{internal class Program{static void Main(string[] args){Console.WriteLine("开启服务器");ServerSocket serverSocket = new ServerSocket();serverSocket.Start("127.0.0.1", 8080, 1000);while (true){string input = Console.ReadLine();if (input.Substring(0, 2) == "B:"){ serverSocket.Broadcast(input.Substring(2));}}}}
}

客户端

using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Unity.VisualScripting;
using UnityEngine;public class NetAsyncMgr : MonoBehaviour
{private static NetAsyncMgr instance;public static NetAsyncMgr Instance => instance;private byte[] cacheBytes = new byte[1024 * 1024];private int cacheNum = 0;private Socket socket = null;private void Awake(){instance = this;DontDestroyOnLoad(gameObject);}void Start(){}void Update(){}public void Connect(string ip, int port){if (socket != null && socket.Connected)return;IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);SocketAsyncEventArgs args = new SocketAsyncEventArgs();args.RemoteEndPoint = iPEndPoint;args.Completed += (socket,args) =>{if (args.SocketError == SocketError.Success){print("连接成功");SocketAsyncEventArgs reveiveArgs = new SocketAsyncEventArgs();reveiveArgs.SetBuffer(cacheBytes,0, cacheBytes.Length);reveiveArgs.Completed += ReceiveCallBack;this.socket.ReceiveAsync(reveiveArgs);}else{print("连接失败"+ args.SocketError);}};socket.ConnectAsync(args);}private void ReceiveCallBack(object obj,SocketAsyncEventArgs args) {if (args.SocketError == SocketError.Success){print(Encoding.UTF8.GetString(args.Buffer, 0, args.BytesTransferred));args.SetBuffer(0, args.Buffer.Length);if(this.socket.Connected)this.socket.ReceiveAsync(args);}else{print("接受消息出错" + args.SocketError);Close();}}public void Close(){if (socket != null){ socket.Shutdown(SocketShutdown.Both);socket.Disconnect(false);socket.Close();socket = null;}}public void Send(string str) {if (this.socket.Connected){byte[] bytes = Encoding.UTF8.GetBytes(str);SocketAsyncEventArgs args = new SocketAsyncEventArgs();args.SetBuffer(bytes,0, bytes.Length);args.Completed += (socket, args) =>{if (args.SocketError == SocketError.Success)print("发送成功");elsethis.socket.Close();};this.socket.SendAsync(args);}}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class MainAsync : MonoBehaviour
{public Text text;public Button button;// Start is called before the first frame updatevoid Start(){if (NetAsyncMgr.Instance == null){GameObject gameObject = new GameObject("Net");gameObject.AddComponent<NetAsyncMgr>();}NetAsyncMgr.Instance.Connect("127.0.0.1",8080);button.onClick.AddListener(() =>{if (text.text != "")NetAsyncMgr.Instance.Send(text.text);});}// Update is called once per framevoid Update(){}
}
UDP通信

服务端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp4
{internal class Program{static void Main(string[] args){//创建套接字Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//绑定本机地址IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);socket.Bind(iPEndPoint);//接受消息byte[] datas = new byte[512];EndPoint endPoint = new IPEndPoint(IPAddress.None, 0); ;int length = socket.ReceiveFrom(datas, ref endPoint);Console.WriteLine((endPoint as IPEndPoint).Address.ToString()+":"+ (endPoint as IPEndPoint).Port.ToString()+ "的消息:" + Encoding.UTF8.GetString(datas, 0, length));//发送消息socket.SendTo(Encoding.UTF8.GetBytes("jjjjjjjj"), endPoint);//关闭释放socket.Shutdown(SocketShutdown.Both);socket.Close();}}
}

客户端

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;public class lesson7 : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){//创建套接字Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram, ProtocolType.Udp);//绑定本机地址IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"),8080);socket.Bind(iPEndPoint);//发送到指定目标socket.SendTo(Encoding.UTF8.GetBytes("1221221"), new IPEndPoint(IPAddress.Parse("127.0.0.1"),8081));//接受消息byte[] datas = new byte[512];EndPoint endPoint = new IPEndPoint(IPAddress.None,0); ;int length = socket.ReceiveFrom(datas, ref endPoint);print((endPoint as IPEndPoint).Address.ToString()+ ":"+ (endPoint as IPEndPoint).Port.ToString()+ "的消息:" + Encoding.UTF8.GetString(datas, 0, length));//释放关闭socket.Shutdown(SocketShutdown.Both);socket.Close();}// Update is called once per framevoid Update(){}
}

结果

http://www.dtcms.com/a/461630.html

相关文章:

  • 大学网站建设技术方案wordpress 评论优化
  • 做网站设计要注意什么问题wordpress 枚举用户
  • 基于单片机的Boost升压斩波电源电路
  • 【Emmy精简系统】清爽加速Windows 11 25H2
  • 洛谷P2071 座位安排
  • 广西代理网站建设公司公司网站建设注意点
  • 设计模式--外观模式:简化复杂系统的统一接口
  • 网站开发需要看哪些书哪个网站可以做一对一老师
  • k8s基础监控promql
  • K8S(一)—— 云原生与Kubernetes(K8S)从入门到实践:基础概念与操作全解析
  • 从入门到精通【Redis】初识Redis哨兵机制(Sentinel)
  • Go语言操作Redis
  • JVM 线上调优与排查指南
  • 青岛公司建站2024年新闻摘抄
  • 杭州网站制作工作室做网站含营销
  • 解决Intellij IDEA控制台,logger.info(),system.out.println()等中文乱码问题
  • Windows+Linux命令总结
  • 无人机智能技术模块运行要点与难点
  • C++17 新特性: std::string_view —— 减少内存分配,让std::string运行得更快
  • 北京营销策划有限公司优化官方网站设计
  • 网站建设涉及和描述的一些问题珠海网站建设厚瑜
  • 11. ubuntu14.0.4 安装文件管理器右键打开终端
  • k8s架构组件
  • 「机器学习笔记8」决策树学习:从理论到实践的全面解析(下)
  • ES6(二)
  • 做co的网站学校网页设计模板图片
  • QTreeView实现多折叠效果
  • 纯 flash 网站比较好的设计欣赏网站
  • 【笔记】树链剖分三题(洛谷 P3384 树剖模板 P2146 软件包管理器 P2486 染色)
  • 建设银行网站用户名忘了怎么办wordpress标签链接优化