http://www.cnblogs.com/my_life/articles/4943353.html
http://www.cppblog.com/tonykee/archive/2008/02/17/42829.aspx
http://www.cnblogs.com/foohack/p/4718320.html
网络数据传输,可以直接发送字符串,但不能直接发送一个结构体。
网络上传输数据,因为发送端和接收端,通常不能保证是两边是相同的编程语言,就算都是使用C语言,CPU字节序,或者CPU位数不一样,直接将结构体的数据整理成流发送过去,数据排序或者长度会跟你想象的不一样。解释起来比较费篇幅。
这里说下通常的解决办法:
- 约定一个协议,协议规定好数据流中每个字节的含义。
- 发送端要保证按照协议要求组装好数据流。
- 接收端按照协议规定读取出里面的数据(解析)。
http://www.cnblogs.com/kaijia9/p/3394953.html
UDP传输模式是数据报,TCP传输模式为字节流,字节流与数据报区别在于有边界与无边界。例如:TCP客户端发送了三个数据包,开的缓存足够大服务端一次可接收三个数据包的数据,这就是无边界。UDP客户端发送了三个数据包,就算开的缓存足够大服务端一次也只能接收一个数据包,这就是有边界。
还有就是协议会维护源地址和目的地址直到协议要求断开连接,这就决定了TCP不能进行广播和多播。
直接发送结构体的方式【是不对的】:
· char send_buf[1024] = "tony 2000 ";
· memset(send_buf,0,1024);
· struct msg
· {
· int cmd;
· int sendID;
· int recvID;
· string name;
· int number;
· };
· msg msg1;
· msg1.cmd = COMMAND;
· msg1.sendID = 2120100324;
· msg1.recvID = 2120100325;
· msg1.name = "Tony";
· msg1.number = 2000;
·
· //memcpy(send_buf,&msg1,sizeof(msg));
· //int len_send = send(Socket,send_buf,sizeof(send_buf),0);
· int len_send = send(Socket,(char *)&msg1,sizeof(msg),0);
如上所示,
TCP是无边界的字节流传输,所以需要将结构体转换为字符串后在发送,最后三行用了两种方法发送属于结构体类型的数据,通过TCP传输。最后在接收方需要转换为结构体。
红色: 数组属于字符串,该方法是将要发送结构体所占字节大小考到数组中, 再通过数组发送。
蓝色: 将该结构体地址转化为char* 类型的地址,目的是使该指针加1移动时 是按一个字节移动,而不是加1按该结构体大小移动,然后发送该结构 体所占字节大小。
struct AP_INFO {
char action;
char *test;
char *testlist;
};
这样定义的结构更是不能直接传输的,因为其中有两个是指针,指向的是结构以外的内存地址,通过socket传输到对方机器后无法解析,搞得不好的话甚至可能造成程序崩溃!
原始的序列化:将结构体的成员一个一个的复制到内存再发到服务端
===============================================
http://www.cnblogs.com/foohack/p/4718320.html
大家都知道,在进行网络传输的时候,因为分布在网络上的每台机器可能大小端的不同,需要进行字节序列转换,比如用win32 API的socket里面就有类似与htonl等与此类似的函数,它就是把主机端的字节序列转换成网络传输的字节序列。当然也有与之相反的函数ntohl,是把网络字节序,转换为主机字节序。
比如 int data = 0x32461256在小端机器上按照“高高低低”的原则,内存上是这样表示,0x56,0x12,0x46,0x32。进行htonl转换后,在内存中的布局就会变成0x32,0x46,0x12,0x56。
所以,我们通过socket的send发送结构体或者对象的时候要注意了,需要序列化,当然,大家可以说,这样一个结构体那么多字段都要手动用htonl之类的函数序列化,那么太麻烦,其实网络上有专门的序列化库,比如google的protobuff,boost也有相应模块,Qt的QDataStream内部就实现了序列化,序列化实际上就是把大小端,还有结构体字节对齐等细节屏蔽了。所以,一般通过send发送结构体不能直接把它转换成char*的字节序列发送,在发送之前,要先做序列化。
以下给出用Qt的QDataStream做序列化例子:
http://www.java2s.com/Code/Cpp/Qt/SerializationwithQDataStream.htm
http://comments.gmane.org/gmane.comp.lib.qt.general/38559
注意:char型的数据是不用序列化的,因为只是单个字节,不是多字节占用
references:
http://stackoverflow.com/questions/5894622/sending-any-structure-via-qtcpsocket
http://stackoverflow.com/questions/2473300/overloading-the-qdatastream-and-operators-for-a-user-defined-type
http://stackoverflow.com/questions/1577161/passing-a-structure-through-sockets-in-c
http://stackoverflow.com/questions/17817280/send-struct-over-socket-in-c
=======================
http://hcq0618.blog.163.com/blog/static/1780903512013101831120514/
主要技术问题:windows,linux等系统采用LITTLE_ENDIAN字节序,而java自身采用BIG_ENGIAN字节序,BIG_ENGIAN是指低地址存放最高有效字节(MSB),而LITTLE_ENDIAN则是低地址存放最低有效字节。Java程序写的客户程序端同c++的服务端程序交互时结构体的某些数据类型需要转换字节序。本文解决方法,java客户端程序发送数据时做相应的转换字节序,等收到数据时再做一次字节序的转换。
现在的网络程序多数采用可靠交付的TCP协议,其采用字节流的传输方式,c++程序中用结构体来模拟报头以此界定每次发送的报文。所以网络中整个字节流的格式:报头+数据负载+报头+数据负载……
c++与java进行socket通信时注意事项
因为java发送的都是网络字节序(big-endium),而c++是主机字节序(little-endium),所以当消息中有整型,浮点型(应尽量避免使用)的时候需要用htonl,htons,ntohl,ntohs等函数转换一下,字符串由于是单字节排序的不需要转换,但应注意c++字符串是以'/0'作为结束符的,如果找不到'/0'可能会出现一些乱码,所以接收的时候可以分配一个length+1的buffer用来接收消息(貌似java会自动处理).
网络只有字节的概念,所以,你必须把你要传送的东西全部转换成字节后,再发送出去,在c中,以字节方式存在的数据,是不需要进行转换的,比如char *什么的
可参考qiyi的ChatMsg.h中的序列化函数:对单字节字段【char, char *, char [], 可当作char*的string等】都没做大小端转换,对其他的short, int, long等都需要大小端转换
short 或者 long的数据在进行通信的时候最好养成:
1、发送的时候使用:htons(l)
2、接受的时候使用:ntohs(l)
当然了一般我都不用int型的数据通信,从来都是字符串通信,发送方利用sprintf组织,接收方利用atoi进行转换~~
http://beginman.cn/unp/2015/10/15/unp-socket/
在《Linux高性能服务器编程》中这里理解更好些:
现代PC大多采用小端字节序,因此小端字节序又称为主机字节序。
当格式化的数据(比如32 bit整型数和16 bit短整型数)在两台使用不同字节序的主机之间直接传递时,接收端必然错误地解释之。解决问题的方法是:发送端总是把要发送的数据转化成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序【因为网络序是大端】,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序,它给所有接收数据的主机提供了一个正确解释收到的格式化数据的保证。
知道为什么有模式的存在,下面需要了解应用场景:
1、不同端模式的处理器进行数据传递时必须要考虑端模式的不同
2、在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式, 对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。
struct { char one; //字符 unsigned short two; //short类型 unsigned int three; //int类型 char * four; //字符串 }BinaryProtocolStruct;
//one为char类型不需要进行网络主机传输模式转换,把one的值写入到内存块中
//two为unsigned short 类型,所以要进行网络主机的传输字节顺序的转换 htons
//three 为int类型 所以要进行网络主机的传输字节顺序的转换 htonl
//four为字符串不需要进行存储转换
主机字节序与网络字节序
主机字节序
不同的CPU有不同的字节序类型 这些字节序是指整数在内存中保存的顺序 这个叫做主机序
最常见的有两种
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
网络字节序
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。
同样 在网络程序开发时 或是跨平台开发时 也应该注意保证只用一种字节序 不然两方的解释不一样就会产生bug.
Java 程序与 C++ 之间的 SOCKET 通讯
4.1 字节序问题
一直以来都在进行着C++ 上面的网络开发,发现在 C++ 上面进行通讯的时候,基本上都没有考虑到网络字节序的问题,特别是网络应用中的用户数据。大家都知道网络通讯传输的都是字节流的数据,于是都是定义一个 char 类型的缓冲区,然后不管 int , WORD, DWORD 还是其他自定义类型的结构对象也好,都直接 memcpy() 拷贝到缓冲区,直接发送出去了,根本都没有考虑所传输数据的网络字节序问题。如果非要说一点关注了网络字节序问题的话,那就是有一个地方,大家都回去用到的,也就是网络通讯端口,把端口号赋值给 sockaddr_in .sin_port之时大家都会使用了htons() ,也就是把端口号从主机字节序转化为网络字节序。
因为这些程序的服务器端也好,客户端也好,都是在x86 系列系统下运行,并且都是 C++ 编译出来的,所以不会因为字节序而出现问题。
现在所做项目,涉及到Java 与 C++ 之间的 SOCKET 通讯,这样的情况下,就需要大家都按规则来办事了, C++ 方面传输的多字节类型数据还请从主机字节序转化成网络字节序再进行传输。
当然,数据是由程序员来组合的,也是由程序员来解析的,所以如果不按标准行事也是可以的。但是就需要在解析数据的时候注意好了。
建议还是按标准行事,把数据转化成网络字节序。
PS:
Java与 Windows 平台下其他开发语言之间进行数据交与,也需要遵循该原则;
Java下读写 Windows 平台下其他开发语言保存的数据,或是 Windows 平台下其他语言读写Java 保存的数据,也需要注意字节序问题。
byte,string区别:本来以为传输的时候就是string类型字符串,原来根本不是一回事。网络上传输数据都是字节码,就是我们常说的 ascII码的,对应到类型上就是byte类型的。而string只是java中一种对象而已。而且java中编码一般是unicode编码的,要进行和byte类型的转化。int和long类型的也要进行int2byte转化。
接收到数据之后,则要进行byte2int转化。且值得注意的是int转成byte并不是直接字符串的转化,比如123转成字符串就是123,但是转成byte就不是了。它要根据整数123所占的字节,得到每个字节的值,再转成byte数组。常用的转化函数有:
//int2byte
public static byte[] intToByte(int n) {
byte[] b = new byte[4];
b[0] = (byte) (n >> 24);
b[1] = (byte) (n >> 16);
b[2] = (byte) (n >> 8);
b[3] = (byte) (n);
return b;
}
public static void int2byte(int n, byte buf[], int offset) {
buf[offset] = (byte) (n >> 24);
buf[offset 1] = (byte) (n >> 16);
buf[offset 2] = (byte) (n >> 8);
buf[offset 3] = (byte) n;
}
// 字节类型转成int类型
public static int byte2int(byte b[]) {
return b[3] & 0xff | (b[2] & 0xff) << 8 | (b[1] & 0xff) << 16
| (b[0] & 0xff) << 24;
}
//short2byte
public static byte[] short2byte(int n) {
byte b[] = new byte[2];
b[0] = (byte) (n >> 8);
b[1] = (byte) n;
return b;
}
// long到byte的转换
public static byte[] long2byte(long n) {
byte b[] = new byte[8];
b[0] = (byte) (int) (n >> 56);
b[1] = (byte) (int) (n >> 48);
b[2] = (byte) (int) (n >> 40);
b[3] = (byte) (int) (n >> 32);
b[4] = (byte) (int) (n >> 24);
b[5] = (byte) (int) (n >> 16);
b[6] = (byte) (int) (n >> 8);
b[7] = (byte) (int) n;
return b;
}
等等,注意:这里只是进行普通的字节码和数值之间的类型转换,并不进行高低位转化。原因前面已经说过了,java和网络字符是一样的,都是高位前,低位后,所以不用进行转化。
中文传输问题:
网络中传输中文极易出现乱码,那怎么办比较好呢,对了,就是对中文进行编码,常用的是Base64编码。再对编码后数据进行传输,接收到后也要先进行base64解码即可。
==================================================
http://www.jscto.net/html/26616.html
struct A
{
int id;
string msg;
};
=============================
虽然,网络编程里面的数据传送推荐用序列化,但我不用,还是选择结构体(返璞归真),有以下几点理由:
1.跨平台问题:
序列化确实可以很好的跨语言平台,可大多数网络游戏不需要跨语言平台
2.别以为有了序列化就不需要结构体
表面上序列化代码量小,按顺序读和写char int, short LPCSTR ... 就好,逻辑对象写不写都无所谓,那就是大错而特错了
待序列化的对象/发送前的结构还是不可省略的。序列化的过程就是 object->(按一定顺序拆分)write->bytes->(按拆分顺序组装)read->object的过程
其实object还是不能省略,很多人写网络程序不注重逻辑对象结构,收到的一堆bytes按一定顺序读和写就完事了,这样虽然灵活,但缺乏结构,容易造成混乱,调试起来是灾难【ulu的设备代码就是这样的,其实这就是最基础原始的序列化】
所以结构体(或类)还是省略不了的,所以:别以为有了序列化,就不需要结构体了。
3.结构体存在内存对齐和CPU不兼容的问题,可以避免
的确结构体是有内存对齐的问题,存在兼容性问题,我可以选择pack(1)把内存对齐给关闭掉,避免兼容性问题,既然选择了iocp就不打算跨平台了,可以避免结构体平台兼容的问题
4.结构体调试起来方便很多,减少内存拷贝,效率高
不用序列化write和read的过程就不需要过多考虑(少写太多代码了),read write 就好像现代社会每个人每天都要穿衣服和脱衣服一样,原始社会需要吗?其实人类进化到原始裸奔状态才是最爽快的:)
但还是要说句公道话:有人说序列化编码解码read write 需要耗费资源, 诚然这个过程基本等于赋值和内存拷贝,那点效率损失主要还在内存拷贝上,这点效率损失很小,不能作为序列化的缺点,当然如果涉及到数据加密那将是另外一个话题
5.结构体貌似呆板,发送数据限制多,发送变长数据就不方便,数据组织起来也不灵活
我想这是很多人抛弃结构体,选择用序列化方式发送和接受数据的一个很重要的原因
但:其实对于变长结构(子结构也是变长)的问题,用结构体来实现的确很麻烦,但并不代表不能实现
我已经实现了,而且读和写变长子结构体嵌套任意多层都不成问题,可以存储复杂变长的数据结构,
数据就如同能自动序列化一样方便,这个应该是技术难点,但细心去做是可以实现的
6.关于结构体指针
游戏里面要发送的数据内存事先分配好的,不存在指针,深度复制更不用考虑,所以内存拷贝不会出错
如果用到指针即使用序列化来实现也会面临同样的问题也占不了多少便宜,由于C++这们语言的特点,
不象java那样有个标准实现,对于序列化本身没有一个统一的标准,所以可想而知,有人说:boost有它的序列化的实现
其实那个实现不见得就合适你自己,如果真要做序列化,编码和解码得仿照那个过程自己写才最为牢靠,
哪些指针对应的内存需要序列化那些不需要序列化,是个逻辑结构,需要自己说了算才好(好像扯远了点)
说回游戏数据,既然不用需要他用到指针,结构体用来发送数据也没问题的
7 平台扩充问题
退一万步的说:换了语言就基本上换了客户端,客户端的数据组织形式都要重写
实在不行还可以考虑用xml json 编码等等一些跨平台的解决方案,现在所写的结构体是可以用来做数据接收的,只是发送的不再是结构体而已
8.综上所述
如果需要跨语言平台,不用序列化(二进制流或xml, json文本等等)根本无法实现
序列化的优点还是非常多的.如果主要是跨平台和语言自定义读写规则,根据需要读写对象的某一部分数据,
空间浪费少,不存在内存对齐问题等诸多优点,缺点就是拐弯抹角,代码量大,调试不方便
最好的方法还是模拟rpc的解决方案,利用idl解析器自动生成序列化,只是这需要用lex/yacc方面的咚咚,
tao,ice等都有原代码可借鉴。