粘包和分包:

Socket通信时会对发送的字节数据进行分包和粘包处理,属于一种Socket内部的优化机制。

1.粘包:当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小字节数据打包成  一个整包进行发送,降低内存的消耗。

2.分包:当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,降低内存和性能的消耗。

解决方案:将发送的字节数据的长度与字节数据拼接后发送,每次读取字节数据时先读取其长度,然后再对接收到的字节数组进行拆分和拼接处理。

具体实现代码如下:(环境:Visual Studio 2017 C#.NET控制台程序)

服务器端代码:

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

namespace PhotoTransmission
{
    class PhotoTransController
    {
        private Message m_msg = new Message();

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

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

            //Process pro = new Process();
            //ProcessStartInfo proStartInfo = new ProcessStartInfo("PhotoTransmission.exe");
            //proStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            //pro.StartInfo = proStartInfo;
            //pro.Start();

            Console.ReadKey();
        }

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

            //向客户端返回消息
            SendClientData(client);

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

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

        private 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;
                }

                m_msg.ReadMessage(count);   //读取出第一条字节数据并删除

                DataProcessing(m_msg.Type);

                client.BeginReceive(m_msg.Data, m_msg.StartIndex, m_msg.RemainSize, SocketFlags.None, AsyncReceiveCallBack, client);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);

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

        }

        /// <summary>
        /// 根据type的不同进行不同的数据处理
        /// </summary>
        /// <param name="_type"></param>
        private void DataProcessing(int _type)
        {
            switch(_type)
            {
                case 0:

                    break;
                case 1:

                    break;
                case 2:

                    break;
            }
        }

        /// <summary>
        /// 向客户端返回数据
        /// </summary>
        private void SendClientData(Socket _client)
        {
            string msgStr = "努力!!奋斗!!!";
            byte[] data = System.Text.Encoding.UTF8.GetBytes(msgStr);
            _client.Send(data);
        }
    }
}

Message类代码:

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

namespace PhotoTransmission
{
    class Message
    {
        private byte[] m_data = new byte[10240];
        private int m_startIndex = 0;
        private int m_count = 0;     //为一条发送的字节数据的长度
        private int m_type = 0;  //表示该数据体是什么类型的数据体,0表示默认为byte数组

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

        /// <summary>
        /// 将字节数据与字节长度拼接
        /// </summary>
        /// <param name="_type">表示哪种数据类型</param>
        /// <param name="_msg">数据体(json等等)</param>
        /// <returns></returns>
        public byte[] GetBytes(int _type, string _msg)
        {
            byte[] msg = System.Text.Encoding.UTF8.GetBytes(_msg);
            int length = msg.Length;    //字节长度
            m_data = new byte[length];   //根据_msg数据的长度创建对应长度的byte数组
            byte[] typeByte = BitConverter.GetBytes(_type);
            byte[] headByte = BitConverter.GetBytes(length).Concat(typeByte).ToArray();
            byte[] newMsg = headByte.Concat(msg).ToArray();   //将字节长度与msg拼接
            return newMsg;
        }

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

            UpdataMessage();

            m_type = BitConverter.ToInt32(m_data, 4);
        }

        /// <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 + 8, m_data, 0, m_count);
                if (m_count < count + 4) break;
            }

            m_startIndex = m_count;
        }
    }
}

 客户端代码如下: 

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 TCPClientController
    {
        private void TCPClientAsync()
        {
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream,     ProtocolType.Tcp);

            try
            {
                client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1017));
            }
            catch (Exception e)
            {
                Debug.Log(e);
                return;
            }

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

            byte[] data = GetBytes(56,"客户端请求数据");
            client.Send(data);

            client.Close();
        }

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