使用C# SocketTCP开发网络游戏ForestWars(五) 处理粘包分包问题

版权声明:未经同意,请勿转载 https://blog.csdn.net/qq_25542475/article/details/86839321
本章主要处理粘包分包问题。

一 为什么要处理粘包分包

由于TCP协议本身的机制,客户端与服务器会维持一个连续发送的数据。如果发送的网络数据包太小,TCP会合并较小的数据包再发送,接收端便无法区分那些数据是发送端分开的,因此便产生了粘包问题。如果数据太大,TCP有可能会把数据拆成多分发送,接收端一次只能接收到部分信息,因此便出现分包问题。

二 怎么处理粘包分包

处理粘包分包问题的一种方法,便是在发送数据时,给数据加上长度信息。每次接收到数据后,先读取长度信息,如果缓冲区的数据长度大于要提取的字节数,则取出相应的字节,然后更新缓冲区,否则等待下一次数据接收。

三 业务逻辑的实现

(1) 服务器端

Conn类中添加字段

 //处理池
        public byte[] lenBytes=new byte[sizeof(Int32)];
        /// <summary>
        /// 消息长度
        /// </summary>
        public int msgLength=0;

新建Message.cs处理粘包分包


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TCP_Sever.Conns;
using TCP_Sever.Timer;
using System.Net;
using System.Net.Sockets;

namespace TCP_Sever.Messages
{
    /// <summary>
    /// 处理消息的接收与发送
    /// </summary>
    class Message
    {
        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="count"></param>
        public static void Recv_Message(Conn conn,int count)
        {
            if(count<=0)
            {
                Console.WriteLine("收到:" + conn.Get_ClientAddress() + "断开连接");
                conn.Client_Close();
                return;
            }
            conn.count += count;
            ProcessData(conn);
        }
        /// <summary>
        /// 处理粘包分包
        /// </summary>
        /// <param name="conn"></param>
        private static void ProcessData(Conn conn)
        {
            if (conn.count < sizeof(Int32))
                return;
            Array.Copy(conn.ReadBuff, conn.lenBytes, sizeof(Int32));
            conn.msgLength = BitConverter.ToInt32(conn.lenBytes, 0);
            if (conn.count < conn.msgLength + sizeof(Int32))
            {
                return;
            }
            //处理消息
            string str = System.Text.Encoding.UTF8.GetString(conn.ReadBuff, sizeof(Int32), conn.msgLength);
            Console.WriteLine("收到消息" + str);
            if (str == "HeatBeat")
            {
                conn.last_HeartTime = HeartbeatTime.GetTimeStamp();
                Console.WriteLine("更新时间" + conn.last_HeartTime);
            }
            int count = conn.count - sizeof(Int32) - conn.msgLength;
            Array.Copy(conn.ReadBuff, sizeof(Int32) + conn.msgLength, conn.ReadBuff, 0, count);
            conn.count = count;
            if (count > 0)
            {
                ProcessData(conn);
            }
        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="conn"></param>
        public static void  Send_Message(string str,Conn conn)
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
            byte[] length = BitConverter.GetBytes(bytes.Length);
            byte[] sendbuff = length.Concat(bytes).ToArray();
            try
            {
                //异步发送
                conn.Client_Socket.BeginSend(sendbuff, 0, sendbuff.Length, SocketFlags.None, null, null);
            }
            catch (Exception e)
            {
                Console.WriteLine("发送消息失败" + e.Message);
            }
        }
    }
}

Sever类中,修改AsynReceiveCallBack函数

      private static void AsynReceiveCallBack(IAsyncResult ar)
        {
            Conn conn = (Conn)ar.AsyncState;
            lock (conn)
            {
                try
                {
                    int count= conn.Client_Socket.EndReceive(ar);
                    Message.Recv_Message(conn, count);
                    for (int i = 0; i < Conn_Helper.Conns.Length; i++)
                    { 
                        if (!Conn_Helper.Conns[i].isUse)continue;
                        AsynSend(Conn_Helper.Conns[i]);
                    }
                    //继续接收,实现循环
                    conn.Client_Socket.BeginReceive(conn.ReadBuff,conn.count ,conn.Buff_Remain(), SocketFlags.None, AsynReceiveCallBack, conn);
                }
                catch (Exception e)
                {
                    Console.WriteLine("客户端断开:" + e.Message);
                    conn.Client_Close();
                }
            }
           
        }

Sever类中,修改AsynSend函数

     public static void AsynSend(Conn conn)
        {
            if (sever_socket == null)
            {
                Console.WriteLine("请先开启服务器");
                return;
            }
            Message.Send_Message("客户端你好",conn);
        }

(2) 客户端

新建Message.cs处理粘包分包

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using System.Net;
using System.Net.Sockets;

namespace YCX_Tool.Client.messages
{
    class Message
    {
       static  int buffCount = 0;
       static  byte[] lenBytes = new byte[sizeof(Int32)];
       static  Int32 msgLength = 0;
        /// <summary>
        /// 接收
        /// </summary>
        /// <param name="count"></param>
        /// <param name="readBuff"></param>
        public static void Recv_Message(int count,byte[] readBuff)
        {
            buffCount += count;
            ProcessData(readBuff);
        }
        private static void ProcessData(byte[] readBuff)
        {
            if (buffCount < sizeof(Int32))
                return;
            Array.Copy(readBuff, lenBytes, sizeof(Int32));
            msgLength = BitConverter.ToInt32(lenBytes, 0);
            if (buffCount < msgLength + sizeof(Int32))
                return;
            string str = System.Text.Encoding.UTF8.GetString(readBuff, sizeof(Int32), (int)msgLength);
            Debug.Log("接收到信息:" + str);
            //清除已处理的信息
            int count = buffCount - msgLength - sizeof(Int32);
            Array.Copy(readBuff, msgLength+sizeof(Int32), readBuff, 0, count);
            buffCount = count;
            if (count > 0)
            {
                ProcessData(readBuff);
            }
        }
        public static void Send(string str,Socket socket)
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
            byte[] length = BitConverter.GetBytes(bytes.Length);
            byte[] sendbuff = length.Concat(bytes).ToArray();
            try
            {
                //异步发送
                socket.BeginSend(sendbuff, 0, sendbuff.Length, SocketFlags.None, null, null);
            }
            catch (Exception e)
            {
                Debug.Log("发送消息失败" + e.Message);
            }
        }
    }
}

Client类

修改AsynReceiveCallBack函数

 private static void AsynReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                //获得客户端套接字
                int count = client_socket.EndReceive(ar);
                Message.Recv_Message(count, readBuff);
                //继续接收,实现循环
                client_socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, AsynReceiveCallBack, null);
            }
            catch (Exception e)
            {
                Debug.Log("服务器断开:" + e.Message);
            }
            
        }

修改AsynSend函数

 public static void AsynSend()
        {
            //客户端接入
            if (client_socket == null)
            {
                Debug.Log("请先开启服务器");
                return;
            }
            Message.Send("服务端你好",client_socket);
        }

猜你喜欢

转载自blog.csdn.net/qq_25542475/article/details/86839321
今日推荐