zoukankan      html  css  js  c++  java
  • 解决Socket通信中,经常遇到的问题——数据粘包的两种方法

        数据粘包问题的出现,是因为在客户端/服务器端都会有一个比较大的数据缓冲区,来存放接收的数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。在收发数据频繁时,由于tcp传输消息的无边界,会导致客户端/服务器端不知道接收到的消息到底是第几条消息,因此,会导致类似一次性接收几条消息的情况,从而乱码。

        在每次发送消息之间,加入空循环,从而可以将消息隔离开来,但是这个方法会严重影响程序的运行效率。

        方法一:数据粘包问题的出现是因为缓冲区过大,因此采用发送/接收变长消息的方法,在发送/接收消息时,将消息的长度作为消息的一部分发送出去,从而接收方可以根据传来的长度信息,制定相应长度的缓冲区;

        方法二:将发送的每条消息的首尾都加上特殊标记符,前加"<"   后加">"。这里我采取的是先将要发送的所有消息,首尾加上特殊标记后,都先放在一个字符串string中,然后一次性的发送给接收方,接受之后,再根据标记符< >,将一条条消息择(zhái)出来。

        代码如下:

        发送消息端,即服务器端:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Net;
      6 using System.Net.Sockets;
      7 using System.Collections;
      8 using System.Diagnostics;
      9 
     10 namespace _07发送变长消息的方法
     11 {
     12     class Program
     13     {
     14         static void Main(string[] args)
     15         {
     16             #region 先将发送的消息带上首尾标记< > 再将要发送的所有消息,放在一个字符串中 然后一次性发送给客户端
     17             DateTime start = DateTime.Now;//记录程序的运行时间
     18             string str = null;
     19             double[] test = { 10.3, 15.2, 25.3, 13.338, 25.41, 0.99, 2017, 20000, 1, 3, 2, 5, 8, 90, 87, 33, 55, 99, 100 };
     20             IPEndPoint IPep2 = new IPEndPoint(IPAddress.Any, 127);
     21             Socket newsock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     22             newsock2.Bind(IPep2);
     23             newsock2.Listen(10);
     24             Console.WriteLine("等待新客户端的连接");
     25             Socket client2 = newsock2.Accept();//等待客户端的连接
     26             IPEndPoint clientep2 = (IPEndPoint)client2.RemoteEndPoint;
     27             Console.WriteLine("与{0}连接在{1}端口", clientep2.Address, clientep2.Port);
     28             string welcome2 = "welcome to the new server";
     29             byte[] data;
     30             str += SpecialMessage(welcome2);//将要发送的消息,都放在字符串str中
     31 
     32             //发送数组的长度信息 给字符串str
     33             str += SpecialMessage(test.Length.ToString());
     34 
     35             //用循环的形式 依次将数组中的元素给str
     36             string[] strvalue = new string[test.Length];
     37             for (int j = 0; j < test.Length; j++)
     38             {
     39                 strvalue[j] = test[j].ToString();//将实际速度集合转换为string数组
     40                 str += SpecialMessage(strvalue[j]);
     41             }
     42 
     43             //将所有发送的信息,都放在了str中,然后一次性的发送过去 注意都是有首尾标记的消息< >
     44             data = System.Text.Encoding.ASCII.GetBytes(str);
     45             client2.Send(data);
     46 
     47             DateTime end = DateTime.Now;
     48             TimeSpan span = end - start;
     49             double seconds = span.TotalSeconds;
     50             Console.WriteLine("程序运行的时间是{0}s", seconds);
     51 
     52 
     53             Console.WriteLine("传送数据成功!");
     54             client2.Close();//释放资源
     55             newsock2.Close();
     56             Console.ReadKey();
     57             #endregion
     58 
     59 
     60             #region 采用变长的消息 即发送时先告诉客户端 消息的长度是多少
     61             //DateTime start = DateTime.Now;
     62             //double[] test = { 10.3, 15.2, 25.3, 13.338, 25.41, 0.99, 2017, 20000, 1, 3, 2, 5, 8, 90, 87, 33, 55, 99, 100 };
     63             //IPEndPoint IPep2 = new IPEndPoint(IPAddress.Any, 127);
     64             //Socket newsock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     65             //newsock2.Bind(IPep2);
     66             //newsock2.Listen(10);
     67             //Console.WriteLine("等待新客户端的连接");
     68             //Socket client2 = newsock2.Accept();//等待客户端的连接
     69             //IPEndPoint clientep2 = (IPEndPoint)client2.RemoteEndPoint;
     70             //Console.WriteLine("与{0}连接在{1}端口", clientep2.Address, clientep2.Port);
     71             //string welcome2 = "welcome to the new server";
     72             //byte[] date = Encoding.ASCII.GetBytes(welcome2);//字符串转换为字节数组  传送给客户端
     73             //SendVarMessage(client2, date);
     74 
     75 
     76             ////发送数组的长度信息 给客户端
     77             //date = Encoding.ASCII.GetBytes(test.Length.ToString());
     78             //SendVarMessage(client2, date);
     79 
     80             ////用循环的形式 依次将数组中的元素发送给客户端
     81             //string[] strvalue = new string[test.Length];
     82             //for (int j = 0; j < test.Length; j++)
     83             //{
     84             //    strvalue[j] = test[j].ToString();//将实际速度集合转换为string数组
     85             //    date = Encoding.ASCII.GetBytes(strvalue[j]);//string数组中的每个元素一次转换为字节 发送给客户端
     86             //    SendVarMessage(client2, date);
     87             //}
     88 
     89             //DateTime end = DateTime.Now;
     90             //TimeSpan span = end - start;
     91             //double seconds = span.TotalSeconds;
     92             //Console.WriteLine("程序运行的时间是{0}s", seconds);
     93 
     94             //Console.WriteLine("传送数据成功!");
     95             //client2.Close();//释放资源
     96             //newsock2.Close();
     97             //Console.ReadKey();
     98             #endregion
     99         }
    100 
    101         /// <summary>
    102         /// 发送变长消息方法
    103         /// </summary>
    104         /// <param name="s"></param>
    105         /// <param name="msg"></param>
    106         /// <returns>不需要返回值</returns>
    107         private static void SendVarMessage(Socket s, byte[] msg)
    108         {
    109             int offset = 0;
    110             int sent;
    111             int size = msg.Length;
    112             int dataleft = size;
    113             byte[] msgsize = new byte[2];
    114 
    115             //将消息的尺寸从整型转换成可以发送的字节型
    116             //因为int型是占4个字节 所以msgsize是4个字节 后边是空字节
    117             msgsize = BitConverter.GetBytes(size);
    118 
    119             //发送消息的长度信息
    120             //之前总是乱码出错 客户端接收到的欢迎消息前两个字节是空 后边的两个字符er传送到第二次接收的字节数组中
    121             //因此将er字符转换为int出错  这是因为之前在Send代码中,是将msgsize整个字节数组发送给客户端 所以导致第3 4个空格也发送
    122             //导致发送的信息混乱 这两个空格使发送的信息都往后挪了两个位置  从而乱码
    123             sent = s.Send(msgsize, 0, 2, SocketFlags.None);
    124             while (dataleft > 0)
    125             {
    126                 int sent2 = s.Send(msg, offset, dataleft, SocketFlags.None);
    127                 //设置偏移量
    128                 offset += sent2;
    129                 dataleft -= sent2;
    130             }
    131         }
    132 
    133 
    134 
    135         /// <summary>
    136         /// 给发送的消息 加特殊结束标记 头尾分别加"<" ">"
    137         /// </summary>
    138         /// <param name="s"></param>
    139         /// <param name="msg"></param>
    140         private static string SpecialMessage(string str)
    141         {
    142             string strNew = "<";
    143             strNew += str;
    144             strNew += ">";
    145             return strNew;
    146         }
    147     }
    148 }

    接收消息端,即客户端:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Net;
      6 using System.Net.Sockets;
      7 using System.Collections;
      8 
      9 namespace _08接收变长消息的方法
     10 {
     11     class Program
     12     {
     13         static void Main(string[] args)
     14         {
     15             #region 接收带有首尾特殊标记符的消息
     16             DateTime start = DateTime.Now;
     17             byte[] data = new byte[1024];
     18             IPEndPoint IPep2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 127);
     19             Socket server2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     20             try
     21             {
     22                 server2.Connect(IPep2);
     23                 Console.WriteLine("连接成功");
     24             }
     25             catch (SocketException e)
     26             {
     27                 Console.WriteLine("连接服务器失败");
     28                 Console.WriteLine(e.ToString());
     29                 Console.ReadKey();
     30                 return;
     31             }
     32             finally
     33             { }
     34             //接收来自服务器的数据
     35             server2.Receive(data);
     36 
     37             //一次性接收了服务器端发来的数据 利用ReceiveSpecialMessage方法接收并根据特殊标记
     38             //将消息一条一条的放在字符串数组strs中
     39             string[] strs = ReceiveSpecialMessage(data);
     40             string stringData = strs[0];
     41             Console.WriteLine(stringData);//接收并显示欢迎消息    成功!
     42 
     43             string length = strs[1];
     44             Console.WriteLine("共接收到{0}个数据", length);
     45             int n = Convert.ToInt32(length);
     46 
     47             //依次接收来自服务器端传送的实际速度值 成功!
     48             string[] speed = new string[n];
     49             for (int i = 0; i < n; i++)
     50             {
     51                 speed[i] = strs[i + 2];
     52                 Console.WriteLine("第{0}次的实际速度是{1}", i + 1, speed[i]);
     53             }
     54 
     55             DateTime end = DateTime.Now;
     56             TimeSpan span = end - start;
     57             double seconds = span.TotalSeconds;
     58             Console.WriteLine("程序运行的时间是{0}s", seconds);
     59             server2.Shutdown(SocketShutdown.Both);
     60             server2.Close();
     61             Console.ReadKey();
     62             #endregion
     63 
     64             #region 接收变长的消息 无特殊标记符
     65             //DateTime start = DateTime.Now;
     66             //byte[] data = new byte[1024];
     67             //IPEndPoint IPep2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 127);
     68             //Socket server2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     69             //try
     70             //{
     71             //    server2.Connect(IPep2);
     72             //    Console.WriteLine("连接服务器成功");
     73             //}
     74             //catch (SocketException e)
     75             //{
     76             //    Console.WriteLine("连接服务器失败");
     77             //    Console.WriteLine(e.ToString());
     78             //    Console.ReadKey();
     79             //    return;
     80             //}
     81             //finally
     82             //{ }
     83             ////接收来自服务器的数据
     84             //data = ReceiveVarMessage(server2);
     85             //string stringData = Encoding.ASCII.GetString(data, 0, data.Length);
     86             //Console.WriteLine(stringData);//接收并显示欢迎消息    成功!
     87 
     88             //data = ReceiveVarMessage(server2);
     89             //string length = Encoding.ASCII.GetString(data);
     90             //Console.WriteLine("共接收到{0}个数据", length);
     91             //int n = Convert.ToInt32(length);
     92 
     93             ////依次接收来自服务器端传送的实际速度值 成功!
     94             //string[] speed = new string[n];
     95             //for (int i = 0; i < n; i++)
     96             //{
     97             //    data = ReceiveVarMessage(server2);
     98             //    speed[i] = Encoding.ASCII.GetString(data, 0, data.Length);
     99             //    Console.WriteLine("第{0}次的实际速度是{1}", i + 1, speed[i]);
    100             //}
    101 
    102             //DateTime end = DateTime.Now;
    103             //TimeSpan span = end - start;
    104             //double seconds = span.TotalSeconds;
    105             //Console.WriteLine("程序运行的时间是{0}s", seconds);
    106 
    107             //server2.Shutdown(SocketShutdown.Both);
    108             //server2.Close();
    109             //Console.ReadKey();
    110             #endregion
    111         }//Main函数
    112 
    113 
    114         /// <summary>
    115         /// 接收变长消息方法
    116         /// </summary>
    117         /// <param name="s"></param>
    118         /// <returns>接收到的信息</returns>
    119         private static byte[] ReceiveVarMessage(Socket s)//方法的返回值是字节数组 byte[] 存放的是接受到的信息
    120         {
    121             int offset = 0;
    122             int recv;
    123             byte[] msgsize = new byte[2];
    124 
    125             //接收2个字节大小的长度信息
    126             recv = s.Receive(msgsize, 0, 2, 0);
    127 
    128             //将字节数组的消息长度转换为整型
    129             int size = BitConverter.ToInt16(msgsize, 0);
    130             int dataleft = size;
    131             byte[] msg = new byte[size];
    132             while (dataleft > 0)
    133             {
    134                 //接收数据
    135                 recv = s.Receive(msg, offset, dataleft, 0);
    136                 if (recv == 0)
    137                 {
    138                     break;
    139                 }
    140                 offset += recv;
    141                 dataleft -= recv;
    142             }
    143             return msg;
    144         }
    145 
    146 
    147         /// <summary>
    148         /// 接收结尾带有特殊标记的消息   哈哈
    149         /// </summary>
    150         /// <param name="s"></param>
    151         /// <returns></returns>
    152         private static string[] ReceiveSpecialMessage(byte[] data)
    153         {
    154             string str = Encoding.ASCII.GetString(data);
    155             int i = 0;
    156             int j = 1;
    157             List<int> list_i = new List<int>();
    158             List<int> list_j = new List<int>();
    159             while (i < str.Length)  //找到特殊标记符 < 所在位置
    160             {
    161                 if (str[i].ToString() == "<")
    162                 {
    163                     list_i.Add(i);
    164                     i++;
    165                 }
    166                 else
    167                 {
    168                     i++;
    169                     continue;
    170                 }
    171             }
    172             while (j < str.Length)  //找到特殊标记符 > 所在的位置
    173             {
    174 
    175                 if (str[j].ToString() == ">")
    176                 {
    177                     list_j.Add(j);
    178                     j++;
    179                 }
    180                 else
    181                 {
    182                     j++;
    183                     continue;
    184                 }
    185             }
    186             string[] strs = new string[list_i.Count];
    187             for (int i1 = 0; i1 < strs.Length; i1++)
    188             {
    189                 strs[i1] = str.Substring(list_i[i1] + 1, list_j[i1] - list_i[i1] - 1); //截取 < > 之间的字符串,是我们要的消息
    190             }
    191             return strs;
    192         }
    193     }
    194 }

    经验证,两种方法都没有问题,并且其运行时间,也没有太大差别(在发送较小的数据量下)。

  • 相关阅读:
    后向边
    图的割点、桥和双连通分支的基本概念
    Hihocoder 1062 最近公共祖先1
    会场问题 差分解法
    POJ2976 01分数规划 普通题
    Hihocoder 1049
    hihocoder 1050树中最长路
    Hihocoder 1055
    POJ1463
    C语言|博课作业02
  • 原文地址:https://www.cnblogs.com/1987-05-04/p/6727922.html
Copyright © 2011-2022 走看看