Unity丛林战争学习02_TCP_解决分包和黏包的问题

1.分包和黏包
分包:当发送的消息过长时会出现分包的情况,即一个消息分多次接收。
黏包:当发送的消息过短时会出现黏包的情况,即多个消息合在一起接收。

2.导致的问题
分包:UTF-8 使用一至四个字节为每个字符编码,比如有一个字符有三个自己,它的一个字节被分包到了上一个包中,另外两个字节被分配到下一个包中,这样每接收一个包后使用System.Text.Encoding.UTF8.ToString之后,就会出现上一个包结尾出现乱码,下个包开头出现乱码的情况
黏包:接收方不知道一次提取多少个字节。

3.改进思路
思路:发送方每次发送消息的时候在消息前面加上一个int类型的数据,表示该消息的长度,接收方就可以根据该int数据进行提取后面对应长度,转换为字符串。

4.发送方(客户端)

定义下面这样一个类,给每一条消息前面加上长度信息(int):

	class Message
	{
		public static byte[] PackageData(string message)
		{
			byte[] originalByte = Encoding.UTF8.GetBytes(message);
			byte[] sizeByte = BitConverter.GetBytes(originalByte.Length);
			return (sizeByte.Concat(originalByte).ToArray());
		}
	}

发送的时候就可以发送处理好的消息,即:

clientSocket.Send(Message.PackageData(sendMessage));

整个客户端代码

Message类(处理消息:给消息前加上长度)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace clientProject
{
	class Message
	{
		public static byte[] PackageData(string message)
		{
			byte[] originalByte = Encoding.UTF8.GetBytes(message);
			byte[] sizeByte = BitConverter.GetBytes(originalByte.Length);
			return (sizeByte.Concat(originalByte).ToArray());
		}
	}
}

Program.cs(对处理好的消息进行发送)

using System;
using System.Net.Sockets;
using System.Net;

namespace clientProject
{
	class Program
	{
		static void Main(string[] args)
		{
			//创建客户端套接字
			Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			//连接服务器
			clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.137.1"), 13001));

			//接收服务器的一条消息
			byte[] messageRecieve = new byte[1024];
			int size = clientSocket.Receive(messageRecieve);
			string messageRecieveStr = System.Text.Encoding.UTF8.GetString(messageRecieve,0,size);
			Console.WriteLine(messageRecieveStr);

			//连续发送短包
			string sendMessage = null;
			for (int i=0;i<100;i++)
			{
				sendMessage = i.ToString();
			    clientSocket.Send(Message.PackageData(sendMessage));
			}


			Console.ReadKey();
			clientSocket.Close();
		}
	}
}

5.接收方(服务器端)

定义一个类,对接收的消息进行存储和解析:

	class Message
	{
		//存储接受到的数据
		byte[] data = new byte[1024];
		public byte[] Data
		{
			get { return data; }
			//set { data = value; }
		}
		//下一次应该从哪存放
		int startIndex = 0;
		public int StartIndex
		{
			get { return startIndex; }
			//set { startIndex = value; }
		}
		public void AddStartIndex(int num)
		{
			startIndex += num;
		}
		/// <summary>
		/// 返回剩余存储长度
		/// </summary>
		/// <returns></returns>
		public int GetRestLength()
		{
			return (data.Length - startIndex);
		}
		/// <summary>
		/// 解析消息,解析到一条完整的消息就输出,然后把后面的数据前移覆盖已输出的消息
		/// </summary>
		public void ParsingData()
		{

			while (true)
			{
				//data内容没有四个字节(标记的长度)
				if (startIndex <= 4) break;

				// 具体消息的内容长度
				int count = BitConverter.ToInt32(data, 0);

				//data内容包含一次发送的内容
				if (startIndex - 4 >= count)
				{
					//输出消息内容
					string str = Encoding.UTF8.GetString(data, 4, count);
					Console.WriteLine("从客户端发来:" + str);

					//把未输出的内容前移,计算startIndex
					Array.Copy(data, 4 + count, data, 0, startIndex - count);
					startIndex = startIndex - count - 4;
				}
				//data内容不及一次发送的内容
				else
				{
					break;
				}

			}
		}

在Program.cs里面,接收时,我们对消息进行存储:

clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.GetRestLength(), SocketFlags.None, RecieveMessage, clientSocket);

