Java與C++Socket通信注意

c++與java進行socket通訊時注意事項
    由於java發送的都是網絡字節序(big-endium),而c++是主機字節序(little-endium),因此當消息中有整型,浮點型(應儘可能避免使用)的時候須要用htonl,htons,ntohl,ntohs等函數轉換一下,字符串因爲是單字節排序的不須要轉換,但應注意c++字符串是以'/0'做爲結束符的,若是找不到'/0'可能會出現一些亂碼,因此接收的時候能夠分配一個length+1的buffer用來接收消息.


舉例:c++ server, java client,假設開發的是c++ server,那麼:


java client--------->c++ server: c++ server須要調用ntohs,ntohl
c++ server--------->java client: c++ server須要調用htons,htonl


至於浮點型可使用如下的函數轉換:
float tcp_htonf(float f) 
{ 
 unsigned char *p, p0, p1; 
 if(htons(1) ==1) return f; 
 p =(unsigned char *)&f;
 p0 =p[0]; 
 p1 =p[1];
 p[0] =p[3];
 p[3] =p0;
 p[1] =p[2];
 p[2] =p1; 
 return f; 
}

float tcp_ntohf(float f)
{
 unsigned char *p, p0, p1; 
 if(ntohs(1) ==1) return f; 
 p =(unsigned char *)&f; 
 p0 =p[0]; 
 p1 =p[1]; 
 p[0] =p[3]; 
 p[3] =p0; 
 p[1] =p[2]; 
 p[2] =p1; 
 return f; 
}


double tcp_htond(double d)  
{  
 unsigned char *p, p0, p1, p2, p3;    
 if(htons(1) ==1) return d;  
 p =(unsigned char *)&d;  
 p0 =p[0];  
 p1 =p[1];  
 p2 =p[2];  
 p3 =p[3]; 
 p[0] =p[7];  
 p[7] =p0;  
 p[1] =p[6];  
 p[6] =p1;??  
 p[2] =p[5];??  
 p[5] =p2; 
 p[3] =p[4];  
 p[4] =p3;  
 return d;  
}


double tcp_ntohd(double d)  
{ 
 unsigned char *p, p0, p1, p2, p3; 
 if(ntohs(1) ==1) return d;    
 p =(unsigned char *)&d; 
 p0 =p[0];  
 p1 =p[1];  
 p2 =p[2];  
 p3 =p[3];  
 p[0] =p[7];  
 p[7] =p0;  
 p[1] =p[6];  
 p[6] =p1;  
 p[2] =p[5];
 p[5] =p2;
 p[3] =p[4]; 
 p[4] =p3;
 return d;  
}




java代碼發送結構體
最近給個朋友作個網站的客戶端,使用C/S模式,Client爲VC6開發,Server爲Java,經過Socket通訊。因爲Client這邊爲C++,因此,在接受Java發過來的數據包時,須要知道發來的包的長度,因此,就要引入變長包的機制。
方法是:首先Server發送一個包頭,以下:
// packet head
typedef struct tagPacketHead{
long PacketID;
long PacketLen;
}PacketHead;
包頭後面跟上包體,其中包體的長度,就是上面結構體中的PacketLen,Clinet首先接受包頭,由於包頭是兩邊約定好的,因此能夠直接Receive一個定長的消息,也就是這個包頭的長度的消息,從包頭中取得包體的長度後,就能夠再次Receive一個包體長度的消息了。那麼Java中如何發送一個結構體呢?下面是解決方法:


package org.charry.org;
import java.net.*;
/**
*
* 字節轉換,參考網絡文章  
*/
class Packet {
private byte[] buf = null;
/**
* 將int轉爲低字節在前,高字節在後的byte數組
*/
private 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;
}
/**
* 將float轉爲低字節在前,高字節在後的byte數組
*/
private static byte[] toLH(float f) {
return toLH(Float.floatToRawIntBits(f));
}
/**
* 構造並轉換
*/
public Packet(int packetID, int packetLen, String packetBody) {
byte[] temp = null;
buf = new byte[packetBody.getBytes().length + 8];
temp = toLH(packetID);
System.arraycopy(temp, 0, buf, 0, temp.length);
temp = toLH(packetLen);
System.arraycopy(temp, 0, buf, 4, temp.length);
System.arraycopy(packetBody.getBytes(), 0, buf, 8,packetBody.length());
}
/**
* 返回要發送的數組
*/
public byte[] getBuf() {
return buf;
}
/**
* 發送測試
*/
public static void main(String[] args) {
try {
String tmp = 「test string!」;
Socket sock = new Socket(」127.0.0.1″, 8888);
sock.getOutputStream().write(
new Packet(123, tmp.length(), tmp).getBuf());
sock.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
從Client端發到Server的數據就無須特殊處理了,Java的流能夠很好的處理這些。






主機字節序與網絡字節序
主機字節序
不一樣的CPU有不一樣的字節序類型 這些字節序是指整數在內存中保存的順序 這個叫作主機序 
最多見的有兩種
1. Little endian:將低序字節存儲在起始地址
2. Big endian:將高序字節存儲在起始地址


LE little-endian 
最符合人的思惟的字節序 
地址低位存儲值的低位 
地址高位存儲值的高位 
怎麼講是最符合人的思惟的字節序,是由於從人的第一觀感來講 
低位值小,就應該放在內存地址小的地方,也即內存地址低位 
反之,高位值就應該放在內存地址大的地方,也即內存地址高位 


BE big-endian 
最直觀的字節序 
地址低位存儲值的高位 
地址高位存儲值的低位 
爲何說直觀,不要考慮對應關係 
只須要把內存地址從左到右按照由低到高的順序寫出 
把值按照一般的高位到低位的順序寫出 
二者對照,一個字節一個字節的填充進去 


例子:在內存中雙字0x01020304(DWORD)的存儲方式 
內存地址 
Addr   4000 4001 4002 4003 
LE   04   03   02   01 
BE   01   02   03   04 


例子:若是咱們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
      big-endian   little-endian
0x0000   0x12       0xcd
0x0001   0x34       0xab
0x0002   0xab       0x34
0x0003   0xcd       0x12
x86系列CPU都是little-endian的字節序. 


網絡字節序
網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操做系統等無關,從而能夠保證數據在不一樣主機之間傳輸時可以被正確解釋。網絡字節順序採用big endian排序方式。
爲了進行轉換 bsd socket提供了轉換的函數 有下面四個
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序
在使用little endian的系統中 這些函數會把字節序進行轉換 
在使用big endian類型的系統中 這些函數會定義成空宏
一樣 在網絡程序開發時 或是跨平臺開發時 也應該注意保證只用一種字節序 否則兩方的解釋不同就會產生bug.


注:
一、網絡與主機字節轉換函數:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
二、不一樣的CPU上運行不一樣的操做系統,字節序也是不一樣的,參見下表。
處理器     操做系統     字節排序
Alpha      所有      Little endian
HP-PA        NT       Little endian
HP-PA        UNIX     Big endian
Intelx86     所有     Little endian  
<!--v:3.2--> 
java與C++之間進行SOCKET通信要點簡要解析
分類: Android/Java2011-03-01 08:58 2000人閱讀 評論(2) 收藏 舉報
目錄(?)[+]
 =======================================================================================
java與 C++ 之間進行 SOCKET 通信要點簡要解析 0、篇外語   此乃本人學習過程當中自娛自樂之做,爲了遺忘後有個地方再溫習。如入您法眼,轉載請尊重原做者,請說明出處。 一、 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 ;   // 大端 } 二、網絡字節序與主機字節序 在各類計算機體系結構中,對於字節、字等的存儲機制有所不一樣,於是引起了計算機通訊領域中一個很重要的問題,即通訊雙方交流的信息單元(比特、字節、字、雙字等等)應該以什麼樣的順序進行傳送。若是不達成一致的規則, 通訊雙方 將沒法進行正確的編/ 譯碼從而致使通訊失敗。 一般所說的網絡字節序(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 類型的系統中這些函數會定義成空宏 三、 java 字節序 因爲Java 運行須要本身的虛擬機來支持,因此 Java 程序所支持的字節序與 Java 虛擬機一致。Java 虛擬機遵循的是 big-endian 規則。因此能夠把 Java 字節序看做是遵循 big-endian 規則的主機字節序。 四、 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 的長度跟編譯器的實現相關)。  
相關文章
相關標籤/搜索