开发中的编码问题

起因

昨天一个客户调用接口,使用之前的代码没有问题。但是调用另一个接口就出错。由于服务调用成功,所以感觉是编码问题。


update

字符集与字符编码
1. 完整的表达编码,要有字符集、字符编码、字库表。
2. 字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
3. 字符集即用一个编码值code point来表示一个字符在字库中的位置
字符编码是字符集对应的存储。
一般来说都会直接将code point的值作为编码后的值直接存储。例如在ASCII中A在表中排第65位,而编码后A的数值是0100 0001也即十进制的65的二进制转换结果。

统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。
例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。算的直接一些,同样一块硬盘,用ASCII可以存1500篇文章,而用3字节Unicode序号存储只能存500篇。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。


结论:
参数中的k = v, v可以编码,但是’=’一定要显式给出。因为tomcat是依靠=区分参数。
经验:
utf8错误当做gbk解析,都是问号方块
gbk错误当做utf8解析,都是火星文字
utf8错误当做iso解析, 都是希腊文


var apikey = System.Configuration.ConfigurationManager.AppSettings["APIKey"];
var mobile = String.Join(",", mobileList);
var text = String.Join(",", textList.ConvertAll(t => Uri.EscapeDataString(t)));

//参数必须进行Uri.EscapeDataString编码。以免&#%=等特殊符号无法正常提交
string parameter = "apikey=" + apikey + "&text=" + text + "&mobile=" + mobile;
System.Net.WebRequest req = System.Net.WebRequest.Create(URI_SEND_MULTI_SMS);
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";

byte[] bytes = System.Text.Encoding.UTF8.GetBytes(parameter);//这里编码设置为utf8
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write(bytes, 0, bytes.Length);
os.Close();
System.Net.WebResponse resp = req.GetResponse();
if (resp == null) return null;

using (var myStreamReader = new System.IO.StreamReader(resp.GetResponseStream(), Encoding.UTF8))

现象

  1. 使用客户给的数据自己进行试验,Java代码不能直接看request提交的报文信息,Java调用成功了。
  2. 转而使用哪个C直接提交报文socket通信,发现在服务器端,直接传文本和对value进行URLEncode再传结果是一样的,而我们的需要的text文本信息是URLEncode。
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>

#define SA      struct sockaddr
#define MAXLINE 4096
#define MAXSUB  2000
#define MAXPARAM 2048

#define LISTENQ         1024

extern int h_errno;

int sockfd;
char *hname = "127.0.0.1";
char *send_sms_json = "/v1/sms/send.json";
char *get_user_json = "/v1/user/get.json";

/**
* 发http post请求
*/
ssize_t http_post(char *page, char *poststr)
{
    char sendline[MAXLINE + 1], recvline[MAXLINE + 1];
    ssize_t n;
    snprintf(sendline, MAXSUB,
        "POST %s HTTP/1.0\r\n"
        "Host: %s\r\n"
        "Content-type: application/x-www-form-urlencoded\r\n"
        "Content-length: %zu\r\n\r\n"
        "%s", page, hname, strlen(poststr), poststr);
    printf("%s\n",sendline);
    write(sockfd, sendline, strlen(sendline));
    while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
        recvline[n] = '\0';
        printf("%s", recvline);
    }
    return n;
}

int main(void)
{
    struct sockaddr_in servaddr;
    char **pptr;
    char str[50];
    struct hostent *hptr;

    //建立socket连接
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8080);
    inet_pton(AF_INET, str, &servaddr.sin_addr);

    connect(sockfd, (SA *) & servaddr, sizeof(servaddr));

    //修改为您的apikey
    char *apikey = "a4b3f38430ef7be6704b03181e76e7ca";
    //修改为您要发送的手机号
    char *mobile = "18127813634";
    //设置您要发送的内容
    char *text = "【云片网】您的验证码是1234";
    char *s1 = "apikey=a4b3f38430ef7be6704b03181e76e7ca&mobile=13012312312  &text=【云片网】您的验证码是1234";
    // 仅加密value 传输成功
    char *s2 = "apikey=a4b3f38430ef7be6704b03181e76e7ca&mobile=13012312312  &text=%e3%80%90%e4%ba%91%e7%89%87%e7%bd%91%e3%80%91%e6%82%a8%e7%9a%84%e9%aa%8c%e8%af%81%e7%a0%81%e6%98%af1234";
    // 全部加密,传输失败
    char *s3 = "apikey%3da4b3f38430ef7be6704b03181e76e7ca%26mobile%3d13012312312++%26text%3d%e3%80%90%e4%ba%91%e7%89%87%e7%bd%91%e3%80%91%e6%82%a8%e7%9a%84%e9%aa%8c%e8%af%81%e7%a0%81%e6%98%af1234";
    http_post(send_sms_json,s1);
    close(sockfd);
    exit(0);
}

思考

byte[] bytes 这里设置的UTF8编码并不是URLEncode而是将其按照UTF8转为字节流。
发现的问题:
1. 为什么直接传入字符和编码一次传入结果相同?
2. 编码方式在哪里设置?
3. 到底什么是编码?


