java与 C++ 之间进行 SOCKET 通讯要点简要解析
1、 big-endian 与 little-endian
Endian定义: 在计算机系统体系结构中用来描述在多字节数中各个字节的存储顺序。
big-endian也称高位在前、大端在前。是 计算机体系结构中一种描述多字节存储顺序的术语,在这种机制中最重要字节(MSB )存放在最低端的地址 上。采用这种机制的处理器有 Mortolora PowerPC 微处理器系列和绝大多数的 RISC 处理器。
big-endian 最直观的字节序:
内存地址从左到右与值由低到高的顺序相对应。
little-endian也称低位在前、小端在前。 计算机体系结构中一种描述多字节存储顺序的术语,在这种机 制中最不重要字节(LSB )存放在最低端的地 址上。采用这种机制的处理器有 Intel x86 系列微处理器和一些网络通信设备。该术语除了描述多字节存储顺序外还常常用来描述一个字节中各个比特的排放次序 ,这里仅讨论多字节存储循序 。
little-endian是最符合人的思维的字节序,低与低,高与高一一对应:
地址低位存储值的低位
地址高位存储值的高位
下面举一个例子具体说明 big-endian 与 little-endian:
int nValue = 0x01020304;
上面的整型nValue 有 4 个字节,其中 01 为最高位的字节, 04 为最低位的字节。那么在内存(或文件)中,该值的存储循序为:
内存(或文件)地址:0x12000001 0x12000002 0x12000003 0x12000004
Big-endian : 01 02 03 04
Little-endian : 04 03 02 01
如果用一个byte 数组来保存的话,也就是如下:
Big-endian模式下: byte byValue[] = {0x01, 0x02, 0x03, 0x04};
Little-endian模式下: byte byValue[] = {0x04, 0x03, 0x02, 0x01};
Big-endian 或是 little-endian的判断:
bool IsLittleEndian ()
{
int i = 1;
char * p = ( char *)& i ;
i f ( * p = 1 )
return true ; // 小端
else
return false ; // 大端
}
2、网络字节序与主机字节序
在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则, 通信双方 将无法进行正确的编/ 译码从而导致通信失败。
通常所说的网络字节序(Network Byte Order )就是遵循 big-endian 规则。实际通信过程中,通信双方需要把数据按照 big-endian 编码再通过网络传输。
通常所说的主机字节序(Host Byte Order ),与 CPU 的字节序一致。 x86 系列主机的字节序都是 little-endian 桂册。所有 little-endian 规则主机直接通过网络通讯的时候,需要进行字节序转化。
为了进行转换 bsd socket 提供了转换的函数 有下面四个
htons 把 unsigned short 类型从主机 字节 序转换到 网络字节序
htonl 把 unsigned long 类型从主机 字节 序转换到 网络字节序
ntohs 把 unsigned short 类型从 网络字节序 转换到主机 字节 序
ntohl 把 unsigned long 类型从网络 字节 序转换到主机 字节 序
在使用little endian 的系统中这些函数会把字节序进行转换
在使用big endian 类型的系统中这些函数会定义成空宏
3、 java 字节序
由于Java 运行需要自己的虚拟机来支持,所以 Java 程序所支持的字节序与 Java 虚拟机一致。Java 虚拟机遵循的是 big-endian 规则。所以可以把 Java 字节序看作是遵循 big-endian 规则的主机字节序。
4、 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 保存的数据,也需要注意字节序问题。
4.2 字节对齐问题
#include <iostream>
using namespace std ;
typedef struct tag_S1
{
char s_szValue [8];
char s_cValue ;
} S1 ;
typedef struct tag_S2
{
int s_nValue1 ;
char s_szValue [8];
char s_cValue ;
int s_nValue2 ;
} S2 ;
typedef struct tag_S3
{
int s_nValue ;
char s_cValue ;
} S3 ;
#pragma pack ( push , 1)
typedef struct tag_S4
{
int s_nValue ;
char s_cValue ;
} S4 ;
#pragma pack ( pop )
int main ( int argc , char * argv [])
{
cout << "sizeof(S1):" << sizeof ( S1 ) << endl ;
cout << "sizeof(S2):" << sizeof ( S2 ) << endl ;
cout << "sizeof(S3):" << sizeof ( S3 ) << endl ;
cout << "sizeof(S4):" << sizeof ( S4 ) << endl ;
system ( "pause" );
return 0;
}
上面的程序在 WinXP sp3 + VS2008Sp1下运行结果如下:
sizeof(S1):9
sizeof(S2):20
sizeof(S3):8
sizeof(S4):5
请按任意键继续. . .
Win32位平台下的微软 C 编译器 (cl.exe for 80x86) 的对齐策略:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。
2) 结构体每个成员相对于结构体首地址的偏移量( offset )都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节( internal adding );
备注: 为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节( trailing padding )。
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
Windows 32位系统下, VC 中,默认的字节对齐方式是 4 字节对齐。
sizeof(S1) :因为结构中数据类型都是 char ,最宽基本类型大小是 1 ,所以结构大小为 9 ,S1 没有进行填充;
sizeof(S2)、 sizeof(S3) : S2 , S3 就被填充了一定的字节;
sizeof(S4):因为设置了对齐方式为 1 字节对齐,所以不会被填充。
在Java 与 C++ 进行 SOCKET 通讯的 C++ 端程序,建议涉及网络通讯的结构使用 1 字节对齐方式,不然 Java 端会增加数据处理的复杂度。
4.3 Java与 C++ 之间基本数据类型的差别
需要注意以下几个数据类型的区别(32 位系统下 ) :
C++ Java
char---------1byte Byte----------1byte
Char----------2byte2
long---------4bytes long----------8bytes
注意:
Java中的 Char 是一个字符,而不是一个字节,与 VC 的 WORD 长度一致;
Java中的 Byte 是一个字节,与 C++ 中的 char 含义一致,而 VC 中的 BYTE 是无符号的 char;
Java中的 long 长度为 8 ,而 VC 中的 long 长度为 4 ( C++ 中 short , long 的长度跟编译器的实现相关)。