原文鏈接: 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;
if (*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的長度跟編譯器的實現相關)。
關於Java和C++的socket通信時如何傳送結構體
問題是這樣的:
有一個socket 程序,一端是c++寫的socket 服務程序
另一端是Java寫客戶端程序,兩者之間需要通信。
這個案例中c++接收和發送的都是結構體,而Java是直接發送的字節流或者byte 數組。
解決方法是:c++socket 在發送結構體的時候其實發送的也是字節流。因為結構體本身也是內存中的一塊連續數據。問題就變成了如何把結構體手動轉成字節的問題了
例如Java客戶端要像服務端發送一個這樣的結構體
/*c++ code:
* struct FileInfo
{
int sessionId;
int filelenth;//文件長度 ,單位字節
char filename[100];
};
* */
下面兩個方法就可以將是結構體轉成字節數組。
/**
* 將int轉為低字節在前,高字節在后的byte數組
*/
private byte[] toLH(int n)
{
byte[] b = new byte[4];
b[0] = (byte) (n & 0xff);
b[1] = (byte) (n >> 8 & 0xff);
b[2] = (byte) (n >> 16 & 0xff);
b[3] = (byte) (n >> 24 & 0xff);
return b;
}
private byte[] getFileInfo(String filename,int filesize, int sessionId)
{
byte[] b = new byte[108];
byte[] temp;
//分別將struct的成員格式為byte數組。
temp = toLH(sessionId);
System.arraycopy(temp, 0, b, 0, temp.length);
temp = toLH(filesize);
System.arraycopy(temp, 0, b, 4, temp.length);
System.arraycopy(filename.getBytes(), 0, b, 8, filename.length());
return b;
}
如果需要用Java 接收結構體的話只需要將上面過程逆過來即可。
另外字節數組直接使用 Java的NIO包中的 ByteBuffer 類更方便,速度也更快。
- public class Common {
- public static byte[] toLH(int n) {
- byte[] b = new byte[4];
- b[0] = (byte) (n & 0xff);
- b[1] = (byte) (n >> 8 & 0xff);
- b[2] = (byte) (n >> 16 & 0xff);
- b[3] = (byte) (n >> 24 & 0xff);
- return b;
- }
- public static int bytes2Integer(byte[] byteVal) {
- int result = 0;
- for (int i = 0; i < byteVal.length; i++) {
- int tmpVal = (byteVal[i] << (8 * (3 - i)));
- switch (i) {
- case 0:
- tmpVal = tmpVal & 0xFF000000;
- break;
- case 1:
- tmpVal = tmpVal & 0x00FF0000;
- break;
- case 2:
- tmpVal = tmpVal & 0x0000FF00;
- break;
- case 3:
- tmpVal = tmpVal & 0x000000FF;
- break;
- }
- result = result | tmpVal;
- }
- return result;
- }
- }