注:存储到Message类的对象的data里,StartIndex表示我应该存放的位置,也就是data存放内容的长度。

然后解析数据,对StartIndex进行更新:

msg.AddStartIndex(size);
msg.ParsingData();

整个代码:
Message类(对消息进行存储,根据标记好的长度输出消息,并用后面未输出的消息覆盖掉已输出的消息)

using System;
using System.Collections.Generic;
using System.Text;

namespace SeverProject
{
	class Message
	{
		//存储接受到的数据
		byte[] data = new byte[1024];
		public byte[] Data
		{
			get { return data; }
			//set { data = value; }
		}
		//下一次应该从哪存放
		int startIndex = 0;
		public int StartIndex
		{
			get { return startIndex; }
			//set { startIndex = value; }
		}
		public void AddStartIndex(int num)
		{
			startIndex += num;
		}
		/// <summary>
		/// 返回剩余存储长度
		/// </summary>
		/// <returns></returns>
		public int GetRestLength()
		{
			return (data.Length - startIndex);
		}
		/// <summary>
		/// 解析消息,解析到一条完整的消息就输出,然后把后面的数据前移覆盖已输出的消息
		/// </summary>
		public void ParsingData()
		{

			while (true)
			{
				//data内容没有四个字节(标记的长度)
				if (startIndex <= 4) break;

				// 具体消息的内容长度
				int count = BitConverter.ToInt32(data, 0);

				//data内容包含一次发送的内容
				if (startIndex - 4 >= count)
				{
					//输出消息内容
					string str = Encoding.UTF8.GetString(data, 4, count);
					Console.WriteLine("从客户端发来:" + str);

					//把未输出的内容前移,计算startIndex
					Array.Copy(data, 4 + count, data, 0, startIndex - count);
					startIndex = startIndex - count - 4;
				}
				//data内容不及一次发送的内容
				else
				{
					break;
				}

			}
		}

	}
}

Program.cs(接收消息后,存放到Message.data后面空白区域,即StartIndex索引处,调用解析方法,最后更新StarIndex)

using System;
using System.Net.Sockets;
using System.Net;


namespace SeverProject
{
    class Program
    {
        static Message msg = new Message();
        static byte[] messageRecieve = new byte[1024];
        static void Main(string[] args)
        {

            //创建服务器端套接字
            Socket severSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //绑定套接字
            IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("192.168.137.1"), 13001);
            severSocket.Bind(iPEndPoint);

            //监听连接
            severSocket.Listen(0);

            //异步接受连接,返回客户端套接字
            //Socket clientSocket = severSocket.Accept();
            severSocket.BeginAccept(AcceptCallBack, severSocket);
            //暂停主线程
            Console.ReadKey();
        }
        static void AcceptCallBack(IAsyncResult ar)
        {
            Socket severSocket = (Socket)ar.AsyncState;
            Socket clientSocket = severSocket.EndAccept(ar);


            //向客户端发送一条消息
            string message = "Hello Client";
            clientSocket.Send(System.Text.Encoding.UTF8.GetBytes(message));

            //异步接收消息
            clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.GetRestLength(), SocketFlags.None, RecieveMessage, clientSocket);
            //异步连接其他客户端
            severSocket.BeginAccept(AcceptCallBack, severSocket);

        }
        static void RecieveMessage(IAsyncResult ar)
        {

            Socket clientSocket = (Socket)ar.AsyncState;
           try
           {
                int size = clientSocket.EndReceive(ar);
                string recieveStr = System.Text.Encoding.UTF8.GetString(msg.Data, 0, size);
                if (recieveStr.Length == 0)
                    return;
                msg.AddStartIndex(size);
                msg.ParsingData();

                clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.GetRestLength(), SocketFlags.None, RecieveMessage, clientSocket);
            }
            catch (Exception e)
            {

                Console.WriteLine(e);
                clientSocket.Close();

            }

        }
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值