socket如何组包和解包发送

glj_sky_2012 2015-09-30 10:20:17
如题:采用面向稳定连接的ASCII流(STREM)方式,具体数据包格式如下所示:
字段名 长度(byte) 描述
头标志(0x02) 1
业务数据包长度 4
业务数据包 N
尾标志(0x03) 1
校验码 1 从包的第一个字节0x02开始,逐字节进行异或到包的结尾0x03,结果为最后校验字节。

各位大虾,帮忙说明一下该包的数据如何组包,发送给服务器端,本人对socket连接理解的不是很深,对于该包体的如何封装和解包
...全文
1035 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
於黾 2015-09-30
  • 打赏
  • 举报
回复
解包的时候一样,从第0个字节开始到倒数第二个字节循环异或,跟最后一个字节比较,如果相同,验证成功
於黾 2015-09-30
  • 打赏
  • 举报
回复
引用 2 楼 glj_sky_2012 的回复:
[quote=引用 1 楼 xian_wwq 的回复:] 对于socket来说,它只认识byte[],socket负责把byte[]从源搬运到目标地 至于byte[]怎么解析,完全是业务层的事情;byte[] 如何构造、如何解析与socket传输无关
恩 我就是不太明白提交的数据如何按照规则“从包的第一个字节0x02开始,逐字节进行异或到包的结尾0x03,结果为最后校验字节。”解析。 可以给一个简单的数据的byte[]转换以及数据包的解析吗,本人实在不太明白上面的规则的含义。[/quote] 就是加了一个字节的异或校验罢了,这个正常用于串口通信中,防止数据出错了但是接收方无法验证数据的合法性.在TCP传输中,底层已经进行了验证,重发,根本不存在数据发送出错的问题. 你要做验证,不过就是循环一下,两两做异或,最后结果存入数据之后的字节里就行了
glj_sky_2012 2015-09-30
  • 打赏
  • 举报
回复
没有头标志和尾标志的的数据,直接发送和解析就可以了如下: client.Send(Encoding.ASCII.GetBytes(input)); //得到实际收到的字节总数 int rec = client.Receive(buf); Console.WriteLine(Encoding.ASCII.GetString(buf, 0, rec)); 那么如何按照下面的规则封包和解包呢 头标志(0x02) 1 业务数据包长度 4 业务数据包 N 尾标志(0x03) 1 校验码 1 从包的第一个字节0x02开始,逐字节进行异或到包的结尾0x03,结果为最后校验字节。
glj_sky_2012 2015-09-30
  • 打赏
  • 举报
回复
引用 1 楼 xian_wwq 的回复:
对于socket来说,它只认识byte[],socket负责把byte[]从源搬运到目标地 至于byte[]怎么解析,完全是业务层的事情;byte[] 如何构造、如何解析与socket传输无关
恩 我就是不太明白提交的数据如何按照规则“从包的第一个字节0x02开始,逐字节进行异或到包的结尾0x03,结果为最后校验字节。”解析。 可以给一个简单的数据的byte[]转换以及数据包的解析吗,本人实在不太明白上面的规则的含义。
xian_wwq 2015-09-30
  • 打赏
  • 举报
回复
对于socket来说,它只认识byte[],socket负责把byte[]从源搬运到目标地 至于byte[]怎么解析,完全是业务层的事情;byte[] 如何构造、如何解析与socket传输无关
glj_sky_2012 2015-09-30
  • 打赏
  • 举报
回复
谢谢 xian_wwq \ Z65443344Forty2 各位大虾,一直做web开发对sokect网络编程这块没有深入研究,万分感激啊。。。。
Forty2 2015-09-30
  • 打赏
  • 举报
回复
具体实现方法可以有很多,一种比较直观,也比较健壮的方式就是用BinaryReader(优点在于分包很容易)。 比如下例,在Stream基础上组包和解包(方便单元测试)。发送和接收时,换入TcpClient.GetStream就可以了:
class Program
{
    static void Main(string[] args)
    {
        TcpClient client = ...;
        byte[] data1 = ...;
            
        // 发送
        Write(client.GetStream(), data1);
        // 接受
        byte[] data2 = Read(client.GetStream());
    }

    static void 单元测试()
    {
        using (MemoryStream stream = new MemoryStream())
        {
            Write(stream, Encoding.UTF8.GetBytes("nihao"));
            Write(stream, new byte[] { 8, 8, 8 });
            Write(stream, Encoding.UTF8.GetBytes("人有多大胆,地有多大产!"));

            stream.Position = 0;
            string msg1 = Encoding.UTF8.GetString(Read(stream));  // nihao
            byte[] b888 = Read(stream);                           // {8,8,8}
            string msg2 = Encoding.UTF8.GetString(Read(stream));  // 人有多大胆,地有多大产!
        }
    }

