数据传输的坑 string & bytearray

571 阅读5分钟

在平时的开发中多多少少涉及 字符的处理

jstring&stringVSQString&string

QString & std::String

【4个字节】如果是以 TxZ[ 开头 【2个字节】后接长度

#include <stdio.h>
#include <unistd.h>
#include <inttypes.h> 
#include <string.h>

// 格式:TxZ[len【两个字节】]
int main(){
    char buf[100] ="TxZ[";
	uint16_t len = 0x111;
	memcpy(buf+4,&len,sizeof(len));  
    buf[4+sizeof(len)]=']';
    buf[4+sizeof(len)+1]='\0';
    printf("size = %lu,data=%s\n",strlen(buf),buf);  // 这里有其他问题,后说
    return 0;
}

现在需要 用QString 进行解析,直接打印看下效果

QString src = QString::fromStdString(buf);
qDebug()<<src;

// 以下就是结果
//"TxZ[\u0011\u0001]"

接下来我们采用QString 取出2个字节的长度

QString len_qstr = src.mid(4,2);
qDebug()<<len_qstr;
uint16_t q_len = (*(uint16_t*)len_qstr.toStdString().c_str());
qDebug("resolve len= %d",q_len);

// 结果
//"\u0011\u0001"
//resolve len= 273

上述表现很正常, 如果我把 长度改掉,这个时候你就会发现 错误了,这就是bug的所在

uint16_t len = 0x1F1;
// 代码不变

// 结果
//"TxZ[?"
//"?"
//resolve len= 49135

所以如果单元测试很重要,要不然悔之晚矣。那要 如何做到通用呢?

QByteArray q_ba = QByteArray(buf,4+sizeof(len)+1);
std::string s_str = std::string(q_ba.data(),q_ba.size());
int s_len = *(uint16_t*)s_str.substr(4,2).c_str();
qDebug("string 的方式 %d",s_len);

// 输出
//string 的方式 497

通过以上可以看出即使修改不同的长度,也能正确取出值。

测试用例如下

0x0 0x1 0x111 0x1F1 0xFF00 0xFFFF

经过上面的验证,不难发现 QString 是对数据做了处理导致 获取不到正确的数值了。

所以平常涉及操作字符数据的时候,需要使用std::string 或者 QByteArray 进行处理。

还有另外的方式

采用 可见字符 进行传输

本例中采用 数字转16进制进行传输

len_length =4;
char len_hex[5]={0};
memset(len_hex,0,sizeof(len_hex));
snprintf(len_hex,sizeof(len_hex),"%4x",len); 
memcpy(buf+4,len_hex,len_length);   		//长度

通过以上的方式进行转换成可见字符,当然这个仅限数字,如果是其他的这种方案行不通的,可能就要涉及base64了。原理就是 转成可见字符

int main(int argc, char *argv[])
{
    char buf[100] ="TxZ[";
    uint16_t len = 0xFFFF;
    int len_length = 0;
#ifdef USE_CHAR
    typedef  uint16_t INT;
    len_length =sizeof(len);
    qDebug("%lu",sizeof(len));
    memcpy(buf+4,&len,sizeof(len));
#else
    typedef  int32_t INT;
    len_length =4;
    char len_hex[5]={0};
    memset(len_hex,0,sizeof(len_hex));
    snprintf(len_hex,sizeof(len_hex),"%4x",len);
    memcpy(buf+4,len_hex,len_length);   		//长度
#endif
    buf[4+len_length]=']';
    buf[4+len_length+1]='\0';
    fwrite(buf,4+len_length+1,1,stdout);

    //////////// 以下使用 QString 方式处理的
    QString src = QString::fromStdString(buf);
    QString len_qstr = src.mid(4,len_length);
    qDebug("使用QString取出的原串%s,裁剪后为%s",src.toStdString().c_str(),len_qstr.toStdString().c_str());
    INT q_len=0;
#ifdef USE_CHAR
    q_len = (*(INT*)len_qstr.toStdString().c_str());
#else
    // 16进制转 10进制
    sscanf(len_qstr.toStdString().c_str(),"%4x",&q_len);
#endif
    qDebug("qString 的方式 len= %d",q_len);
    /////// 以下为使用 std::string 处理的
    QByteArray q_ba = QByteArray(buf,4+len_length+1);
    std::string s_str = std::string(q_ba.data(),q_ba.size());
    INT s_len = 0;
#ifdef USE_CHAR
    s_len = *(INT*)s_str.substr(4,len_length).c_str();
#else
    sscanf(s_str.substr(4,len_length).c_str(),"%4x",&s_len);
#endif
    qDebug("string 的方式 len= %d",s_len);
    return 0;
}

综上,一个原则在c++里面如果做 字符数据的传递 需要使用 QByteArray 来进行传递,QString 会进行处理导致出现问题。

上面有个遗漏的点忘了说了:

fwrite(buf,4+len_length+1,1,stdout);

打印应该用这种方式,虽然会出现乱码,但是比printf 遇到 \0 就结束来的太好。

JString & String

在java层面构造一个 byte数据 ,通过 string 传递和 byte传递 ,来查看区别

public static void main(String[] args) {
        byte[] b = new byte[5];
        b[0]= (byte) 0x00;
        b[1]= (byte) 0x0F;
        b[2]= (byte) 0xF0;
        b[3]= (byte) 0xFF;
        b[4] = ']';

        for (int i = 0; i < b.length; i++) {
            System.out.print(String.format("第%d个",i));
            System.out.print(String.format("数据是:%s",b[i]));
            System.out.println(String.format("十六进制是:%2x",b[i]));
        }
        System.out.println("---------------------");
        String s = new String(b);
        for (int i = 0; i < s.length(); i++) {
            System.out.print(String.format("第%d个",i));
            System.out.print(String.format("数据是:%s",s.charAt(i)));
            System.out.println(String.format("十六进制是:%2x", ((byte) s.charAt(i))));
        }
    }

// 结果
/**
第0个数据是:0十六进制是: 0
第1个数据是:15十六进制是: f
第2个数据是:-16十六进制是:f0
第3个数据是:-1十六进制是:ff
第4个数据是:93十六进制是:5d
---------------------
第0个数据是: 十六进制是: 0
第1个数据是:十六进制是: f
第2个数据是:�十六进制是:fd
第3个数据是:�十六进制是:fd
第4个数据是:]十六进制是:5d
**/

通过上面可以看出 如果采用string包装了 数据,会导致数据的改变,所以如果涉及字符处理需要使用 byte[] 进行数据的传递,才能保持数据的完整性。

在JNI的开发中也是一样的,java层的数据丢到 c/c++里面

 fun dosth(){
     val b:ByteArray = ByteArray(5);
     b[0]=0x00;
     b[1]=0x0F;
     b[2]= 0xF0.toByte();
     b[3]= 0xFF.toByte();
     b[4]= ']'.toByte();

    native2Str(String(b));
    native2byteArray(b);
}

//native
private external fun native2Str(str:String)
private external fun native2byteArray(b:ByteArray)

companion object {
    init {
    	System.loadLibrary("dataprocessor")
    }
}

c++层

#define JNI_BYTE_ARRAY_TO_STRING(_env, _arr, _str) \
do \
{ \
    if (NULL == (_arr))break; \
    jbyte* ___byte_array_data = (_env)->GetByteArrayElements((_arr), NULL); \
    (_str).assign((const char*)___byte_array_data, (_env)->GetArrayLength((_arr))); \
    (_env)->ReleaseByteArrayElements((_arr), ___byte_array_data, 0); \
} while(0)

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_CharsetUtilsActivity_native2byteArray(JNIEnv *env, jobject thiz,
                                                                     jbyteArray b) {
    // TODO: implement native2byteArray()
    size_t length = (size_t) env->GetArrayLength(b);
    jbyte* pBytes = env->GetByteArrayElements(b, NULL);

    std::string str;
    JNI_BYTE_ARRAY_TO_STRING(env,b,str);

    for (int i = 0; i < str.length(); ++i) {
        __android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "第%d字符是:%c,十六进制是%2x\n", i, str[i],*(uint8_t *)&str[i]);
    }

    __android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "分割线-------------------\n");
    for (int i = 0; i < length; ++i) {
        __android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "第%d字符是:%c,十六进制是%2x\n", i, pBytes[i],*(uint8_t *)&pBytes[i]);
    }
}

如果采用jbytearray 进行数据的传递才是正确的,jstring 什么的都是 有问题的。

总结

在做跨端,跨语言传输的时候,尽量采用原有的字节类型进行传递,而不要使用 string 进行传递,否则在对端解析数据的时候会面临解不开的问题。导致浪费青春。

场景:1. 在后台经过 加密给到 客户端的数据,需要在客户端进行解密。

  1. 在jni 开发的时候,比方 protobuffer 这样的数据的时候
  2. 在qt开发的时候, 比方串口通讯的时候。