轉自:http://blog.csdn.net/hslinux/article/details/6214594
java與 C++ 之間進行 SOCKET 通訊要點簡要解析
hslinux
0、篇外語
此乃本人學習過程中自娛自樂之作,爲了遺忘後有個地方再溫習。如入您法眼,轉載請尊重原作者,請說明出處。
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 的長度跟編譯器的實現相關)。