访问Tomcat服务器返回数据乱码

前序:

在网络中,数据的传输,最常用的格式有两种:XML和JSON 。

今天在做一个app版本更新检查。流程是:

1、Andriod客户端 向 Tomcat服务器 发起Http请求。

2、服务器响应并返回数据。返回的数据中,包含了新版app的特性和更新内容。并通过一个Dialog 对话框的形式,来告知用户,新版的app作了那些方面的改进。也就是调用dialog.setMessage()来设置消息内容,结果发现全是乱码。

3、之前一直没遇到这种情况,后来在QQ群了问了才知道,原来这个涉及到了编码的问题。

客户端和服务端发送数据的过程:

先贴个Tomcat webapps目录底下的升级文件内容图:

这个文件,我是直接在桌面新建一个txt文档,然后强制修改文件类型为json的。之后用记事本打开,在里面编写内容。最后经过个人验证,这种做法是很有问题的,也是埋下乱码的一个伏笔。

再贴个乱码的图:

一、先梳理一下:客户端访问网络的流程:

首先一定要清楚的一点:网络传递的是字节流,所以从服务器到android的转换过程如下:

在分析这个图之前。我们先来,理一理app访问网络的一个思路:

1、客户端使用 HttpURLConnection 向服务器请求响应。

2、服务器接收到请求后,响应请求,并返回数据。

3、客户端接收到数据。此时,客户端一般都是接受到一个InputStream对象(输入流)。

4、用InputStream 生产各种对象,最后转成一个String 对象,也就是一个字符串。然后开始对这个字符串进行解析(这时候 XML和JSON 这两个格式 的解析方法 就派上用场了)。

5、数据解析完成后,各个变量拿到自己的对象后。各干各的去了。

但是,大家有没有想过,我们拿到的数据,是用什么编码方式得出了的数据?这也是为什么会出现乱码的关键点了。

需要再清楚的一点:

1、在进行XML 或者JSON 数据解析之前,我们能利用的资源,有且只有一个,也就是服务器返的唯一的一样东西—-InputStream(输入流)。

2、 我们拿到这个输入流之后,经过一连串处理,得到一个字符串。具体怎么处理,等下看代码就行了。

3、 接着根据字符串的格式,xml格式?还是Json格式?进行数据解析。然后拿到我们想要的东西。

好了,废话不多说,我们还是来贴代码吧。

有人说中文是最美的语言,但我认为是最操蛋的语言。因为无论你说什么,都他妈的可以有各种意思!

这里给大家提供一个比较标准的Android访问网络的代码:

