浅析java字符编码(在socket游走之间)
来源:互联网 发布:windows经典桌面图片 编辑:程序博客网 时间:2024/05/18 13:23
在引子中,介绍了一些在文件操作时需要注意的java字符编码细节。而在实际工程中,对java字符的操作更多情况下是在网络上以流的方式进行编解码。这篇文章就尝试着记录一些这方面的心得,与大家分享。
一般来说,工程中免不了与第三方厂商或系统交互,有时候你是作为发送方,有时候你是作为接收方。如果不采用类似于MQ这样的消息中间件,那么对于消息的交互细节,有一些是必须注意的。
自己动手,丰衣足食
为了完成一些基本的接收和发送动作,我们大可以自己动手,利用java中的socket api,实现一个如下的客户端和服务端。
Client:
1: InetAddress addr = InetAddress.getLocalHost();
2: int port = 9876;
3: Socket socket = new Socket();
4: SocketAddress socketAddr = new InetSocketAddress(addr, port);
5:
6: String data = "中文123";
7: socket.connect(socketAddr);
8:
9: String path = "/";
10: BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
11: bw.write("POST " + path + " HTTP/1.0\r\n");
12: bw.write("Content-Length: " + data.getBytes("UTF-8").length + "\r\n");
13: bw.write("\r\n");
14:
15: // send request with the data
16: bw.write(data);
17: bw.flush();
18:
19: // get response
20: BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
21: String line = "";
22: while ((line = br.readLine()) != null) {
23: System.out.println(line);
24: }
25: bw.close();
26: br.close();
这里需要注意的是有几点:
1. 第12行Content-Length的大小一定要设置正确,这里的长度是POST正文数据的byte大小。一会联系server端的代码详细解释这个属性。
2. Content-Type作为Http header并不是必需的。有时候我们会发现有些流Content-Type是写着UTF-8,但实际上它的内容不是按照UTF-8编码的。所以这个属性对内容数据并没有约束力。
3. 第10行至第13行用了BufferedWriter进行数据发送,这里可以有多种选择,比如PrintStream。
Server:
刚才说到了Client发送的Content-Length指定了发送内容的byte大小,那么这个值在server端如何取得呢?一般的做法是通过一个BufferedReader对socket.getInputStream()进行adapt,然后就可以用readline()方法一行一行地将头信息读出来,由于Http Header都是ASCII字符,由于无论哪种解码方式Content-Length的值都能正确得到。
接下来读取body信息的方式就有一些陷阱在里面了。
陷阱1. 网络上的字符流不同于文件中固定的字符流,它是没有EOF的。所以什么时候停止是由你来决定的,如果你一直readLine()它是不会终止的。
陷阱2. 刚才已经得到了Content-Length,那么你是不是会尝试以下的代码呢?
1: for (int i = 0; i < contentLength; i++) {
2: sb.append((char) br.read());
这段代码的结果在处理刚才发送的“中文123”时便会读了5个字符后无法往下读,这是由于br是按char来读,而并非byte,那么在读完5个字符后便停止了(但如同readLine()读完后一样,它并不会返回一个EOF)。而contentLength是9。
如果你说为什么不在发送时将contentLength指定为5呢?这就纯粹是为了解决问题而解决问题,更违背了HTTP的RFC规范。
规范的做法是首先不对socket.getInputStream()进行任何的adapt,即不进行decoding,先将body的byte[]数组完整取出来,再对其decoding。
Solution1. 如果socket的inputstream你只需要遍历一遍,那么用inputstream的read()方法先得到contentLength,就能顺利得到body的byte[]数组。
Solution2. 如果socket的inputstream你需要遍历多遍,比如首先要计算body的某一部分的md5值,那么通过socket的inputstream首先构造一个ByteArrayInputStream是更好的选择。不但可以调用mark()和reset()方法,更可以利用其有EOF直接调用readLine()方法。
1: InputStream input = socket.getInputStream();
2: byte[] buffer = new byte[1024];
3: int readNum = input.read(buffer);
4:
5: // wrap a local stream
6: ByteArrayInputStream ins = new ByteArrayInputStream(buffer, 0, readNum);
7: BufferedReader br = new BufferedReader(new InputStreamReader(ins, "UTF-8")); // UTF-8 is necessary
8: String line = "";
9: long contentLength = 0;
10: while ((line = br.readLine()) != null) {
11: if (line.startsWith("Content-Length")) {
12: contentLength = Long.parseLong(line.substring(line.indexOf(':') + 1).trim());
13: }
14: if (line.equals(""))
15: break;
16: }
17:
18: StringBuffer sb = new StringBuffer();
19:
20: while ((line = br.readLine()) != null)
21: sb.append(line);
22:
23: PrintStream out = new PrintStream(socket.getOutputStream());
24: out.println("HTTP/1.0 200 OK");
25: out.println("Content-Type: text/html;charset=UTF-8");
26: out.println(); // blank will end http-header
27: out.write("123中文".getBytes("UTF-8"));
28:
29: socket.close();
几点说明:
1. 第2行到第3行的代码需要些小改动以适应大数据量post的情况。
2. 第23行到第27的代码与Client端利用BufferedWriter输出的情况类似,一些细节可以自行比较。
3. 你可能会注意到一个细节,为什么Client在read response(Client代码20-24行)可以毫无忌惮地使用BufferedReader来读?因为在Client端的read动作是在Server端发出close()动作之后,而这同样可以解释为什么在Server端socket.getInpuStream()没有EOF标志。
下一节将介绍一下client端及server端代表了先进生产力的API,自己制造粗糙的轮子并不是一件好事,嗯。
- 浅析java字符编码(在socket游走之间)
- java字符编码原理浅析
- java字符编码原理浅析
- java字符编码原理浅析
- 浅析Java socket编码解码协议
- 游走在艺术与商业之间
- 浅析字符编码
- 浅析“字符编码”
- 浅析字符编码
- 浅析字符编码
- 在N个IM之间游走,痛并快乐着
- 游走在网管与。net开发之间的我
- 在社交网络之间游走 勿忘保护隐私
- 弗吉尼亚-伍尔夫:在优雅和疯癫之间游走
- C#通过编码在字符和字节之间的转换
- 字符编码 以及在java中注意事项
- java处理字符编码的常见问题(最近在弄字符串设置编码写入文件)
- JAVA Socket超时浅析
- 第十周contest(2)
- java汉字编码
- 窗口关闭setDefaultCloseOperation()的整理
- Gallery
- 一种高效快速的高密度椒盐噪声消除算法(吕宗伟等)
- 浅析java字符编码(在socket游走之间)
- 焦点移到编辑框后让里面内容默认选中,还原从eclipse里拉出来的独立窗口
- Linux C++ 程序内存布局
- java动态代理学习
- JAVA 利用Stack进行加减减乘除运算
- Stepwise Logistic Regression
- 【Cocos2d-x】Cocos2d-x跨Android平台搭建之四:Win7 64位+ eclipse + cocos2dX
- JQuery上传插件Uploadify使用详解
- 第十周上机实践----------1/3-3/5+5/7-7/9---------------+19/21