IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> C#Socket通信,解决粘包和分包问题 -> 正文阅读

[网络协议]C#Socket通信,解决粘包和分包问题

源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载

(16条消息) 网络通信编程学习.7z-C#文档类资源-CSDN文库https://download.csdn.net/download/Trinity_Force/44900216

什么是粘包分包

  1. TCP是面向连接的协议
  2. TCP是点到点的通信
  3. TCP提供可靠的传输服务
  4. TCP协议提供全双工的通信
  5. TCP协议面向字节流进行传输的,可以对用户的数据进行拆分或合并

????????TCP协议是面向字节流传输的,TCP协议会保证字节流传输时顺序不会改变,不会丢失内容,但是TCP协议会灵活的拆分或者合并用户Socket.Send(buffer)出来的内容,将小的数据整合发送或者是将大的数据拆开发送。

????????所以在实际的编程中就会出现服务端一次Receive就收到了客户端多次Send的数据(“粘包”),或者是客户端只Send了一次,服务端却要多次Receive才能完整接收。

粘包示例:客户端发送了一万条“Hello”到服务端,结果服务端收到的是这样的

分包示例:客户端发送了一大串“a”到服务端,结果服务端是分三次收到的。

?解决方法

自己自定义报文格式,发送时根据固定的格式封包,接收时再按照这个格式解包

1 数据包首部添加数据包长度

接收到数据时,先解析首部的“数据包长度”,再解析数据包内容,如果数据包内容的长度不足数据包首部规定的长度,则认为出现了“分包”,需要等待接收下一个数据包,直到传输完整。如果数据包内容的长度大于数据包首部规定的长度,则出现了“粘包”需要认为将粘包分开。

2 数据包结尾添加固定的分隔符

接收到数据后,如果出现结尾标识,则人为将粘包分开,如果一个包中没有结尾标识,则认为出现了“分包”,需要等待下一个数据包,直到出现结尾标识

客户端发送时的封包方法

private void BtnSend_Click(object sender, EventArgs e)
        {
            byte[] dataToBeSend = GetSendData(TextSendData.Text.Trim());
            if (int.TryParse(textRepeatTimes.SelectedItem.ToString(), out int times))
            {
                int dataSize = 0;
                for (int i = 0; i < times; i++)
                {
                    dataSize += ClientSocket.Send(dataToBeSend);
                }
                ShowReceiveDataWithDelegate($"共发送{dataSize}字节的数据");
            }
        }

        private byte[] GetSendData(string text)
        {
            //数据包内容
            byte[] content = Encoding.Default.GetBytes(text);
            //数据包头部
            byte[] header = new byte[4];
            ConvertIntToByteArray(content.Length, ref header);
            //最终封装好的数据包,数据包首位 0 消息 1 文件,2-5位 数据长度
            byte[] dataToBeSend = new byte[content.Length + 5];
            dataToBeSend[0] = 0;
            Array.Copy(header, 0, dataToBeSend, 1, header.Length);
            Array.Copy(content, 0, dataToBeSend, 5, content.Length);
            return dataToBeSend;
        }

        /// <summary>
        /// 把int32类型的数据转存到4个字节的byte数组中
        /// </summary>
        /// <param name="m">int32类型的数据
        /// <param name="arry">4个字节大小的byte数组
        /// <returns></returns>
        private bool ConvertIntToByteArray(Int32 m, ref byte[] arry)
        {
            if (arry == null) return false;
            if (arry.Length < 4) return false;
            arry[0] = (byte)(m & 0xFF);
            arry[1] = (byte)((m & 0xFF00) >> 8);
            arry[2] = (byte)((m & 0xFF0000) >> 16);
            arry[3] = (byte)((m >> 24) & 0xFF);
            return true;
        }

服务端解包类

using System;
using System.Collections.Generic;

namespace AsyncSocketServer
{
    /// <summary>
    /// SocketTCP通信解包类,包格式为:内容类型(1位)内容长度(4位)剩余。。
    /// 读取完DataList的数据后请务必执行Clear方法();
    /// </summary>
    public class SocketTcpPack
    {
        /// <summary>
        /// 接收是否完成了
        /// </summary>
        public bool IsComplete = false;

        /// <summary>
        /// 接收缓存
        /// </summary>
        public byte[] Buffer;

        /// <summary>
        /// 下次接收从Buffer的哪里开始写入
        /// </summary>
        public int Offset = 0;

        /// <summary>
        /// 下次写入Buffer的长度
        /// </summary>
        public int Size;

        /// <summary>
        /// 接收到的数据
        /// </summary>
        public List<ReceiveDataModel> DataList = new List<ReceiveDataModel>();

        /// <summary>
        /// 缓存长度
        /// </summary>
        private readonly int BufferLength;