private void sendRequestWithHttpURLConnection() {
        //访问网络,首要就是--开启子线程。
        new Thread(new Runnable() {
            @Override
            public void run() {
                //下面4个变量定义在try catch块外面,是因为如果定义在try里面,finally里面就拿不到变量了,
                //关闭不了对象,会造成内存泄露。
                HttpURLConnection connection = null;
                InputStream is = null;
                BufferedReader buffer = null;
                String result = null;
                try {
                    //这里我访问的是我Tomcat的服务器数据。
                    URL url = new URL("http://1r667695p8.iok.la:37179/mydata/get_data.json");
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    is = connection.getInputStream();
                    buffer = new BufferedReader(new InputStreamReader(is));
                    StringBuilder response = new StringBuilder();
                    String line;
                    //打开连接后,子线程会一直死循环读数据,直到null为止。不符合条件,才会往下执行。
                    while ((line = buffer.readLine()) != null) {
                        response.append(line);
                    }
                    //转换为字符串
                    result = response.toString();
                    //字符串解析(用什么方法解析,主要看:字符串是什么格式 xml 还是json格式)
                    parseJSONWithJSONObject(result);
                } catch (Exception e) {
                    Log.d("异常捕获", "http post error");
                } finally {
                     //这几个一定要关闭,否则会造成内存泄漏。
                    if (buffer != null) {
                        try {
                            buffer.close();
                        } catch (IOException ignored) {
                        }
                    }
                    if (is != null) {
                        try {
                            is.close();
                        } catch (IOException ignored) {

                        }
                    }
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

上面的代码,估计大家都很熟悉。但是不知道大家有没有想过这几行代码:

从14行到18行:

connection = (HttpURLConnection) url.openConnection();

connection.setRequestMethod(“GET”);

connection.setConnectTimeout(8000);

connection.setReadTimeout(8000);

is = connection.getInputStream();

我们一起来看一看,14行代码打开一个连接(connection),接着设置一些参数,18行就去接收输入流了。问题来了,我们都知道,程序都是一行一行执行的。而访问网络,肯定也是需要时间的。你凭什么在18行,就可以把输入流赋值给is变量?也就是说CPU执行到

is = connection.getInputStream();

的时候,它怎么保证访问网络都完成了?小编想了想,哦!这就是关系到了Java的封装性了。也就是说当JVM执行到connection对象的方法getInputStream()时候,jvm会进入这个方法,这个方法里面肯定会一直循环。直到不符合某种条件后退出,然后把返回值赋值给is。也就是说

is = connection.getInputStream();

//这行代码是先进入getInputStream()这个方法内执行,至于内部搞不搞死循环我们就不得知了。执行完成后再赋值。

所以大家以后要多用用Android studio 的debug模式,设置断点。跟踪程序怎么走的!!这样才更清晰的知道程序的流程。

//数据解析,代码很简单。

private void parseJSONWithJSONObject(String result) {
        try {
            JSONObject obj = new JSONObject(result);
            String apkUrl = obj.getString("url");                 //APK下载路径
            String updateMessage = obj.getString("updateMessage");//版本更新说明
            int apkCode = obj.getInt("versionCode");              //新版APK对于的版本号
            //取得已经安装在手机的APP的版本号 versionCode
            int versionCode = getCurrentVersionCode();

            //对比版本号判断是否需要更新
            if (apkCode > versionCode) {
                showDialog(updateMessage, apkUrl);
            }

        } catch (JSONException e) {
            LogX.d(TAG, "parse json error");
        }
    }

对话框的方法,下图

private void showDialog(String content, final String downloadUrl) {
        //字符串 content 可能需要先转码
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        builder.setTitle(R.string.dialog_choose_update_title);
        builder.setMessage(content)
                .setPositiveButton(R.string.dialog_btn_confirm_download, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        //下载apk文件
                        goToDownloadApk(downloadUrl);
                    }
                })
                .setNegativeButton(R.string.dialog_btn_cancel_download, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                });

        AlertDialog dialog = builder.create();
        //点击对话框外面,对话框不消失
        dialog.setCanceledOnTouchOutside(false);
        dialog.show();
    }

其中Sting类型的参数content 。就是我们要显示的中文了,只不过它现在还是经过编码的字符串。当我们显示到手机屏幕的时候,Android会帮我们解码。拿到对应的字符集的字。如果我们拿到的不是utf-8的编码,那么此时,我们是需要进行转码的,转成utf-8。然后给Android显示端。 (主要是要明白,解码在那个阶段地方,上面的代码没能体现出转码的地方,因为我们拿到的就是utf-8)

二、现在来谈谈编码方式

1、编码的问题

在谈编码方式之前,大家先来做一个小实验:

大家在电脑上新建一个txt类型的文档,然后双击打开,在里面输入:联通2字 。

接着保存并退出。然后直接双击打开刚才的txt文档,你会发现,咦,怎么是乱码了!!!

这是为什么呢?这就涉及到了编码方式的问题了。这是因为我们的保存和打开的方式并不是同一种。这样的话,解码的过程就出错了,看到的就是乱码了。我们的电脑上都有很多不同格式类型的文件,比如: txt文档、记事本、word文档等等,那么这个编码方式是由谁来确定的呢?是我们系统平台Windows、Linux,还是我们编辑的软件呢。回想一下刚才的小实验,和Android studio 中常用的xml 文件。答案肯定是编辑软件了。

也就是说,我们在保存的时候,当前的编辑软件是先把我们的内容通过编码的方式,打包成ByteArray,然后交给系统,系统帮我们保存数据包。如果是由系统编码,那系统不累死了,而且市场上那么多类型的文件,系统怎么知道你这个软件要使用那一种编码?所以系统的责任只是保存。它不管你里面是什么,只要是数据包就ok。

整个流程,我们可以用下面一个图来描述:

对同一个字符,如果采用了不同字符集来编码,那么生成的值可能是不一样。比如,对同一个中文,采用不同的字符集来编码,得到的数值都不一样。那么解码的时候,如果用另一套字符集,那肯定会乱码。

2、解码的问题

好了,既然知道了保存的时候使用的是某一种编码方式,那么打开的时候,肯定也要使用相应的解码方式,这样才能获得正确的数据。

三、本次乱码的问题的原因

由上面的知识知道,保存的时候会有一个编码的过程,打开的时候会有一个解码的过程。但是,Tomcat并不提供一个打开过程,而是在启动的时候,自动去加载webapps 底下目录的资源。

1、也就是说,这些资源(其实就是ByteArray)是被加载到内存中的,并不像我们直接使用软件去打开,所以我们不能从视觉上,直观的看到加载到内存中的数据有没有变化,或者说存不存在乱码的问题。换句话说,我们并不知道,Tomcat有没有对系统交给它的ByteArray进行解码。

2、Tomcat在响应请求的时候,发送数据之前会不会对自己内存的数据进行编码呢?

换句话说,如果Tomcat在加载资源的时候有进行解码,而它用了却用了自己特有的方式去解码,那么Tomcat加载到内存中的数据本身就已经成乱码了,如果在发送的时候,Tomcat再进行一次编码,那就是乱上加乱,火上浇油了。这样,客户端完全没法解码了!

如果我们能搞清楚上面的两个问题,那么一切都会迎刃而解了。然而,理想是美好的,现实却是残酷的。由于知识所限,我们根本没法验证这些东西。

Tomcat的整个加载流程我们可以这么来描述:

四、问题的解决。

虽然以上的大多数问题,我们都了解了。但是,我们根本就无法去验证。

比如:你写了一个json类型的文件,你怎么知道你当前的编辑软件用的是什么编码格式呢?

如下图所示:

这时候,我想起了,我们Android Studio 中使用的编码方式是utf-8。那么我们可不可以用Android Studio 去打开我们这个json类型的文件呢?之后修改并保存。这样的话,我们保存在系统上的数据,就是采用utf-8这种编码方式了。这时候,只好动手来试一试了。

然后我们用Android Studio去打开我们的json升级文件。

什么鬼,怎么中文全是乱码?到这里,大家应该明白了吧,Android Studio 是使用utf-8 来编码的,解码当然也用utf-8了。换个角度来说,这也证明了,我们之前用记事本保存数据的时候,它的编码并不是utf-8。那么解码方式不对,也就乱码了!所以我们只好在Android Studio打开的方式下,手动修改下。

修改好之后,保存。然后启动我们的Tomcat,再用APP端向服务端申请数据。

耶,终于不是乱码了!

从这个实验中,我们可以得出两个结论:

1、Tomcat在发送数据的时候,是会对数据进行编码的,用的是utf-8编码。因为Android在显示的时候,是一定会对数据进行解码的。如果Tomcat不进行编码,那Android端得到的就是乱码。

2、Tomcat加载资源的时候,肯定是会对资源进行解码的。它的解码方式也是utf-8,为什么说肯定会解码呢,因为不解码的话,数据在内存中做了各种加减乘除运算后,鬼知道你是什么了。

小结:保存数据要编码—加载数据要解码—发送服务要编码—接收数据要解码 。四个环节的方式都要一样,这样才不会乱码。

通过这件事情,我明白了,原来我们系统上保存的资源,都是经过编码的ByteArray

最后给大家一个,android端向服务端post数据时,编码的转换流程图:

张鸿洋的博客:

Android访问服务器(TOMCAT)乱码引发的问题。链接:http://blog.csdn.net/lmj623565791/article/details/21789381

一定要注意这句:服务器(Tomcat)默认使用iso-8859-1解码。Iso-8859-1是不支持中文的,也就是说不做处理,中文是一定乱码的。

这里说的是:解码用的是iso-8859-1,并不是编码。很多人的博客都是乱写的。小编用的是Tomcat6.0。就没有去设置这个玩意。

在TOMCAT的配置文件的server.xml中更改:

时间: 04-21

访问Tomcat服务器返回数据乱码的相关文章

通过ajax访问Tomcat服务器web service接口时出现No 'Access-Control-Allow-Origin' header问题的解决办法

问题描述 通过ajax访问Web服务器(Tomcat7.0.42)中的json web service接口的时候,报以下跨域问题: XMLHttpRequest cannot load http://localhost:8080/get-employees-by-name/name/admin. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhos

ICE学习第四步-----客户端请求服务器返回数据

这次我们来做一个例子,流程很简单:客户端向服务器发送一条指令,服务端接收到这条指令之后,向客户端发送数据库中查询到的数据,最终显示在DataGridView上. 根据上一篇文章介绍的Slice语法,我们先来定义ICE文件.我定义两个ICE文件,一个用来描述测试数据库表中属性相关信息,另一个则是请求数据的方法. 结构如下:    定义结构体,和数据库中表的列对应,添加序列(相当于数组类型). 在获取表的方法中注意要记得#include带有结构的ice文件,并把接口函数的返回值类型写成之前定义的数组

ASP.NET MVC在控制器中分别用Get和post模式调用天气url返回数据乱码

ASP.NET MVC在控制器分别中用Get和POST模式调用天气url返回数据乱码 !!!已解决,分享分享!!! 感谢大神:Daniel Cai 感谢大神:顾晓北 post方法,本人不知道,如果有大神知道,请指导!! //用UTF-8转码有汉字参数的url protected string UrlUtf_8(string url) { byte[] bs = Encoding.GetEncoding("UTF-8").GetBytes(url); StringBuilder sb =

node路由访问,中间件返回数据

node路由访问,中间件返回数据 定义一个变量存放json数据,中间件接受数据 var responseData; router.use(function(req, res, next) { responseData = { code: 0, message: '' }; next(); }); 访问路由,返回相应的数据 router.get('/', function(req, res, next) { responseData.code = 1; responseData.message =

Postman----模拟服务器返回数据

使用场景:在某些情况下,比如A接口还没开发好,我们需要测试B接口,刚好B接口的请求数据中需要包含A接口返回的数据,这时我们就可以模拟A接口服务器返回的数据来测试B接口 解决办法: 举例:模拟此 https://t.app.goodiber.com/api/todo/month-list的服务器返回数据 1.添加一个Examples 点击"Add Example"之后出现的页面如下,根据标出的步骤分别根据实际需要进行填写. 添加完Example返回,可看到页面如下. 2.添加Mock 创

用ajax的同步请求解决登陆注册需要根据服务器返回数据判断是否能提交的问题

最近在写www.doubilaile.com的登陆注册.需要用ajax请求服务器判断用户名是否存在,用户名和密码是否匹配,进而提交数据.碰到的问题是异步请求都能成功返回数据,但是该数据不能作为紧接着的判断的依据.我现在的理解是:异步请求去了服务器端,而本地代码仍在往下执行.服务器数据最终的确会回来,但是本地判断已经执行完毕.所以才会出现密码框单独blur后能提交,而直接submit按钮却要按两次才能提交的问题.将同步改成异步问题解决了.理解是:同步操作会等待服务器数据返回来之后才继续往下执行,所

datatables 服务器返回数据后的处理-表格数据属性的操作方法(ajax.dataSrc)

http://dt.thxopen.com/reference/option/ajax.dataSrc.html http://datatables.net/reference/option/ajax.dataSrc 通过 Ajax,从一个文件获取 JSON 数据,使用 dataSrc属性把 data改为 tableData (比如: { tableData: [ ...data... ] } ) $('#example').dataTable( {   "ajax": {     &

猫猫学iOS之去除服务器返回数据中的html标签,去除指定字符串,替换字符串

猫猫分享,必须精品 原创文章,欢迎转载.转载请注明:翟乃玉的博客 地址:http://blog.csdn.net/u013357243 一:问题 如图中,服务器返回的数据里面有大串的html 但是我们只用字符串,由于不想麻烦后台修改数据....(喵很为别人着想)于是自己想办法解决. 其实解决的方法很多很多..比如用字符串的截取方法的到range,然后根据位置来得到里面的想要的东东..嘎的,想想都崩溃. 还有呢用正则表达式等等...正则表达式,说实话这东西除了面试时候说说和学习时候用过做项目还从来

手机通过笔记本开的WIFI访问TOMCAT服务器站点示例

我一直想用手机连上笔记本上的服务器TOMCAT,尝试了好久没连上,实验室一个妹子会这个技术,我也想学,自己摸索着学了几次,没成功,今晚想个办法试了一下,可以连接了,以后可以做手机网站开发了,这也是移动开发的一部分哦! 1.打开电脑wifi,手机连上此WIFI,然后手机和电脑就在一个网段上了. 2.命令行输入ipconfig回车,可以看到笔记本建立的wifi的IP地址,在手机的WIFI功能里面也可以看到手机的IP,我的测试一下,发现电脑IP是:172.16.222.1, 手机:172.16.222