標籤:java socket 網路通訊 c++ 大小端
最近做了一款Android應用,需要和PC端通過socket傳遞資料。PC端是用C++開發的,為了方便傳輸,在發送之前都將資料轉化為位元組數組。本以為可以萬事大吉,可是過程還是會有一些問題。下面是我遇到的幾個問題以及解決方案,這裡做一個簡單的記錄,也希望大家遇到後能夠快速解決,同時歡迎各位補充指正~
Java Socket的接收與發送
首先我們會建立一個socket串連,然後從中擷取InputStream和OutputStream。在接收的時候我是用一個DataInputStream對Socket的InputStream進行封裝,便於後面接收不同大小的資料類型。在發送時先通過DataOutputStream()對OutputStream進行封裝,然後將不同的資料類型寫入,最後用ByteArrayOutputStream封裝轉化成位元組流,通過已經建立好的Socket通道發送。這樣以來會有以下兩個問題。
問題一:大小端問題
PC端裝置使用的是x86平台, 資料為小端格式。而jvm虛擬機器採用的大端格式,當你用Java讀取一個位元組,如:調用dataInputStream.read()的時候,可以正常的讀出一個位元組。但是如果要讀出多位元組的資料類型,如:dataInputStream.readInt()的時候,就會出現大小端的資料錯誤。小端格式會把低位元組的資料存放區在低地址儲存空間,而大端格式會把低位元組的資料存放區在高地址空間。這樣以來,資料的高地位就正好顛倒了。
解決方案
這個問題的解決方案也比較容易,通過對錯誤資料高地位進行蝶形交換可以得到結果。對於大小端格式轉換可以先做一個基於short的轉換,然後對Int資料高低位分別調用short的轉換即可,代碼如下:
/** * 大小端蝶形交換 * * @param data * 待交換的short資料 * @return 轉換後的short數 */ private static short swapShortToLittleEndian(short data) { short ret = (short) ((data << 8) | (data >> 8) & 0x00FF); ret = (short) (ret & 0xFFFF); return ret; }
然後對於int資料可以分別對高低16位進行以上轉換:
/** * 大小端蝶形交換 * * @param data * 待交換的int資料 * @return 轉換後的int數 */private static void swapIntToLittleEndian(int data) { int ret = swapShortToLittleEndian((short) ((data >> 16) & 0xFFFF)) | (swapShortToLittleEndian((short) data) << 16); return ret; }
經過以上兩種函數可以將16位元組、32位元組的資料轉為小端格式,對於64位的可以採用同樣的原理。
PS:可能有朋友會對上面頻繁的和0xFFFF/0xFF作與運算有些疑惑,這裡也是要說的第二個問題,這個與運算是必須的,下面來說說原因
問題二:Java隱式類型轉換
在Java中,對byte 的 + - * / >> >>> << & | ^ (加,減,乘,除,右移,左移,無符號右移,位與,位或,位異或)操作,均會是首先將byte轉化為int, 再行運算。然後,這一過程可能導致多種問題。
解決方案
大家知道在Java中為了方便資料的處理,只存在有符號數。即用最高位來表示符號,其餘位表示實際的資料。在上面大小端轉換中,我們需要首先將低8位元據移到高8位,同時將高8位元據移到低8位。在左移的過程當中,右邊毫無疑問是自動補0,那麼直接進行移位的結果會是我們想要的結果。但是如果是右移,對於正數而言(最高位是0),也能得到正確的結果;然而如果是負數(最高位是1),此時右移的時候高位是會自動補1,當然這個可以通過運算子“>>>”來解決,但是這裡還有另一個問題:當對byte移位時,系統會將其隱式轉為int資料,如果是負數,那麼補齊的高位元據則全為1。
如果在右移之後不和0xFF做位與,那麼之後和高位元據位或拼接的時候高位會直接全為1,這也就是為什麼會頻繁做位與操作的原因。
綜上:
1. 用Java接收C++無符號資料的時候,一定要注意大小端的轉換。
2. 另外遇到移位操作,需要注意在右移之後必須將高位清零來消除錯誤。
以上是個人遇到的問題及自己的解決方案,如有更多方案,歡迎補充!
Java與C/C++網路通訊時的資料轉換