        public SocketTcpPack(int bufferLength = 1024)
        {
            BufferLength = bufferLength;
            Buffer = new byte[BufferLength];
            Size = BufferLength;
        }

        /// <summary>
        /// 处理接收到的数据
        /// </summary>
        /// <param name="currentDataSize">接收到的数据长度,Socket.Receive()方法返回的数值</param>
        public void UntiePack(int currentDataSize)
        {
            //Size != BufferLength说明Buffer中保留了一些上次接收的数据,要把这部分数据长度加上
            int dataSize = currentDataSize;
            if (Size != BufferLength)
            {
                dataSize += Offset;
            }

            if (DataList.Count == 0)
            {
                SplitData(Buffer, dataSize);
            }
            else
            {
                //取出DataList中的最后一个元素,通过判断这个元素是否完整来判断是有分包需要补充完整
                ReceiveDataModel LastReceiveData = DataList[DataList.Count - 1];
                if (LastReceiveData.IsComplete)
                {
                    SplitData(Buffer, dataSize);
                }
                else
                {
                    //最后一个包的剩余长度
                    int remainingDataLength = LastReceiveData.DataLength - LastReceiveData.Content.Length;
                    //剩余长度 < 本次接收的数据长度,说明这一次接收就可以把上一个分包补充完整
                    if (remainingDataLength < dataSize)
                    {
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.DataLength];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);

                        //继续处理剩下的数据
                        byte[] last = new byte[dataSize - remainingDataLength];
                        Array.Copy(Buffer, remainingDataLength, last, 0, last.Length);
                        SplitData(last, last.Length);
                    }
                    //剩余长度 > 本次接收的数据长度,说明这一次接收还不能把上一个分包补充完整,还需要继续等待接收
                    else if (remainingDataLength > dataSize)
                    {
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.Content.Length + dataSize];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, dataSize);

                        Offset = 0;
                        Size = BufferLength;
                        Buffer = new byte[BufferLength];
                    }
                    else
                    {
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.DataLength];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);

                        Offset = 0;
                        Size = BufferLength;
                        Buffer = new byte[BufferLength];
                        IsComplete = true;
                    }
                }
            }
        }

        /// <summary>
        /// 处理byte[]前5位就是包首部的这种数据
        /// </summary>
        /// <param name="data">byte[]</param>
        /// <param name="dataSize">内容的实际长度</param>
        private void SplitData(byte[] data, int dataSize)
        {
            //长度 <= 5 说明包首部还没有接收完成,需要继续接收
            if (dataSize <= 5)
            {
                byte[] temp = new byte[BufferLength];
                Array.Copy(data, 0, temp, 0, dataSize);
                Buffer = temp;
                Offset = dataSize;
                Size = BufferLength - dataSize;
                IsComplete = true;
                return;
            }

            //包首部
            byte[] header = new byte[5];
            //包内容
            byte[] content = new byte[dataSize - 5];

            Array.Copy(data, 0, header, 0, 5);
            Array.Copy(data, 5, content, 0, dataSize - 5);

            //包内容长度
            int dataLength = BitConverter.ToInt32(header, 1);

            //dataLength < content.Length 说明本次接收的数据中已经包含一个完整的包,将这个完整的包取出后继续处理剩下的数据
            if (dataLength < content.Length)
            {
                //发生了粘包
                byte[] b = new byte[dataLength];
                Array.Copy(content, 0, b, 0, dataLength);
                ReceiveDataModel receiveData = new ReceiveDataModel()
                {
                    DataType = header[0],
                    DataLength = dataLength,
                    Content = b
                };
                DataList.Add(receiveData);
                byte[] last = new byte[content.Length - dataLength];
                Array.Copy(content, dataLength, last, 0, last.Length);
                SplitData(last, last.Length);
            }
            //dataLength >= content.Length 说明本次接收的数据不完整,保存后继续接收
            else if (dataLength >= content.Length)
            {
                //发生了分包或者什么都没发生
                ReceiveDataModel receiveData = new ReceiveDataModel()
                {
                    DataType = header[0],
                    DataLength = dataLength,
                    Content = content
                };
                DataList.Add(receiveData);
                Offset = 0;
                Size = BufferLength;
                Buffer = new byte[BufferLength];
                if (dataLength == content.Length) IsComplete = true;
            }
        }

        public void Clear()
        {
            if (DataList.Count > 0)
            {
                DataList.Clear();
                IsComplete = false;
            }
        }
    }
}