    static void Write(Stream stream, byte[] data)
    {
        using (var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen:true))
        {
            byte checksum = 0x2;
            checksum ^= BitConverter.GetBytes(data.Length).Aggregate((s,b) => (byte)(s ^ b));
            checksum ^= data.Aggregate((s, b) => (byte)(s ^ b));
            checksum ^= 0x3;

            writer.Write((byte)0x2);   // 头标志
            writer.Write(data.Length); // 业务数据包长度
            writer.Write(data);        // 业务数据包
            writer.Write((byte)0x3);   // 尾标志
            writer.Write(checksum);    // 校验码
            writer.Flush();
        }
    }
        
    static byte[] Read(Stream stream)
    {
        using (var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true))
        {
            if (reader.ReadByte() != 0x2) throw new InvalidOperationException("不正确头标志");
            int dataLength = reader.ReadInt32();
            byte[] data = reader.ReadBytes(dataLength);
            if (reader.ReadByte() != 0x3) throw new InvalidOperationException("不正确尾标志");

            byte checksum = 0x2;
            checksum ^= BitConverter.GetBytes(data.Length).Aggregate((s, b) => (byte)(s ^ b));
            checksum ^= data.Aggregate((s, b) => (byte)(s ^ b));
            checksum ^= 0x3;

            if (reader.ReadByte() != checksum) throw new InvalidOperationException("不正确校验码");
            return data;
        }
    }
}
xian_wwq 2015-09-30
  • 打赏
  • 举报
回复
引用 2 楼 glj_sky_2012 的回复:
[quote=引用 1 楼 xian_wwq 的回复:] 对于socket来说,它只认识byte[],socket负责把byte[]从源搬运到目标地 至于byte[]怎么解析,完全是业务层的事情;byte[] 如何构造、如何解析与socket传输无关
恩 我就是不太明白提交的数据如何按照规则“从包的第一个字节0x02开始,逐字节进行异或到包的结尾0x03,结果为最后校验字节。”解析。 可以给一个简单的数据的byte[]转换以及数据包的解析吗,本人实在不太明白上面的规则的含义。[/quote] 处理思路不复杂,逐次从byte[]中读取数据,进行验证就可以。 以验证包头为例

       private byte ReadByte(byte[] inputData, int offset)
        {
            return ((byte)inputData[offset]);
        }     

        private int ReadInt32(byte[] inputData, int offset)
        {
            int num = 4 + offset;

            return (((inputData[num - 4] << 24 | (inputData[num - 3] << 16)) | (inputData[num - 2] << 0x8)) | (inputData[num - 1]));
        }    


        /// <summary>
        /// 检查接收的byte[]是否是合法包
    /// </summary>
        /// <returns></returns>
        private int CheckReceivedBuffer(byte[] dataBuffer)
        {
            int nFlag = -1;

            if (dataBuffer != null && dataBuffer.Length > 0)
            {
                //接收到的真实长度
         int readBytesLen = dataBuffer.Length;
                //读取包头,验证标识
        byte headFlag = ReadByte(dataBuffer, 0);  //头标志
        if( headFlag  != 0x02)
                {
                   return -1;
                }
                int packetLen = ReadInt32(dataBuffer, 1);
                
                //如果包头数据正常
        if(readBytesLen >= packetLen + 7)
                {
                   //parse data    
                
                   nFlag = 0;      
                }
                
            }
            return nFlag;
        } 

