原文鏈接:https://blog.csdn.net/u011490813/article/details/16991571
Java與C++之間的Socket通信,對於小的數據量和控制命令,直接可以封裝成json或xml格式,進行傳輸。但對於文件等大數據量傳輸,必須要將文件封裝成幀,每一幀都設定固定大小的緩衝區,逐幀傳輸。此時json和xml便不再適用了。
在此過程中要需解決如下問題:
1. Java和C++數據基本類型不同,不僅所佔字節數不同(如long型,java佔8bytes,C++一般爲4bytes)。C++緩衝區一般使用char*型,但是java中沒有char*型,相互之間傳輸的數據,如何接收解析?
2. C++常用的特殊類型:結構體,如何解析成Java中的類。即使是兩者都具有的枚舉類型,兩者的機制是不一樣的,如何進行對接?
3. Java端和C++端,發送給socket的數據形式是什麼?char數組型還是字節型C++端又有何種形式進行接收?接收到的數據又如何正確解析出來?
4. 字節序問題。Java爲大字節序,而大部分PC主機C++都是小字節序,大小字節序和網絡字節序相互之間的轉化,也是需要考慮的問題。
這些問題,本文將對其逐一進行講述和解決。話不多說,先普及熟悉一下基礎知識。
本文涉及的問題較爲基礎,大神若有垂憐本文,可直接跳過基礎部分。
一、基礎知識
1.大小字節序
1.1基本概念
由於不同主處理器和操作系統,多字節數據在計算機內存中存儲或者網絡傳輸時各字節的存儲順序會有所不同,爲保證不同系統之間的正確數據傳輸需要做相應的字節序調整,即爲字節序問題。例如兩個字節的short int和4個字節的float類型變量都會有字節序問題。
字節序通常分爲大字節序(big-endian)和小字節序(little-endian)。
Ø 大字節序(big-endian):表示變量的起始地址存放低字節,高字節依順序存放。
Ø 小字節序(little-endian):表示變量的起始地址存放高字節,低字節依順序存放,這種存儲方式對於人類來說是最自然的。
1.2 字節序範例
例如:在內存中雙字0x01020304(DWORD)的存儲方式,設定其內存地址爲:4000&4001&4002&4003。
Ø 對於小字節序而言,其在內存中的存儲方式爲:01 02 03 04
Ø 對於大字節序而言,字節存儲順序恰好相反:04 03 02 01
如果用byte數組來保存數據,則具體的存儲方式爲:
Ø 小字節序:byteToByteValue={0x01,0x02,0x03,0x04}。
Ø 大字節序:與小字節序順序恰好相反,byte ToByteValue = {0x04 , 0x03, 0x02 ,0x01 }。
1.3 主機大小字節序的判斷
1.新建一個聯合體union
typedef Union{
//存儲雙字節變量
unsigned short int value;
//與value共享一個內存地址,可以通過byte數組來訪問value的高低字節
unsigned char byte[2];
}
2.通過對value賦值,如0xabcd,若byte中存儲的高字節存儲的是0xab,則爲大字節序,否則爲小字節序。
2.網絡字節序和主機字節序
2.1基本概念
網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節順序採用big endian排序方式。
如對4個字節的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit,這種傳輸次序稱作大端字節序。由於TCP/IP首部中所有的二進制整數在網絡中傳輸時都要求以這種次序,因此它又稱作網絡字節序。
主機字節序就是我們平常說的大端和小端模式,由主機CPU決定,不同CPU會有不同字節序。x86系列CPU都是小字節序。
2.2 常用字節序轉換函數
在主機之間通過網絡進行通信時,往往需要進行字節序轉換,linux系統中常用字節序轉換函數如下:
#include <arpa/inet.h>
uint32_t htonl(uint 32_t) 主機字節序到網絡字節序的長整形轉換
uint16_t htons(uint16_t):主機字節序到網絡字節序短整型轉換
uint32_t ntohl(uint32_t):網絡字節序到主機字節序長整型轉換
uint16_t ntohs(uint16_t):網絡字節序到主機字節序短整型轉換
其中XtoX就是進行數據存儲順序的主機和網絡順序的轉換,h—host—主機,n—net—網絡,l—long—長整形,s—short—短整形。
2.3 Java字節序和c++字節序
對於x86系列CPU都是小字節序,因此對於c++而言,主機字節序通常爲小字節序。
JAVA字節序指的是在JAVA虛擬機中多字節類型數據的存放順序,JAVA字節序也是BIG-ENDIAN。所以Android客戶端和c++服務器數據通信時要注意字節序轉換問題。
二、java和c++之間的socket通信代碼實現
本系統在Android客戶端採用將高字節序轉換爲低字節序,並存儲在字節數組當中,進行socket字節傳輸,C++服務器直接按照各數據類型所佔空間大小,解析出不同數據。反之,服務器向客戶端發送數據也是字節數組形式,在客戶單調用BasicDataTypeTransfer中的函數,將字節數組中的數據解析出來。
2.1 java和c++數據類型對比
1.C++與Java基本類型佔用內存空間差別
----------------C++----------- ------------Java----------
(01)bool------------------1byte 01)boolean--1 byte
(02)char------------------1byte 02)char------2bytes
(03)signedchar---------1byte 03)byte------1 byte
(04)unsignedchar------1 byte
(05)wchar_t--------------2bytes
(06)short-----------------2bytes 04)short-----2 bytes
(07)unsignedshort-----2 bytes
(08)int--------------------4bytes 05)int---------4 bytes
(09)unsignedint--------4 bytes
(10)long------------------4bytes 06)long-------8bytes
(11)unsignedlong-------4 bytes
(12)longlong-------------8 bytes
(13)unsignedlong long--8 bytes
(14)float-------------------4bytes 07)float-------4 bytes
(15)double----------------8bytes 08)double----8 bytes
(16)longdouble---------12 bytes
Java中每一種數據類型的內存大小是已經嚴格限定。但C++標準規定了每個算術類型的最小存儲空間,它並不阻止編譯器使用更大的存儲空間,以上採用32位機器上的C++數據類型的典型佔用字節數。
Java中沒有無符號類型,對於long型Java是8個字節,C++是4個字節。進行相應數據傳輸的時候,要特別注意,以免造成數據丟失或錯誤。
2.特殊類型:結構體和枚舉類型的傳輸和轉換
結構體是C++中經常要用到的數據結構,而Java中沒有結構體,以及枚舉類型的傳輸,要實現這些特殊類型的傳輸,需要對其內存中存儲方式有一個透徹瞭解,特別是要把握字節存儲的概念。
2.2代碼實現
Java和C++定義的數據結構
從上文講述的基礎知識,可以得到以下處理思路,在C++端將數據封裝在結構體當中,結構體中有消息類型,控制命令,文件緩衝區,文件屬性相關信息等。socket發送時將其強制轉換爲char*類,進行發送。
Java端socket以byte數組方式接手,隨後根據結構體的字節存儲順序對數據進行解析。如字節數組中,首先爲int數據,就讀取4個字節將其複製到Java的整形變量中,順此思路從而完成對結構體的數據還原。
C++下定義的數據類型如下:
//文件數據幀數據類型
enum FssFileFrameType
{
FILE_NAME_TYPE = 1, //文件名稱
FILE_DATA_TYPE = 2, //文件數據
FILE_END_TYPE = 3, //文件結尾
FILE_ERROR_TYPE = 4,//文件出錯
FILE_OVERWRITE_TYPE = 5, //覆蓋文件
FILE_SKIP_TYPE = 6, //跳過文件傳送
DISCONNECT_TYPE = 7//斷開連接
};
//文件數據幀
typedef struct
{
FssFileFrameType type;
int length;
char buffer[MAX_BUFFER_SIZE];
}FileDataFrame;
//狀態控制幀數據類型
enum TransactionStateType
{
OK_TYPE = 1, //表明服務器端正確接收到了客戶端的數據
FILE_EXIST_TYPE = 2, //文件已經存在
ERROR_TYPE = 3, //出錯
FINISHED_TYPE =4, //文件傳輸並保存成功
INVALID_TYPE = 5 //保留的無效位,主要用來簡化程序的控制邏輯
};
//狀態控制幀
typedef struct
{
TransactionStateType type;
int length;
char info[MAX_INFO_SIZE];
}TransactionStateFrame;
Java中對應的數據類型定義如下(幀類實現下面會具體說明):
//文件數據幀數據類型
public enum FssFileFrameType
{
FILE_INFO_TYPE(1), //文件名稱
FILE_DATA_TYPE(2), //文件數據
FILE_END_TYPE(3), //文件結尾
FILE_ERROR_TYPE(4),//文件出錯
FILE_OVERWRITE_TYPE(5), //覆蓋文件
FILE_SKIP_TYPE(6), //跳過文件傳送
DISCONNECT_TYPE(7); //斷開連接
//用於對枚舉成員初始化
public int value;
FssFileFrameType(int value){
this.value=value;
}
}
//狀態控制幀數據類型
public enum TransactionStateType
{
OK_TYPE(1), //表明服務器端正確接收到了客戶端的數據
FILE_EXIST_TYPE(2), //文件已經存在
ERROR_TYPE(3), //出錯
FINISHED_TYPE(4), //文件傳輸並保存成功
INVALID_TYPE(5); //保留的無效位,主要用來簡化程序的控制邏輯
//用於對枚舉成員初始化
public int value;
TransactionStateType(int value){
this.value=value;
}
}
/**
* 文件數據幀
* @author yin
*
*/
public static class FileDataFrame{
public int type;
public byte[] packetArray =new byte[FILE_DATA_PACKET_SIZE];
private byte[] dataArray =new byte[MAX_BUFFER_SIZE];//緩衝數據
/**
* 用於下載文件,接收服務器文件幀,並解析出FileDataFrame各成員變量
* @param byteArray
*/
public FileDataFrame(byte[] byteArray){
}
/**
* 用於上傳文件將文件數據包封裝成幀
* @param msgType
* @param packLen
* @param packetArray
*/
public FileDataFrame(int msgType,int packLen,byte[] bufferArray){
}
}
/**
* 文件狀態控制幀
* @author yin
*
*/
public static class TransactionStateFrame{
public int type;
public int length;
public byte[] infoArray =new byte[MAX_INFO_SIZE];//具體消息內容
//發送或接收到的字節數組幀
byte[] packetArray =new byte[STATE_CONTROL_PACKET_SIZE];
/**
* 用於接收消息,解析數據幀
* @param byteArray
*/
public TransactionStateFrame(byte[] byteArray){
}
/**
* 用於發送消息,將數據包封裝成幀
* @param msgType
* @param packLen
* @param packetArray
*/
public TransactionStateFrame(int msgType,int packLen,byte[] bufferArray){
}
}
}
1. BasicDataTypeTransfer類
BasicDataTypeTransfer類負責各基本類型和字節數組的相互字節序轉換,同時對將服務器通過socket發過來的字節數組進行解析。以下只提供幾種類型的代碼實現,其他類型原理類似。
package com.scut.util;
import java.io.UnsupportedEncodingException;
/**
* 字節序轉換類
* @author yin
*
*/
public class BasicDataTypeTransfer {
public static BasicDataTypeTransfer basicDataTypeTransfer=null;
public static BasicDataTypeTransfer getInstance(){
if(basicDataTypeTransfer==null){
basicDataTypeTransfer=new BasicDataTypeTransfer();
}
return basicDataTypeTransfer;
}
/*
*將int轉換爲小字節序
**/
public byte[] IntToByteArray(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 int ByteArrayToInt(byte[] bAttr){
int n=0;
int leftmove;
for(int i=0;i<4&&(i<bAttr.length);i++){
leftmove = i*8;
n += bAttr[i]<<leftmove;
}
return n;
}
public byte[] StringToByteArray(String str){
byte[] temp=new byte[100];
try {
temp=str.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return temp;
}
public String ByteArrayToString(byte[] bAttr,int maxLen){
int index=0;
while(index <bAttr.length&&index<maxLen){
if(bAttr[index] == 0){
break;
}
index++;
}
byte[] tmp = new byte[index];
System.arraycopy(bAttr, 0, tmp, 0, index);
String str=null;
try {
str = new String(tmp,"GBK");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return str;
}
}
2. FssCommon類
調用上述BasicdataTypeTransfer,針對C++服務器結構體的具體結構,進行相應的解析,以還原成對應的java類。
package com.scut.util;
/**
* 通用全局變量封裝類
* @author yin
*
*/
public class FssCommon {
/**
* 文件數據幀
* @author yin
*
*/
public static class FileDataFrame{
public int type;
public byte[] packetArray =new byte[FILE_DATA_PACKET_SIZE];
private byte[] dataArray =new byte[MAX_BUFFER_SIZE];//緩衝數據
/**
* 用於下載文件,接收服務器文件幀,並解析出FileDataFrame各成員變量
* @param byteArray
*/
public FileDataFrame(byte[] byteArray){
BasicDataTypeTransfer basicDataTypeTransfer=new BasicDataTypeTransfer();
if(byteArray!=null && byteArray.length>=sizeOfInt){
byte[] typeArray = new byte[sizeOfInt];
//讀出存儲在前面的int型數據
System.arraycopy(byteArray, 0, typeArray, 0, sizeOfInt);
//將其轉換爲整形,從而轉換爲枚舉類型
this.type=basicDataTypeTransfer.ByteArrayToInt(typeArray);
//讀取length
byte[] lenByteArray = new byte[sizeOfInt];
System.arraycopy(byteArray, sizeOfInt, lenByteArray, 0, sizeOfInt);
//讀取緩衝數據
System.arraycopy(byteArray,2*sizeOfInt, dataArray, 0, dataArray.length);
}
}
/**
* 用於上傳文件將文件數據包封裝成幀
* @param msgType
* @param packLen
* @param packetArray
*/
public FileDataFrame(int msgType,int packLen,byte[] bufferArray){
this.type=msgType;
System.arraycopy(bufferArray, 0, this.dataArray, 0, bufferArray.length);
BasicDataTypeTransfer basicDataTypeTransfer=new BasicDataTypeTransfer();
//cmdType
byte[] cmdTypeByte = basicDataTypeTransfer.IntToByteArray(msgType);
System.arraycopy(cmdTypeByte, 0, packetArray, 0, cmdTypeByte.length);
//packet length
byte[] packLenByte = basicDataTypeTransfer.IntToByteArray(packLen);
System.arraycopy(packLenByte, 0, packetArray, cmdTypeByte.length, packLenByte.length);
//packBuff
if(dataArray!=null){
System.arraycopy(dataArray, 0, packetArray, cmdTypeByte.length+packLenByte.length, dataArray.length);
}
//數據包總大小,4+4+MAX_BUFFER_SIZE(8*1024)
}
}
/**
* 文件狀態控制幀
* @author yin
*
*/
public static class TransactionStateFrame{
public int type;
public int length;
public byte[] infoArray =new byte[MAX_INFO_SIZE];//具體消息內容
//發送或接收到的字節數組幀
byte[] packetArray =new byte[STATE_CONTROL_PACKET_SIZE];
/**
* 用於接收消息,解析數據幀
* @param byteArray
*/
public TransactionStateFrame(byte[] byteArray){
BasicDataTypeTransfer basicDataTypeTransfer=new BaisicDataTypeTransfer();
if(byteArray!=null && byteArray.length>=sizeOfInt){
byte[] typeArray = new byte[sizeOfInt];
//讀出存儲在前面的int型數據
System.arraycopy(byteArray, 0, typeArray, 0, sizeOfInt);
//將其轉換爲整形,從而轉換爲枚舉類型
this.type=basicDataTypeTransfer.ByteArrayToInt(typeArray);
//讀取length
byte[] lenByteArray = new byte[sizeOfInt];
System.arraycopy(byteArray, sizeOfInt, lenByteArray, 0, sizeOfInt);
this.length=basicDataTypeTransfer.ByteArrayToInt(lenByteArray);
//讀取緩衝數據
System.arraycopy(byteArray,2*sizeOfInt, infoArray, 0, infoArray.length);
}
}
/**
* 用於發送消息,將數據包封裝成幀
* @param msgType
* @param packLen
* @param packetArray
*/
public TransactionStateFrame(int msgType,int packLen,byte[] bufferArray){
this.type=msgType;
this.length=packLen;
System.arraycopy(bufferArray, 0, this.infoArray, 0, bufferArray.length);
BasicDataTypeTransfer basicDataTypeTransfer=new BasicDataTypeTransfer();
//cmdType
byte[] cmdTypeByte = basicDataTypeTransfer.IntToByteArray(msgType);
System.arraycopy(cmdTypeByte, 0, packetArray, 0, cmdTypeByte.length);
//packet length
byte[] packLenByte = basicDataTypeTransfer.IntToByteArray(packLen);
System.arraycopy(packLenByte, 0, packetArray, cmdTypeByte.length, packLenByte.length);
//packBuff
if(infoArray!=null){
System.arraycopy(infoArray, 0, packetArray, cmdTypeByte.length+packLenByte.length, infoArray.length);
}
//數據包總大小,4+4+MAX_INFO_SIZE(8*1024)
}
}
}
3.C++將字節數組還原成結構體
這一步就很簡單啦,強制轉換一下就okay。
if(!newsocket.recv((char *)&fileDataFrame,sizeof(fileDataFrame)))
{
std::cout <<"recv error:"<<errno;
}
switch(fileDataFrame.type)
{
case FILE_NAME_TYPE:
{
std::cout <<"file information received";
break;
}
case FILE_DATA_TYPE:
break;
case FILE_END_TYPE:
break;
default:
break;
三、總結
在實際編程中,不同編程語言,如Java、c、c++、delphi所寫的網絡程序進行通訊時,需要進行相應的數據轉換。從不同數據之間的字節存儲方式來理解不同主機和語言的數據通信,將各數據轉換成字節流的形式進行傳輸和解析,是一種非常有效的方式。