Socket处理粘包和分包问题(TCP异步)

csharp

浏览数:157

2019-1-7

片段 1片段 2片段 3


服务器端代码:

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

namespace TCPServer
{
    class Program
    {
        static Message msg = new Message();

        static void Main(string[] args)
        {
            StartServerAsync();
        }

        /// <summary>
        /// 异步
        /// </summary>
        static void StartServerAsync()
        {
            //开启服务器
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iPAddress = IPAddress.Parse("192.168.1.108");
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 50000);
            serverSocket.Bind(iPEndPoint);   //绑定ip和端口号
            serverSocket.Listen(0);   //服务器端的监听(0代表不限个数)

            //Socket client = serverSocket.Accept();  //接收客户端链接,当没有接收到任何客户端链接时中断程序

            //异步接收客户端链接并发送消息
            serverSocket.BeginAccept(AsyncAcceptCallBack, serverSocket);

            Console.ReadKey();
        }

        static void AsyncAcceptCallBack(IAsyncResult ar)
        {
            Socket serverSocket = ar.AsyncState as Socket;
            Socket client = serverSocket.EndAccept(ar);

            //向客户端发送消息
            string msgStr = "努力!!奋斗!!!";
            byte[] data = System.Text.Encoding.UTF8.GetBytes(msgStr);
            client.Send(data);

            //异步接收消息(回调函数)
            client.BeginReceive(msg.Data, msg.StartIndex, msg.Data.Length, SocketFlags.None, AsyncReceiveCallBack, client);
            //client.BeginReceive(msg.Data, 0, 1024, SocketFlags.None, AsyncReceiveCallBack, client);

            //继续等待下一个客户端连接(回调函数)
            serverSocket.BeginAccept(AsyncAcceptCallBack, serverSocket);
        }

        static void AsyncReceiveCallBack(IAsyncResult ar)
        {
            Socket client = null;
            try
            {
                client = ar.AsyncState as Socket;
                int count = client.EndReceive(ar);   //异步时使用EndReceive接收

                if (count == 0)   //服务器不会接收空消息
                {
                    client.Close();
                    return;
                }

                msg.ReadMessage(count);   //读取出第一条字节数据并删除
                
                client.BeginReceive(msg.Data, msg.StartIndex, msg.RemainSize, SocketFlags.None, AsyncReceiveCallBack, client);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);

                if(client != null) client.Close();
            }

        }
    }
}


客户端代码:

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

namespace TCPClient
{
    class Program
    {
        static void Main(string[] args)
        {
            TCPClientAsync();
            Console.ReadKey();
        }

        static void TCPClientAsync()
        {
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                client.Connect(new IPEndPoint(IPAddress.Parse("192.168.1.108"), 50000));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return;
            }

            //从服务器接收消息
            byte[] buffer = new byte[1024];
            int size = client.Receive(buffer);
            string msgReceive = System.Text.Encoding.UTF8.GetString(buffer, 0, size);
            Console.Write(msgReceive);

            //向服务器发送消息
            //while (true)
            //{
            //    string msg = Console.ReadLine();

            //    if (msg == "e")
            //    {
            //        client.Close();
            //        return;
            //    }

            //    Console.Write(msg);
            //    byte[] data = GetBytes(msg);
            //    client.Send(data);
            //}

            for (int i = 0; i < 1000; i++)
            {
                byte[] data = GetBytes(i.ToString() + "身开始登记卡");
                client.Send(data);
            }

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

        /// <summary>
        /// 将字节数据与字节长度拼接
        /// </summary>
        /// <param name="_msg"></param>
        /// <returns></returns>
        public static byte[] GetBytes(string _msg)
        {
            byte[] msg = System.Text.Encoding.UTF8.GetBytes(_msg);
            int length = msg.Length;    //字节长度
            byte[] newMsg = BitConverter.GetBytes(length).Concat(msg).ToArray();   //将字节长度与msg拼接
            return newMsg;
        }
    }
}


Message类:

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

namespace TCPClient
{
    class Message
    {
        /* Socket通信时内部会对消息进行分包和粘包处理,一种优化内存机制
         * 分包:当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,降低内存和性能的消耗
         * 粘包:当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小
         *       字节数据打包成一个整包进行发送,降低内存的消耗
         * 
         * 解决方案:将发送的字节数据的长度与字节数据拼接后发送,每次读取字节数据时先读取其长度,然后再对接收到的字节数组进行拆分和拼接处理
         */

        private byte[] m_data = new byte[10240];
        private int m_startIndex = 0;
        private int m_count = 0;     //为一条发送的字节数据的长度

        public int Count { get { return m_count; } }
        public byte[] Data { get { return m_data; } }
        public int StartIndex { get { return m_startIndex; } }
        public int RemainSize { get { return m_data.Length - m_count; } }

        /// <summary>
        /// 将字节数据与字节长度拼接
        /// </summary>
        /// <param name="_msg"></param>
        /// <returns></returns>
        public static byte[] GetBytes(string _msg)
        {
            byte[] msg = System.Text.Encoding.UTF8.GetBytes(_msg);
            int length = msg.Length;    //字节长度
            byte[] newMsg = BitConverter.GetBytes(length).Concat(msg).ToArray();   //将字节长度与msg拼接
            return newMsg;
        }

        /// <summary>
        /// 解析字节数据
        /// </summary>
        /// <returns></returns>
        public void ReadMessage(int _count)
        {
            m_startIndex = 4;
            m_count += _count;

            UpdataMessage();
        }

        /// <summary>
        /// 读取完成字节数据后删除第一条字节数据
        /// </summary>
        private void UpdataMessage()
        {
            while (true)
            {
                int count = BitConverter.ToInt32(m_data, 0);
                Console.WriteLine(System.Text.Encoding.UTF8.GetString(m_data, m_startIndex, count));
                m_count -= count + 4;
                Array.Copy(m_data, count + 4, m_data, 0, m_count);
                if (m_count < count + 4) break;
            }

            m_startIndex = m_count;
        }
    }
}