实验结果

  1. 用浏览器提交信息都会进行一次URLEncode,而在服务器接收到的信息是没有的。
  2. 在windows平台当文件以gbk保存,服务器接收到的信息是明显的gbk转utf8错误。
  3. 最终发现提交的报文只有是
    text=urlencode(urlencode(text1).join(urlencode(text2))才能正常提交。

原因

  1. 服务器接收过程:servlet容器比如tomcat会将接收到的二进制信息进行解码,这里的字符集使用tomcat配置中的信息。然后会对有%的进行URL解码。

    解释现象

    1. 没有外层编码urlencode(text1).join(urlencode(text2)那么信息直接就会被tomcat解码,所以失败。
    2. 因为我的平台是Mac,默认使用utf8的方式,但如果没有System.Text.Encoding.UTF8.GetBytes(parameter);,c#平台默认是gbk,那样的话会解码出错。
  2. URL编码是将一个字符的字节码用%表示,eg:”中” 的utf8编码 4E2D那么编码就是%4E%2D,gbk 编码是D6D0,那么编码就是%D6%D0。这个和使用的编码有关。

    因此我们确定servlet容器的编解码和字符集有关,tomcat默认是ISO- 8859-1,这样的话在请求的解码过程中信息会丢失。


关于编解码的理解

这里写图片描述
区分存储与编码
对应关系是指映射,eg:abc => ‘中’
程序用一种编码(utf8)进行存储,那么程序在读入时会依据前几个字节判断是什么类型的编码,然后用那种的字符表表示映射关系。可以正常输出(和显示对应起来)。Unicode和gbk可以设定映射关系charset[id]=gbkid. 这样来做到编码转换。编码转换


当这些字节按照iso-8859-1编码时,没有对应关系,所以都缺省为?(期初以为这里的转码会是逐个字节转码,错误!)那么信息就丢失了。


关于Javaweb的编码坑点

URL编码坑点
WEB应用中文字符问题
域名:端口/contextPath/servletPath/pathInfo?queryString
(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没有说明这一点。
(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。
(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。
(4) POST提交的数据是作为request body的一部分。
(5) 网页的Http头中ContentType(“text/html; charset=GBK”)的作用:

(a) 告诉浏览器网页中数据是什么编码;
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。

对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType(“text/html; charset=GBK”)中指定的编码进行对表单中的数据进行编码,然后发给服务器。在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。


HTMLPOST和GET编码问题(深入篇)
1.get提交
对于这种,影响的有tomcat的URIEncoding。
浏览器会根据自己的页面的编码格式作为起始编码格式(右击菜单编码有显示的),把字符使用浏览器的编码格式编码成byte字节进行传输。到了tomcat这里,tomcat会使用URIEncoding进行重新编码(解码),如果tomcat没有配置的话就会使用iso-8859-1对byte进行重新编码(解码)成字符。如果浏览器得编码格式为UTF-8,且tomcat没有配置重新编码(解码)格式的话,就可以使用下面的方式拿到正确的字符了new String(request.getParameter("text").getBytes("iso-8859-1"),"utf-8")上的意思就是说,把刚才的字符,用iso-8859-1进行编码成byte,还原回去,再使用uft-8对byte进行重新编码(解码)成字符。(这个方法就是刚才从浏览器到tomcat过来的逆向过程)

这里为什么可以?要注意这里是针对二进制编解码!而不是字符。如果此时是对utf8存储的汉字进行iso编码则再也回不来。

2.post提交
对于这种情况,response.setCharacterEncoding有影响,当没有对response.setCharacterEncoding设置的时候值为null,则默认采用iso-8859-1来进行重新编码(解码)。
浏览器根据自己页面的编码格式作为起始编码格式,把字符进行编码成byte进行传输,到了tomcat,tomcat不进行干涉其中的重新编码(解码)格式。如果response.getCharacterEncoding为null,那么默认采用iso-8859-1进行重新编码(解码)成字符,如果设置了,就按照设置的编码格式进行重新编码(解码)字符。

jsp:pageEncoding=”GB18030” jsp页面的编码格式,即jsp会被解析成servlet时,采用的编码格式。如果不配置,默认采用iso-8859-1,当jsp文件保存编码类型和pageEncoding不一致时就会出现jsp内部解析乱码。Eclipse现在默认pageEncoding就是文件的编码格式,修改pageEncoding就会修改文件的编码格式。该参数还有一个功能,就是在JSP中不指定contentType参数,也不使用response.setCharacterEncoding方法时指定对服务器响应进行重新编码(解码)的编码,从而pageEncoding会影响浏览器的编码格式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UG二次开发文乱码问题可能由以下原因引起: 1. 字符集设置问题:在UG二次开发时,需要确保使用的字符集与系统或文件本身的字符集一致。如果字符集不匹配,就会导致文乱码问题。因此,可以尝试在代码设置正确的字符集,如UTF-8或GBK。 2. 文件编码问题:如果在UG二次开发涉及到读取或写入文件,需要注意文件的编码格式。如果文件编码格式与读取或写入时指定的格式不匹配,就会导致文乱码问题。可以尝试使用正确的文件编码格式,如UTF-8或GBK。 3. 程序逻辑问题:有时候,文乱码问题可能是由于程序逻辑上的错误导致的。例如,在处理字符串时没有正确地转换字符编码,或者没有正确地设置字符集。需要仔细检查代码涉及到文处理的部分,确保逻辑正确。 4. 环境配置问题:UG二次开发可能需要在特定的开发环境进行,例如Eclipse、Visual Studio等。在安装和配置开发环境时,需要确保正确地设置编码格式和字符集。如果环境配置不正确,就会导致文乱码问题。 为解决文乱码问题,可以按照以下步骤进行操作: 1. 检查代码的字符集设置,确保使用的字符集与系统或文件的字符集一致。 2. 如果涉及到文件操作,检查文件的编码格式,并在读取或写入时指定正确的编码格式。 3. 仔细检查代码文处理部分,确保逻辑正确,包括字符编码的转换和字符集的设置等。 4. 确认开发环境的正确配置,包括编码格式和字符集的设置。 如果以上方法仍未解决文乱码问题,可以参考UG开发文档或向UG开发社区寻求更多帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值