发送时调用这个类

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace AsyncSocketServer
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 存储客户端连接
        /// </summary>
        private Dictionary<string, Socket> ClientSocketList = new Dictionary<string, Socket>();

        /// <summary>
        /// 接收数据缓冲区
        /// </summary>
        private Dictionary<string, SocketTcpPack> ReceiveBufferDic = new Dictionary<string, SocketTcpPack>();

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnStartListen_Click(object sender, EventArgs e)
        {
            if (!IPAddress.TryParse(TextIP.Text.Trim(), out IPAddress ip))
            {
                MessageBox.Show("不正确的IP地址");
                return;
            };

            if (!int.TryParse(TextPort.Text.Trim(), out int port))
            {
                MessageBox.Show("不正确的端口号");
                return;
            }

            //创建Socket
            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint pEndPoint = new IPEndPoint(ip, port);
            //绑定IP和端口
            socketServer.Bind(pEndPoint);
            //开始监听
            socketServer.Listen(10);
            ShowReceiveDataWithDelegate("监听成功");
            //接收连接
            socketServer.BeginAccept(Accept, socketServer);
        }

        /// <summary>
        /// BeginAccept的回调
        /// </summary>
        /// <param name="result"></param>
        private void Accept(IAsyncResult result)
        {
            Socket socket = (Socket)result.AsyncState;
            Socket clientSocket = socket.EndAccept(result);
            string clientIP = clientSocket.RemoteEndPoint.ToString();
            ClientSocketList.Add(clientIP, clientSocket);
            CmbSocket.BeginInvoke(new EventHandler(delegate
            {
                CmbSocket.Items.Add(clientIP);
            }));
            ShowReceiveDataWithDelegate("连接成功");

            SocketTcpPack tcpPack = new SocketTcpPack(1024);
            ReceiveBufferDic.Add(clientIP, tcpPack);
            //开始接受客户端消息
            clientSocket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, clientSocket);
            //接受下一个连接
            socket.BeginAccept(Accept, socket);
        }

        /// <summary>
        /// BeginReceive的回调
        /// </summary>
        /// <param name="result"></param>
        private void Receive(IAsyncResult result)
        {
            Socket socket = (Socket)result.AsyncState;
            try
            {
                string clientIP = socket.RemoteEndPoint.ToString();
                int dataSize = socket.EndReceive(result);
                if (dataSize > 0)
                {
                    //对接收到的消息进行解包
                    ReceiveBufferDic[clientIP].UntiePack(dataSize);
                    if (ReceiveBufferDic[clientIP].IsComplete)
                    {
                        foreach (var item in ReceiveBufferDic[clientIP].DataList)
                        {
                            string str = Encoding.Default.GetString(item.Content, 0, item.DataLength);
                            ShowReceiveDataWithDelegate($"{socket.RemoteEndPoint}发来消息:{str}");
                        }
                        ReceiveBufferDic[clientIP].Clear();
                    }
                }

                //接收下一条消息
                socket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, socket);
            }
            catch (SocketException)
            {
                string ip = socket.RemoteEndPoint.ToString();
                Close(ip);
            }
        }

        private void BtnSend_Click(object sender, EventArgs e)
        {
            if (CmbSocket.SelectedIndex < 0)
            {
                MessageBox.Show("请选择客户端IP");
                return;
            }
            string clientIP = CmbSocket.SelectedItem.ToString();
            Socket clientSocket = ClientSocketList[clientIP];
            byte[] data = Encoding.Default.GetBytes(TextSendData.Text.Trim());

            clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, (result) =>
            {
                Socket socket = (Socket)result.AsyncState;
                socket.EndSend(result);
            }, clientSocket);
            TextSendData.Clear();
        }

        private void ShowReceiveDataWithDelegate(string msgContent)
        {
            IAsyncResult result = ListReveiveData.BeginInvoke(new EventHandler(delegate
                {
                    ListReveiveData.Items.Add($"{ListReveiveData.Items.Count} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :{msgContent}");
                    ListReveiveData.SelectedIndex = ListReveiveData.Items.Count - 1;
                }));
            ListReveiveData.EndInvoke(result);
        }

        private void Close(string ip)
        {
            ShowReceiveDataWithDelegate($"客户端{ip}断开连接");
            ClientSocketList.Remove(ip);
            ReceiveBufferDic.Remove(ip);
            CmbSocket.BeginInvoke(new EventHandler(delegate
            {
                CmbSocket.Items.Remove(ip);
            }));
        }
    }
}

其他

namespace AsyncSocketServer
{
/// <summary>
/// Socket接收 数据类型
/// </summary>
public class ReceiveDataModel
{
    /// <summary>
    /// 数据类型 0 文本,1 文件
    /// </summary>
    public byte DataType { get; set; }

    /// <summary>
    /// 数据长度
    /// </summary>
    public int DataLength { get; set; }

    /// <summary>
    /// 数据
    /// </summary>
    public byte[] Content { get; set; }

    public bool IsComplete
    {
        get
        {
            if (DataLength == 0) return false;
            return DataLength == Content.Length;
        }
    }
}
}

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-20 18:45:47  更:2021-11-20 18:48:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:46:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码