st_asio_wrapper是一组类,功能是对boost.asio的装(调试环境:boost-1.51.0),目的是简化boost.asio开发; 其特点是效率高、跨平台、完全异步,当然这是从boost.asio继承而来; 自动重连,数据透明传输,自动解决分问题(你可以像udp一样使用它); 注:只支持tcp协议; 教程:http://blog.csdn.net/yang79tao/article/details/7724514 1.1版更新内容: 增加了自定义数据模式的支持,可用于st_asio_wrapper server与其它客户端的通信、或者st_asio_wrapper client与其它服务端的通信;当然,两端都是st_asio_wrapper的话,就用透明传输即可(1.0版已经支持了)。 1.2版更新内容: 修复BUG:当stop_service之后,再start_service时,client_base内部某些成员变量可能没有得到复位; 服务端增加修改监听地址功能,当然仍然要在start_service之前调用set_server_addr函数。 1.3版更新内容: 增加自定义消息格式的发送,这个本来是在1.1版本实现的,结果我漏掉了,只实现了自定义消息格式的接收。 1.4版更新内容: 将打与解器从client_base分离出来,以简化这个日益复杂的基类; 可以在运行时修改打器。 1.5版更新内容: 增加ipv6支持,默认是ipv4,服务端和客户端都通过设置一个ipv6的地址来开启这个功能; 增加了一些服务端helper函数,小改了一下客户端set_server_addr函数签名(调换了两个参数的位置以保持和服务端一样)。 1.6版更新内容: 增加了接收消息缓存(改动较大,on_msg的语义有所变化,请看开发教程第三篇)。 1.7版更新内容: 修复vc2010下编译错误; 修复默认解器BUG(同时修改解器接口); 修复log输出BUG; 更好的装了服务端类库,现在服务端可以像客户端一样简单的使用了(完全不用继承或者重写虚函数,申请一个对象即可); 结构大调整,类名大调整,请参看开发教程第一篇。 1.8版更新内容: 增加健壮性和稳定性; 退出服务更新优雅。 1.9版更新内容: 提高代码通用性; 可以指定服务端同时投递多少个async_accept; 修复BUG,此BUG可能造成数据发送不完全。 2.0版更新内容: 服务端增加对象池功能; 优化美化代码; 更规范化接口签名。 2.1版更新内容: 修复BUG,此BUG会造成st_client在stop_service之后,仍然可能尝试重新连接服务器; 在消息发送的时候,增加了一个参数can_overflow,用于确定是否在缓存满的时候返回失败,这在某些不能阻塞等待直到缓存可用的场合非常有用,比如on_msg; 当消息接收缓存满的时候,st_socket现在可以保证消息不丢失,之前的行为是调用on_recv_buffer_oveflow之后,丢弃消息; 更规范化接口签名; 更多更新请看st_asio_wrapper_socket.h,所有更新都会罗列在这个头文件的开头处,另外st_asio_wrapper_server.h的开头部分注释也很重要,有工作原理相关的说明。
st_asio_wrapper是一组类,功能是对boost.asio的装(调试环境:boost-1.51.0),目的是简化boost.asio开发; 其特点是效率高、跨平台、完全异步,当然这是从boost.asio继承而来; 自动重连,数据透明传输,自动解决分问题(你可以像udp一样使用它); 注:只支持tcp协议; 教程:http://blog.csdn.net/yang79tao/article/details/7724514 1.1版更新内容: 增加了自定义数据模式的支持,可用于st_asio_wrapper server与其它客户端的通信、或者st_asio_wrapper client与其它服务端的通信;当然,两端都是st_asio_wrapper的话,就用透明传输即可(1.0版已经支持了)。 1.2版更新内容: 修复BUG:当stop_service之后,再start_service时,client_base内部某些成员变量可能没有得到复位; 服务端增加修改监听地址功能,当然仍然要在start_service之前调用set_server_addr函数。 1.3版更新内容: 增加自定义消息格式的发送,这个本来是在1.1版本实现的,结果我漏掉了,只实现了自定义消息格式的接收。 1.4版更新内容: 将打与解器从client_base分离出来,以简化这个日益复杂的基类; 可以在运行时修改打器。 1.5版更新内容: 增加ipv6支持,默认是ipv4,服务端和客户端都通过设置一个ipv6的地址来开启这个功能; 增加了一些服务端helper函数,小改了一下客户端set_server_addr函数签名(调换了两个参数的位置以保持和服务端一样)。 1.6版更新内容: 增加了接收消息缓存(改动较大,on_msg的语义有所变化,请看开发教程第三篇)。 1.7版更新内容: 修复vc2010下编译错误; 修复默认解器BUG(同时修改解器接口); 修复log输出BUG; 更好的装了服务端类库,现在服务端可以像客户端一样简单的使用了(完全不用继承或者重写虚函数,申请一个对象即可); 结构大调整,类名大调整,请参看开发教程第一篇。 1.8版更新内容: 增加健壮性和稳定性; 退出服务更新优雅。 1.9版更新内容: 提高代码通用性; 可以指定服务端同时投递多少个async_accept; 修复BUG,此BUG可能造成数据发送不完全。 2.0版更新内容: 服务端增加对象池功能; 优化美化代码; 更规范化接口签名。 2.1版更新内容: 修复BUG,此BUG会造成st_client在stop_service之后,仍然可能尝试重新连接服务器; 在消息发送的时候,增加了一个参数can_overflow,用于确定是否在缓存满的时候返回失败,这在某些不能阻塞等待直到缓存可用的场合非常有用,比如on_msg; 当消息接收缓存满的时候,st_socket现在可以保证消息不丢失,之前的行为是调用on_recv_buffer_oveflow之后,丢弃消息; 更规范化接口签名; 更多更新请看st_asio_wrapper_socket.h,所有更新都会罗列在这个头文件的开头处,另外st_asio_wrapper_server.h的开头部分注释也很重要,有工作原理相关的说明。 2.2版更新内容: 增加了一个demo——文件传输服务端及客户端,它几乎可以当成软件来使用,多线程且支持分块传送; 增加了timer功能,考虑到st_server已经用上了timer,加之做文件传输服务器的时候,也用到了timer,所以干脆把timer抽象出来,其设计原理及使用跟MFC的timer差不多。timer已经被st_socket和st_server继承。用的时候,调用set_timer开始timer,重写on_timer虚函数等待被调用即可; 进一步优化消息接收缓存,通过宏,让优化发生于编译阶段,参看FORCE_TO_USE_MSG_RECV_BUFFER宏以了解更多。demo里面有使用案例(asio_server,asio_client,file_server,file_client,performance/asio_client); 增加了所有的宏都可以在外面修改的功能,在include类库头文件之前,可以定义你想要的宏,st_asio_wrapper不会修改它们,除非它们没有被定义,此时使用默认值,这也是st_asio_wrapper之前设计不好的地方,具体请参看使用教程。

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