最近使用Thrift传输图片数据,一开始使用string保存图片数据,创建的service如下:

点击(此处)折叠或打开

  1. enum RetCode {
  2.   F_Success = 0,
  3.   F_NotFound,
  4.   F_Failed,
  5.   F_LastStatus
  6. }


  7. struct Response {
  8.   1:RetCode ret_code,
  9.   2:string err_msg
  10. }


  11. service FileStorage {
  12. Response WriteFile(1:string file_name, 2:string schema, 3:string write_buffer, 4:i32 length)
  13. }


这里需要介绍以下几个问题: 
1. 
第一个问题:为什么可以通过string传输二进制数据?如果二进制数据中有’\0’字节的话,string不会被截断吗?

        1)先看Thrift生成的接口:

 点击(此处)折叠或打开

  1. class FileStorageIf {
  2.  public:
  3.   virtual ~FileStorageIf() {}
  4.   virtual void WriteFile(Response& _return, const std::string& file_name, const std::string& schema, const std::string& write_buffer, const int32_t length) = 0;
  5. };
    2) Thrift客户端是将数据写到socket的:
    
客户端通过FileStorage_WriteFile_args::write将各个参数传给TBufferedTransport
    最后调用下面的函数将数据写到socket

oprot_->getTransport()->flush();

点击(此处)折叠或打开

  1. void FileStorageClient::send_WriteFile(const std::string& file_name, const std::string& schema, const std::string& write_buffer, const int32_t length)
  2. {
  3.   int32_t cseqid = 0;
  4.   oprot_->writeMessageBegin("WriteFile", ::apache::thrift::protocol::T_CALL, cseqid);

  5.   FileStorage_WriteFile_pargs args;
  6.   args.file_name = &file_name;
  7.   args.schema = &schema;
  8.   args.write_buffer = &write_buffer;
  9.   args.length = &length;
  10.   args.write(oprot_);

  11.   oprot_->writeMessageEnd();
  12.   oprot_->getTransport()->writeEnd();
  13.   oprot_->getTransport()->flush();
  14. }

  15. uint32_t FileStorage_WriteFile_args::write(::apache::thrift::protocol::TProtocol* oprot) const {
  16.   uint32_t xfer = 0;
  17.   ……
  18.   xfer += oprot->writeFieldBegin("write_buffer", ::apache::thrift::protocol::T_STRING, 3);
  19.   xfer += oprot->writeString(this->write_buffer);
  20.   xfer += oprot->writeFieldEnd();
  21.   ……
  22.   return xfer;
  23. }

    下面看writeString方法的实现:
        
首先通过str.size()得到string的长度,先将长度写入TBufferedTransportbuffer
        
然后将str.data()写入buffer

点击(此处)折叠或打开

  1. template <class Transport_>
  2. uint32_t TBinaryProtocolT<Transport_>::writeString(const std::string& str) {
  3.   uint32_t size = str.size();
  4.   uint32_t result = writeI32((int32_t)size);
  5.   if (size > 0) {
  6.     this->trans_->write((uint8_t*)str.data(), size);
  7.   }
  8.   return result + size;
  9. }
    3) str.size()str.data()可以得到这个二进制字符串的长度和完整数据吗?

问题:如果二进制数据中有’\0’字节的话,string不会被截断吗?

答案是string不会截断字符串,而char*会截断字符串。下面是测试程序:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <string>
  3. #include <iostream>

  4. using namespace std;


  5. int main(int __argc, char* __argv[]) {
  6.   char char_buf[] = {'a', 'b', 'c', 'd', '\0', 'e'};
  7.   string str1 = char_buf;
  8.   string str2;
  9.   str2.assign(char_buf);
  10.   string str3;
  11.   str3.assign(char_buf, sizeof(char_buf));
  12.   cout << "str1: " << str1.size() << endl;
  13.   cout << "str2: " << str2.size() << endl;
  14.   cout << "str3: " << str3.size() << endl;
  15.   return 0;
  16. }
     运行结果如下:

guojun8@guojun8-desktop:~/test/string$ g++ -o test main.c

guojun8@guojun8-desktop:~/test/string$ ./test

str1: 4

str2: 4

str3: 6

string类型中存放有数据的长度和数据(数据时buffer而不是字符串),当通过下面的方法设置数据时,会根据传递的长度复制数据并设置长度;

              string& assign (const char* s, size_t n); 
 
    2. 当客户端使用java调掉并传入String时,会出现数据格式错误的问题。
我昨天遇到这个问题,用java调用这个接口并写入图片数据时,在服务端收到数据并检查是总是报数据不合法,不是图片数据,但是用PythonPHP写入数据都没有这个问题。我一起没有学过java,但为了弄清这个问题还是看了一下,下面分析一下这个问题:
1) Thrift文件生成的Java客户端代码:
    使用Thrift生成的Java接口代码如下:
	

点击(此处)折叠或打开

  1. public class FileStorage {
  2.   public interface Iface {
  3.     public Response WriteFile(String file_name, String schema, String write_buffer, int length) throws org.apache.thrift.TException;
  4.   }
    2) 客户端的代码:  

点击(此处)折叠或打开

  1. InputStream in = new FileInputStream("C:/temp/photo4l.jpg");
  2. data = new byte[in.available()];
  3. in.read(data);
  4. in.close();
  5. String str = new String(data);
  6. Response result = client.WriteFile(file_name, schema, str, in.available());
    客户端的代码实现是从文件中读取图片数据并将数据转为String,并传输到服务端。但是服务端收到数据解析永远是bad_image_format.
3)问题分析
下面看String的构造函数说明:
 public String(byte[] bytes)
Constructs a new String by decoding the specified array of bytes using the platform's default charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array. 
The behavior of this constructor when the given bytes are not valid in the default charset is unspecified. The CharsetDecoder class should be used when more control over the decoding process is required.
String的构造函数用当前的默认字符编码解码字节数组,当字节数组不合法时没有处理。而图片数据的字节数组不可能满足任何一种字符集的解码操作,所以这里生成的String未定义,全是乱码。   
 另外JavaTBinaryProtocol::writeString的实现:
											

点击(此处)折叠或打开

  1. public void writeString(String str) throws TException {
  2.     try {
  3.       byte[] dat = str.getBytes("UTF-8");
  4.       writeI32(dat.length);
  5.       trans_.write(dat, 0, dat.length);
  6.     } catch (UnsupportedEncodingException uex) {
  7.       throw new TException("JVM DOES NOT SUPPORT UTF-8");
  8.     }
  9.   }
     str.getBytes("UTF-8");String编码成UTF-8格式的byte数组,并写到buffer中,如果String的数据是乱码,编码生成的data也是乱码,所以传到服务端的数据也是乱码。
 4) 问题解决
    使用PythonPHPC++调用这个接口时都不会出现这个问题,因为string类型默认不会对数据做编解码,保存的是原始的字节数组格式。

Thrift中提供了binary类型用于解决这个问题,将thrift文件中的write_buffer改成binary类型,如下:

点击(此处)折叠或打开

  1. service FileStorage {
  2. Response WriteFile(1:string file_name, 2:string schema, 3:binary write_buffer, 4:i32 length)
  3. }

生成的Java接口中write_bufferByteBuffer:

点击(此处)折叠或打开

  1. public class FileStorage {
  2.   public interface Iface {
  3.     public Response WriteFile(String file_name, String schema, ByteBuffer write_buffer, int length) throws org.apache.thrift.TException;
  4.   }


 

 

12-17 00:40