多端点传输杜绝乱码方案

多个端点之前传输字符串(String),实际上是传输字节流(Byte[])。如果发送方采用UTF8编码将字符串变为字节流(Byte[]=String.getBytes("UTF8")),当接收方接受到后,却使用GBK来解码字节流到字符串(String = new String(byte[],"GBK");),那么就会出现乱码。为了避免乱码这种情况,通讯双方需要知道对方的编码格式,或者双方统一编码。

 

本文介绍的方式,是无需定编码的方式,来杜绝乱码,原理就是使用UNICODE这个字符集和UTF16编码格式。本方案的驱动来源于实际工作中遇到3个端点之间的通讯需要相互约定编码格式,导致部署时麻烦而且容易乱码。

 

1 对于JAVA发送端,

发送字符串前,先转为UNICODE文本,具体处理如下:

 

    public static String toUnicodeString(String s){
    	StringBuilder strb = new StringBuilder();
    	char[] c = s.toCharArray();
    	for(int i=0,len=c.length;i<len;i++){
	    	if(c[i] < 128){
	    		strb.append(c[i]);
	    	}else{
	    		strb.append("\\u");
	    		String hex = Integer.toHexString(c[i]);
	    		
	    		if(hex.length()<4){
	    			int padLen = 4-hex.length();//需要左补齐的0
	    			for(int k=0;k<padLen;k++){
	    				hex = "0" + hex;
	    			}
	    		}
	    		strb.append(hex);
	    	}
    	}
    	return strb.toString();
    }

上述代码的原理在于,利用JAVA内存中使用UTF16编码保存字符串的特点,将字符串转换为UNICODE文本。

 

这样子,例如

”中文abc"

 通过如下调用

 

String unicodeStr = toUnicodeString("中文abc");
System.out.println(unicodeStr);

 

变成了

\u4e2d\u6587abc

 注意这个最后的字符串,它仅仅包含0-127编码的基本字符,这些字符在所有的编码格式中,解析和被解析都是一致的,不会存在乱码问题。

 

2 对于JAVA接受端,

在使用接受的UNICODE文本字符串之前,先转换为正确的字符串。

 

    /**
     * 将字符的unicode部分,全部转换成具体字符
     * @param s
     * @return
     */
    public static String parseUnicodeString(String s){
    	StringBuilder rtn = new StringBuilder();
    	for(int i=0,len=s.length();i<len;i++){
    		char c = s.charAt(i);
    		if(c=='\\'){
    			i++;
    			char c1 = s.charAt(i);
    			if(c1 == 'u'){
    				try{
    					char c_chinese = (char) Integer.parseInt(s.substring(i+1,i+1+4),16);
    					rtn.append(c_chinese);
    					i += 4;
    				}catch(Exception e){
    					throw new RuntimeException("parseUnicodeString出错",e);
    				}
    			}else{
    				rtn.append(c).append(c1);
    			}
    		}else{
    			rtn.append(c);
    		}
    	}
    	return rtn.toString();
    }

 这样子,例如

 

\u4e2d\u6587abc

 通过如下调用

 

		String s = parseUnicodeString("\u4e2d\u6587abc");
		System.out.println(s);

 

变成了

”中文abc"

 

3 对于C++发送端

 

std::string Util::toUnicodeString(std::string& strascii)  
{   string rtn = "";
    int widesize = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);  
    wchar_t * wcharBuf = new wchar_t[widesize];
    int convresult = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, wcharBuf, widesize);
	
	for(int i=0;i<widesize-1;i++){
		if(wcharBuf[i]<128){
			rtn += (char)wcharBuf[i];
		}else{
			char hex[6]={0};
			sprintf(hex,"\\u%04x",wcharBuf[i]);
			rtn += hex;
		}
	}
	
	delete [] wcharBuf;
    return rtn;  
} 

关键在于WIDECHAR,由于JAVA本身在内存的字符串就是用WIDECHAR存放,所以节省了一些步骤,但是C++就没有这个特性,所以需要MultiByteToWideChar来将ANSI转换为UTF16格式,最后存放在WIDECHAR。

 

 

 4 对于C++接收端

 

std::string Util::parseUnicodeString(std::string& strUnicodeFormat)  
{  
	string rtn = "";
	wstring wstr;
	wstr.reserve(strUnicodeFormat.length()*2);
	//将ascii char转换为unicode wchar
	for(int i=0;i<strUnicodeFormat.length();i++){
		char c = strUnicodeFormat[i];
		if('\\' == c){
			i++;
			char c1 = strUnicodeFormat[i];
			if('u'==c1){
				string hex = strUnicodeFormat.substr(i+1,4);
				WCHAR wcs[2];
				memset(wcs,0,sizeof(wcs));
				wcs[0] = (WCHAR)(strtol(hex.c_str(),NULL,16));
				wstr.append(wcs);
				i += 4;
			}else{
				WCHAR wcs[2];
				memset(wcs,0,sizeof(wcs));
				wcs[0] = (WCHAR)c;
				
				WCHAR wcs1[2];
				memset(wcs1,0,sizeof(wcs));
				wcs1[0] = (WCHAR)c1;
				
				wstr.append(wcs).append(wcs1);
			}
		}else{
			WCHAR wcs[2];
			memset(wcs,0,sizeof(wcs));
			wcs[0] = (WCHAR)c;
			wstr.append(wcs);
		}
	}
	return UnicodeToANSI(wstr);
} 

 5 优点与缺点

这种方案的好处就是无需担心文本的乱码问题了,利用了UNICODE,简单可靠。

缺点在于

1. 字符串发送和接受,需要一定程度上的额外处理。

2. 传送的字节流也会有一定程度上的增大。这个可以用GZIP去规避。(C++使用zlib,Java使用GzipOutputStream)。

 

没有完美的方案,好方案是权衡轻重后得出的方案。对于本人来说,该方案带来的效率下降并不大,对比起乱码这种严重又纠结的问题(如果端点很多,程序编码的配置就很要命了),带来的得益要多得多;对于部署人员来说,则减少了部署的工作量和压力,皆大欢喜:)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值