如何接收和发送结构体 ==杂 //如果用c的话,结构体里的数据也可以理解为一个uint8_t数组,所以直接用memcpy考到包里应该就可以了。。收到以后在memcpy考到结构体里 如果是面向对象语言的话,可能有一些序列化的方法,不过这个是用来传object的,变通一下就可以传你想传的数据了 //如果在不同架构主机间传递时,由于涉及字节序问题,相同的字节流,对应相同的内存布局(memcpy可以保证),但却引出了不同的变量类型的内存解释~ //结构体存在内存对齐和CPU不兼容的问题,可以避免 //最好是自己格式化数据后再发送 否则会因对齐问题出错 //用send_dat((char*)(&shuju), sizeof(shuju))发送的时候注意结构体的字节对齐方式,sizeof(shuju)可能比你实际要发送的字节数多. //如果是非8位单片机,要注意对齐方式,否则有可能发送字节数出错。 //实际上我在做通信程序的时候是不会这么干的 考虑到通讯双方的体系结构以及编译规则 LZ最好是制定一个通用的协议来进行通讯 ==讨论 socket如何发送结构体? 楼主: 我在客户端上把USERINFO结构体变量强制转换为char指针,再发送到服务端,然后服务端用memcpy方法把接收到的数据再复制到一个USERINFO类型的的结构体变量中,但却是空的。求正确的办法,谢谢了 以下为一些回答: // 一般来说,先要将结构体转换成字符串流再传输 到了服务端后再将字符流转成结构体 ============= 你这样做也是可以的 但要注意 1 服务端和客户端结构体定义要严格一致(包括使用的对齐值等) 2 不能有指针 3 传输时注意启始和结束 // 将结构体转换成char*然后再发送。注意服务端的结构体和客户端结构体要严格一致。 定长的简单,直接转成char*搞定。 如果结构体是变长的,要用到一些trick。 最简单的就是零长度的数组。 struct test { int aint; char buff[0]; } 分配内存的时候 test pTest = new(sizeof(test) + n); 然后发送的长度就是 sizeof(test) + n 然后转换成char *发送过去就是了。 // struct test { int aint; char buff[10]; } test buf;//定义结构体变量 int len=sizeof(test); send(s,(char*)&buf,len,0)//这样就OK了, 不管你要发送什么都东西,只是是连续内存区域,都可以强制转换成char*发送出去 // 给出USERINFO的结构具体内容. 该结构中是否包含指针. 转换为char*后,其内容是什么长度有多大. 接收方接收到的char*内容是什么,有多大.和发送时的内容是否一致. 接收后,强制转换为USERINFO时.是否与预期一致 USERINFO若包含指针.则指针自身的值是否与预期一致(指针所指的位置肯定不是原来的位置). 如果都一致的话,则说明没有问题.但有可能与你预期的效果不一致(指针自身值是一致,但指向的内容肯定不能是同一个地方). //关键问题: 1) 你的代码没有逻辑错误 2) 你的结构体里用的是不是只有C++内置类型,而没有自定义类型,如:string,string里面是指针,数据在堆上,这类需要自己序列化,然后再传输,收到后反序列化 3) 如果用的都是内置类型,要注意服务器和客户端的CPU类型,字节顺序是否一样,如果一个是大端模式,一个是小端模式,收到的数据肯定还是不对 ==C#结构体和字节数组的转换 在写C#TCP通信程序时,发送数据时,只能发送byte数组,处理起来比较麻烦不说,如果是和VC6.0等写的程序通信的话,很多的都是传送结构体,在VC6.0中可以很方便的把一个char[]数组转换为一个结构体,而在C#却不能直接把byte数组转换为结构体,要在C#中发送结构体,可以按以下方法实现: (1)定义结构体: //命名空间 using System.Runtime.InteropServices; //注意这个属性不能少 [StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)] struct TestStruct { public int c; //字符串,SizeConst为字符串的最大长度 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string str;ITPUB个人空间9H] //int数组,SizeConst表示数组的个数,在转换成 //byte数组前必须先初始化数组,再使用,初始化 //的数组长度必须和SizeConst一致,例test = new int[6]; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] test; } (2)结构体转byte数组: //// ITPUB个人空间hUH b? X /// 结构体转byte数组 /// /// 要转换的结构体 /// 转换后的byte数组 public static byte[] StructToBytes(object structObj) { //得到结构体的大小 int size = Marshal.SizeOf(structObj) //创建byte数组 byte[] bytes = new byte[size]; //分配结构体大小的内存空间 IntPtr structPtr = Marshal.AllocHGlobal(size); //将结构体拷到分配好的内存空间 Marshal.StructureToPtr(structObj, structPtr, false); //从内存空间拷到byte数组 Marshal.Copy(structPtr, bytes, 0, size); //释放内存空间 Marshal.FreeHGlobal(structPtr); //返回byte数组 return bytes; } (3)byte数组转结构体: /// /// byte数组转结构体 /// /// byte数组 /// 结构体类型 /// 转换后的结构体 public static object BytesToStuct(byte[] bytes,Type type) { //得到结构体的大小 int size = Marshal.SizeOf(type); //byte数组长度小于结构体的大小 if (size > bytes.Length) { //返回空 return null; } //分配结构体大小的内存空间 IntPtr structPtr = Marshal.AllocHGlobal(size); //将byte数组拷到分配好的内存空间 Marshal.Copy(bytes,0,structPtr,size); //将内存空间转换为目标结构体 object bj = Marshal.PtrToStructure(structPtr, type); //释放内存空间 Marshal.FreeHGlobal(structPtr); //返回结构体 return obj; } ==返璞归真,网络传输——结构体还是序列化? 虽然,网络编程里面的数据传送推荐用序列化,但我不用,还是选择结构体(返璞归真),有以下几点理由: 1.跨平台问题: 序列化确实可以很好的跨语言平台,可大多数网络游戏不需要跨语言平台 2.别以为有了序列化就不需要结构体 表面上序列化代码量小,按顺序读和写char int short LPCSTR … 就好,逻辑对象写不写都无所谓,那就是大错而特错了 待序列化的对象发送前的结构还是不可省略的序列化的过程就是 object->(按一定顺序拆分)write->bytes->(按拆分顺序组装)read->object的过程 其实object还是不能省略,很多人写网络程序不注重逻辑对象结构,收到的一堆bytes按一定顺序读和写就完事了,这样虽然灵活,但缺乏结构,容易造成混乱,调试起来是灾难 所以结构体(或类)还是省略不了的,所以:别以为有了序列化,就不需要结构体了。 3.结构体存在内存对齐和CPU不兼容的问题,可以避免 的确结构体是有内存对齐的问题,存在兼容性问题,我可以选择pack(1)把内存对齐给关闭掉,避免兼容性问题,既然选择了iocp就不打算跨平台了,可以避免结构体平台兼容的问题 4.结构体调试起来方便很多,减少内存拷贝,效率高 不用序列化可write和read的过程就不需要过多考虑(少写太多代码了),read write 就好像现代社会每个人每天都要穿衣服和脱衣服一样,原始社会需要吗?其实人类进化到原始裸奔状态才是最爽快的:) 但还是要说句公道话:有人说序列化编码解码read write 需要耗费资源, 诚然这个过程基本等于赋值和内存拷贝,那点效率损失主要还在内存拷贝上,这点效率损失很小,不能作为序列化的缺点,当然如果涉及到数据加密那将是另外一个话题 5.结构体貌似呆板,发送数据限制多,发送变长数据就不方便,数据组织起来也不灵活 我想这是很多人抛弃结构体,选择用序列化方式发送和接受数据的一个很重要的原因 但:其实对于变长结构(子结构也是变长)的问题,用结构体来实现的确很麻烦,但并不代表不能实现 我已经实现了,而且读和写变长子结构体嵌套任意多层都不成问题,可以存储复杂变长的数据结构, 数据就如同能自动序列化一样方便,这个应该是技术难点,但细心去做是可以实现的 6.关于结构体指针 游戏里面要发送的数据内存事先分配好的,不存在指针,深度复制更不用考虑,所以内存拷贝不会出错 如果用到指针即使用序列化来实现也会面临同样的问题也占不了多少便宜,由于C++这们语言的特点, 不象java那样有个标准实现,对于序列化本身没有一个统一的标准,所以可想而知,有人说:boost有它的序列化的实现 其实那个实现不见得就合适你自己,如果真要做序列化,编码和解码的仿照那个过程自己写才最为牢靠, 哪些指针对应的内存需要序列化那些不需要序列化,是个逻辑结构,需要自己说了算才好(好像扯远了点) 说回游戏数据,既然不用需要他用到指针,结构体用来发送数据也没问题的 7 平台扩充问题 退一万步的说:换了语言就基本上换了客户端,客户端的数据组织形式都要重写 实在不行还可以考虑用xml json 编码等等一些跨平台的解决方案,现在所写的结构体是可以用来做数据接收的,只是发送的不再是结构体而已 8.综上所述 如果需要跨语言平台,不用序列化(二进制流或xml, json文本等等)根本无法实现 序列化的优点还是非常多的.如果主要是跨平台和语言自定义读写规则,根据需要读写对象的某一部分数据, 空间浪费少,不存在内存对齐问题等诸多优点,缺点就是拐弯抹角,代码量大,调试不方便 权衡了良久 数据如果能组织的合理,而且没有跨语言平台的要求,用结构体也未尝不可,毕竟数据发送直来直去还是方便些,减少内存拷贝,效率也高了很多 特别是调试起来容易太多了,衡量利弊我还是放弃了序列化,选择了原始的结构体,只是难在数据的组织(好在基本已经克服了) 我知道:序列化很好很强大,很多网络程序高手根本不屑于直接发一个逻辑结构体,用这种方式就好象是旁门左道,狗肉上不了大雅之堂一样,狗肉还是很多人喜欢吃的嘛,:)。 我还是返璞归真选择了结构体 一句话:物尽其用,用的恰当,够用就好。 如果有什么不对,敬请拍砖,莫要客气