一. HTTP全称是Hyper Transfer protocol ,即超文本传输协议,当今普遍采用版本Http1.1,HTTP协议已属于应用层协议,它构建在TCP之上,处于TCP/IP架构的顶端,为了更好的理解HTTP协议,我们基于java的SocketAPI设计一个简单的应用层通信协议,来窥探协议实现的一些过程与细节。
客户端向服务端发送一条命令,服务端接收到命令后,会判断命令是否为"HELLO",若是则返回客户端"hello!",否则返回客户端"bye bye"。
1.协议请求:
1 package com.zqf.fenbushi;
2 //协议请求
3 //2016110758 邹奇方
4 public class Request {
5
6 /**
7 * 协议编码 0:GBK;1:UTF-8
8 */
9 private byte encode;
10 /**
11 * 命令
12 */
13 private String command;
14 /**
15 * 命令长度
16 */
17 private int commandLength;
18
19 public byte getEncode() {
20 return encode;
21 }
22
23 public void setEncode(byte encode) {
24 this.encode = encode;
25 }
26
27 public String getCommand() {
28 return command;
29 }
30
31 public void setCommand(String command) {
32 this.command = command;
33 }
34
35 public int getCommandLength() {
36 return commandLength;
37 }
38
39 public void setCommandLength(int commandLength) {
40 this.commandLength = commandLength;
41 }
42
43 }
2.协议响应:
1 package com.zqf.fenbushi;
2 //协议响应
3 //2016110758 邹奇方
4 public class Response {
5 /**
6 * 编码
7 */
8 private byte encode;
9 /**
10 * 响应
11 */
12 private String response;
13 /**
14 * 响应长度
15 */
16 private int responseLength;
17
18 public byte getEncode() {
19 return encode;
20 }
21
22 public void setEncode(byte encode) {
23 this.encode = encode;
24 }
25
26 public String getResponse() {
27 return response;
28 }
29
30 public void setResponse(String response) {
31 this.response = response;
32 }
33
34 public int getResponseLength() {
35 return responseLength;
36 }
37
38 public void setResponseLength(int responseLength) {
39 this.responseLength = responseLength;
40 }
41
42 @Override
43 public String toString() {
44 return "Response [encode=" + encode + ", response=" + response + ", responseLength=" + responseLength + "]";
45 }
46
47 }
3.客户端发送,以及服务端响应处理代码:
1 package com.zqf.fenbushi;
2 //客服端发送,以及服务端响应处理代码
3 //2016110758 邹奇方
4 import java.net.ServerSocket;
5 import java.net.Socket;
6
7
8 public class Server {
9 public static void main(String[] args) throws Exception{
10 System.out.println("开启服务器...");
11 ServerSocket server = new ServerSocket(16789);
12 while(true) {
13 Socket client = server.accept();
14 //读取请求数据
15 Request request = ProtocolUtil.readRequest(client.getInputStream());
16 //封装响应数据
17 Response response = new Response();
18 System.out.println("response"+response);
19 response.setEncode(Encode.UTF8.getValue());
20 response.setResponse(request.getCommand().equals("HELLO") ? "hello!" : "bye bye");
21 response.setResponseLength(response.getResponse().length());
22 //响应到客户端
23 ProtocolUtil.writeResponse(client.getOutputStream(), response);
24 }
25 }
26 }
4.客户端:
1 package com.zqf.fenbushi;
2 //客户端
3 //2016110758 邹奇方
4 import java.net.Socket;
5
6 public class Client {
7
8 public static void main(String[] args) throws Exception {
9 //组装请求数据
10 Request request = new Request();
11 request.setCommand("HELLO");
12 request.setCommandLength(request.getCommand().length());
13 request.setEncode(Encode.UTF8.getValue());
14 Socket client = new Socket("127.0.0.1", 16789);
15 System.out.println(client);
16
17
18 //发送请求
19 ProtocolUtil.writeRequest(client.getOutputStream(), request);
20 //读取相应
21 Response response = ProtocolUtil.readResponse(client.getInputStream());
22 System.out.println(response);
23 }
24 }
5.ProtocolUtil 类:
1 package com.zqf.fenbushi;
2
3 //2016110758 邹奇方
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 public class ProtocolUtil {
8
9 public static void writeRequest(OutputStream out, Request request) {
10 try {
11 out.write(request.getEncode());
12 //write一个int值会截取其低8位传输,丢弃其高24位,因此需要将基本类型转化为字节流
13 //java采用Big Endian字节序,而所有的网络协议也都是以Big Endian字节序来进行传输,所以再进行数据的传输和接收时,需要先将数据转化成Big Endian字节序
14 //out.write(request.getCommandLength());
15 out.write(int2ByteArray(request.getCommandLength()));
16 out.write(Encode.GBK.getValue() == request.getEncode() ? request.getCommand().getBytes("GBK") : request.getCommand().getBytes("UTF8"));
17 out.flush();
18 } catch (Exception e) {
19 System.err.println(e.getMessage());
20 }
21 }
22
23 /**
24 * 将响应输出到客户端
25 * @param os
26 * @param response
27 */
28 public static void writeResponse(OutputStream out, Response response) {
29 try {
30 out.write(response.getEncode());
31 out.write(int2ByteArray(response.getResponseLength()));
32 out.write(Encode.GBK.getValue() == response.getEncode() ? response.getResponse().getBytes("GBK") : response.getResponse().getBytes("UTF8"));
33 out.flush();
34 } catch (Exception e) {
35 System.err.println(e.getMessage());
36 }
37 }
38
39 public static Request readRequest(InputStream is) {
40 Request request = new Request();
41 try {
42 //读取编码
43 byte [] encodeByte = new byte[1];
44 is.read(encodeByte);
45 byte encode = encodeByte[0];
46 //读取命令长度
47 byte [] commandLengthByte = new byte[4];//缓冲区
48 is.read(commandLengthByte);
49 int commandLength = byte2Int(commandLengthByte);
50 //读取命令
51 byte [] commandByte = new byte[commandLength];
52 is.read(commandByte);
53 String command = Encode.GBK.getValue() == encode ? new String(commandByte, "GBK") : new String(commandByte, "UTF8");
54 //组装请求返回
55 request.setEncode(encode);
56 request.setCommand(command);
57 request.setCommandLength(commandLength);
58 } catch (Exception e) {
59 System.err.println(e.getMessage());
60 }
61 return request;
62 }
63
64 public static Response readResponse(InputStream is) {
65 Response response = new Response();
66 try {
67 byte [] encodeByte = new byte[1];
68 is.read(encodeByte);
69 byte encode = encodeByte[0];
70 byte [] responseLengthByte = new byte[4];
71 is.read(responseLengthByte);
72 int commandLength = byte2Int(responseLengthByte);
73 byte [] responseByte = new byte[commandLength];
74 is.read(responseByte);
75 String resContent = Encode.GBK.getValue() == encode ? new String(responseByte, "GBK") : new String(responseByte, "UTF8");
76 response.setEncode(encode);
77 response.setResponse(resContent);
78 response.setResponseLength(commandLength);
79 } catch (Exception e) {
80 System.err.println(e.getMessage());
81 }
82 return response;
83 }
84
85 public static int byte2Int(byte [] bytes) {
86 int num = bytes[3] & 0xFF;
87 num |= ((bytes[2] << 8) & 0xFF00);
88 num |= ((bytes[1] << 16) & 0xFF0000);
89 num |= ((bytes[0] << 24) & 0xFF000000);
90 return num;
91 }
92
93 public static byte[] int2ByteArray(int i) {
94 byte [] result = new byte[4];
95 result[0] = (byte) ((i >> 24) & 0xFF);
96 result[1] = (byte) ((i >> 16) & 0xFF);
97 result[2] = (byte) ((i >> 8) & 0xFF);
98 result[3] = (byte) (i & 0xFF);
99 return result;
100 }
101
102 }
6.Encode类:
1 package com.zqf.fenbushi;
2
3 //2016110758 邹奇方
4 public class Encode {
5
6 private byte enCode; //编码,取值1表示 utf-8编码,取值0表示 gbk编码
7
8
9 public static Encode UTF8 = new Encode("utf-8");
10 public static Encode GBK = new Encode("gbk");
11
12 public Encode(String enCode){
13
14 if(enCode.equals("utf-8")){
15 this.enCode = 1;
16 }
17 if(enCode.equals("gbk")){
18 this.enCode = 0;
19 }
20 }
21
22 public byte getValue(){
23
24 return this.enCode;
25
26 }
27
28 //测试
29 public static void main(String[] args) {
30
31 System.out.println(Encode.UTF8.getValue());
32 System.out.println(Encode.GBK.getValue());
33
34 }
35
36 }
以上代码运行结果:(在客服端输入HELLO,则输出hello,输入不是HELLO,则输出为bye bye)
(此输入为HELLO)
(此输入不是HELLO)
二.HTTP请求与响应
下图是HTTP请求与响应的过程步骤,在此不详细赘述。
三.通过HttpClient发送HTTP请求
HttpClient对HTTP协议通信的过程进行了封装,下面是简单的通过HttpClient发送HTTP GET请求,并获取服务端响应的代码:
1 //url前加上http协议头,标明该请求为http请求 2 String url = "https://www.baidu.com";
3 //组装请求
4 HttpClient httpClient = new DefaultHttpClient();
5 HttpGet httpGet = new HttpGet(url);
6 //接收响应
7 HttpResponse response = httpClient.execute(httpGet);
8 HttpEntity entity = response.getEntity();
9 byte[] byteArray = EntityUtils.toByteArray(entity);
10 String result = new String(byteArray, "utf8");
11 System.out.println(result);
四.使用HTTP协议的优势
随着请求规模的扩展,基于TCP协议的RPC的实现,需要考虑多线程并发、锁、I/O等复杂的底层细节,在大流量高并发的压力下,任何一个小的错误都可能被无限放大,最终导致程序宕机。而对于基于HTTP协议的实现来说,很多成熟的开源web容易已经帮其处理好了这些事情,如Apache,Tomcat,Jboss等,开发人员可将更多的精力集中在业务实现上,而非处理底层细节。