RPC之——HTTP协议栈

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/52531185

今天,给大家带来一篇稍有深度的文章——《RPC之——HTTP协议栈》,好了,我们进入正题吧。

 HTTP协议属于应用层协议,它构建于TCP和IP协议之上,处于TCP/IP协议架构层的顶端,所以,它不用处理下层协议间诸如丢包补发、握手及数据的分段及重新组装等繁琐的细节,使开发人员可以专注于应用业务。

协议是通信的规范,为了更好的理解HTTP协议,我们可以基于Java的Socket API接口,通过设计一个简单的应用层通信协议,来简单分析下协议实现的过程和细节。

在我们今天的示例程序中,客户端会向服务端发送一条命令,服务端在接收到命令后,会判断命令是否是“HELLO”,如果是“HELLO”, 则服务端返回给客户端的响应为“hello”,否则,服务端返回给客户端的响应为“bye bye”。

我们接下来用Java实现这个简单的应用层通信协议:

 1、协议请求的定义

协议的请求主要包括:编码、命令和命令长度三个字段。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.params;  
  2.   
  3. /** 
  4.  * 协议请求的定义 
  5.  * @author liuyazhuang 
  6.  * 
  7.  */  
  8. public class Request {  
  9.     /** 
  10.      * 协议编码 
  11.      */  
  12.     private byte encode;  
  13.       
  14.     /** 
  15.      * 命令 
  16.      */  
  17.     private String command;  
  18.       
  19.     /** 
  20.      * 命令长度 
  21.      */  
  22.     private int commandLength;  
  23.   
  24.     public Request() {  
  25.         super();  
  26.     }  
  27.   
  28.     public Request(byte encode, String command, int commandLength) {  
  29.         super();  
  30.         this.encode = encode;  
  31.         this.command = command;  
  32.         this.commandLength = commandLength;  
  33.     }  
  34.   
  35.     public byte getEncode() {  
  36.         return encode;  
  37.     }  
  38.   
  39.     public void setEncode(byte encode) {  
  40.         this.encode = encode;  
  41.     }  
  42.   
  43.     public String getCommand() {  
  44.         return command;  
  45.     }  
  46.   
  47.     public void setCommand(String command) {  
  48.         this.command = command;  
  49.     }  
  50.   
  51.     public int getCommandLength() {  
  52.         return commandLength;  
  53.     }  
  54.   
  55.     public void setCommandLength(int commandLength) {  
  56.         this.commandLength = commandLength;  
  57.     }  
  58.   
  59.     @Override  
  60.     public String toString() {  
  61.         return "Request [encode=" + encode + ", command=" + command  
  62.                 + ", commandLength=" + commandLength + "]";  
  63.     }  
  64.       
  65. }  

 

2、响应协议的定义

协议的响应主要包括:编码、响应内容和响应长度三个字段。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.params;  
  2.   
  3. /** 
  4.  * 协议响应的定义 
  5.  * @author liuyazhuang 
  6.  * 
  7.  */  
  8. public class Response {  
  9.     /** 
  10.      * 编码 
  11.      */  
  12.     private byte encode;  
  13.       
  14.     /** 
  15.      * 响应内容 
  16.      */  
  17.     private String response;  
  18.       
  19.     /** 
  20.      * 响应长度 
  21.      */  
  22.     private int responseLength;  
  23.   
  24.     public Response() {  
  25.         super();  
  26.     }  
  27.   
  28.     public Response(byte encode, String response, int responseLength) {  
  29.         super();  
  30.         this.encode = encode;  
  31.         this.response = response;  
  32.         this.responseLength = responseLength;  
  33.     }  
  34.   
  35.     public byte getEncode() {  
  36.         return encode;  
  37.     }  
  38.   
  39.     public void setEncode(byte encode) {  
  40.         this.encode = encode;  
  41.     }  
  42.   
  43.     public String getResponse() {  
  44.         return response;  
  45.     }  
  46.   
  47.     public void setResponse(String response) {  
  48.         this.response = response;  
  49.     }  
  50.   
  51.     public int getResponseLength() {  
  52.         return responseLength;  
  53.     }  
  54.   
  55.     public void setResponseLength(int responseLength) {  
  56.         this.responseLength = responseLength;  
  57.     }  
  58.   
  59.     @Override  
  60.     public String toString() {  
  61.         return "Response [encode=" + encode + ", response=" + response  
  62.                 + ", responseLength=" + responseLength + "]";  
  63.     }  
  64.       
  65. }  

 

3、编码常量定义

编码常量的定义主要包括UTF-8和GBK两种编码。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.constant;  
  2.   
  3. /** 
  4.  * 常量类 
  5.  * @author liuyazhuang 
  6.  * 
  7.  */  
  8. public final class Encode {  
  9.     //UTF-8编码  
  10.     public static final byte UTF8 = 1;  
  11.     //GBK编码  
  12.     public static final byte GBK = 2;  
  13. }  

 

4、客户端的实现

客户端先构造一个request请求,通过Socket接口将其发送到远端,并接收远端的响应信息,并构造成一个Response对象。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.protocol.client;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.net.Socket;  
  7.   
  8. import com.lyz.constant.Encode;  
  9. import com.lyz.params.Request;  
  10. import com.lyz.params.Response;  
  11. import com.lyz.utils.ProtocolUtils;  
  12.   
  13. /** 
  14.  * 客户端代码 
  15.  * @author liuyazhuang 
  16.  * 
  17.  */  
  18. public final class Client {  
  19.     public static void main(String[] args) throws IOException{  
  20.         //请求  
  21.         Request request = new Request();  
  22.         request.setCommand("HELLO");  
  23.         request.setCommandLength(request.getCommand().length());  
  24.         request.setEncode(Encode.UTF8);  
  25.           
  26.         Socket client = new Socket("127.0.0.1", 4567);  
  27.         OutputStream out = client.getOutputStream();  
  28.           
  29.         //发送请求  
  30.         ProtocolUtils.writeRequest(out, request);  
  31.           
  32.         //读取响应数据  
  33.         InputStream in = client.getInputStream();  
  34.         Response response = ProtocolUtils.readResponse(in);  
  35.         System.out.println("获取的响应结果信息为: " + response.toString());  
  36.     }  
  37. }  

 

5、服务端的实现

服务端接收客户端的请求,根据接收命令的不同,响应不同的消息信息,如果是“HELLO”命令,则响应“hello”信息,否则响应“bye bye”信息。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.protocol.server;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.net.ServerSocket;  
  7. import java.net.Socket;  
  8.   
  9. import com.lyz.constant.Encode;  
  10. import com.lyz.params.Request;  
  11. import com.lyz.params.Response;  
  12. import com.lyz.utils.ProtocolUtils;  
  13.   
  14. /** 
  15.  * Server端代码 
  16.  * @author liuyazhuang 
  17.  * 
  18.  */  
  19. public final class Server {  
  20.     public static void main(String[] args) throws IOException{  
  21.         ServerSocket server = new ServerSocket(4567);  
  22.         while (true) {  
  23.             Socket client = server.accept();  
  24.             //读取请求数据  
  25.             InputStream input = client.getInputStream();  
  26.             Request request = ProtocolUtils.readRequest(input);  
  27.             System.out.println("收到的请求参数为: " + request.toString());  
  28.             OutputStream out = client.getOutputStream();  
  29.             //组装响应数据  
  30.             Response response = new Response();  
  31.             response.setEncode(Encode.UTF8);  
  32.             if("HELLO".equals(request.getCommand())){  
  33.                 response.setResponse("hello");  
  34.             }else{  
  35.                 response.setResponse("bye bye");  
  36.             }  
  37.             response.setResponseLength(response.getResponse().length());  
  38.             ProtocolUtils.writeResponse(out, response);  
  39.         }  
  40.     }  
  41. }  

 

6、ProtocolUtils工具类的实现

ProtocolUtils的readRequest方法将从传递进来的输入流中读取请求的encode、command和commandLength三个参数,进行相应的编码转化,构造成Request对象返回。而writeResponse方法则是将response对象的字段根据对应的编码写入到响应的输出流中。

有一个细节需要重点注意:OutputStream中直接写入一个int类型,会截取其低8位,丢弃其高24位,所以,在传递和接收数据时,需要进行相应的转化操作。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.utils;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6.   
  7. import com.lyz.constant.Encode;  
  8. import com.lyz.params.Request;  
  9. import com.lyz.params.Response;  
  10.   
  11. /** 
  12.  * 协议工具类 
  13.  * @author liuyazhuang 
  14.  * 
  15.  */  
  16. public final class ProtocolUtils {  
  17.     /** 
  18.      * 从输入流中反序列化Request对象 
  19.      * @param input 
  20.      * @return 
  21.      * @throws IOException 
  22.      */  
  23.     public static Request readRequest(InputStream input) throws IOException{  
  24.         //读取编码  
  25.         byte[] encodeByte = new byte[1];  
  26.         input.read(encodeByte);  
  27.         byte encode = encodeByte[0];  
  28.           
  29.         //读取命令长度  
  30.         byte[] commandLengthBytes = new byte[4];  
  31.         input.read(commandLengthBytes);  
  32.         int commandLength = ByteUtils.byte2Int(commandLengthBytes);  
  33.           
  34.         //读取命令  
  35.         byte[] commandBytes = new byte[commandLength];  
  36.         input.read(commandBytes);  
  37.         String command = "";  
  38.         if(Encode.UTF8 == encode){  
  39.             command = new String(commandBytes, "UTF-8");  
  40.         }else if(Encode.GBK == encode){  
  41.             command = new String(commandBytes, "GBK");  
  42.         }  
  43.         //组装请求返回  
  44.         Request request = new Request(encode, command, commandLength);  
  45.         return request;  
  46.     }  
  47.     /** 
  48.      * 从输入流中反序列化Response对象 
  49.      * @param input 
  50.      * @return 
  51.      * @throws IOException 
  52.      */  
  53.     public static Response readResponse(InputStream input) throws IOException{  
  54.         //读取编码  
  55.         byte[] encodeByte = new byte[1];  
  56.         input.read(encodeByte);  
  57.         byte encode = encodeByte[0];  
  58.           
  59.         //读取响应长度  
  60.         byte[] responseLengthBytes = new byte[4];  
  61.         input.read(responseLengthBytes);  
  62.         int responseLength = ByteUtils.byte2Int(responseLengthBytes);  
  63.           
  64.         //读取命令  
  65.         byte[] responseBytes = new byte[responseLength];  
  66.         input.read(responseBytes);  
  67.         String response = "";  
  68.         if(Encode.UTF8 == encode){  
  69.             response = new String(responseBytes, "UTF-8");  
  70.         }else if(Encode.GBK == encode){  
  71.             response = new String(responseBytes, "GBK");  
  72.         }  
  73.         //组装请求返回  
  74.         Response resp = new Response(encode, response, responseLength);  
  75.         return resp;  
  76.     }  
  77.       
  78.     /** 
  79.      * 序列化请求信息 
  80.      * @param output 
  81.      * @param response 
  82.      */  
  83.     public static void writeRequest(OutputStream output, Request request) throws IOException{  
  84.         //将response响应返回给客户端  
  85.         output.write(request.getEncode());  
  86.         //output.write(response.getResponseLength());直接write一个int类型会截取低8位传输丢弃高24位  
  87.         output.write(ByteUtils.int2ByteArray(request.getCommandLength()));  
  88.         if(Encode.UTF8 == request.getEncode()){  
  89.             output.write(request.getCommand().getBytes("UTF-8"));  
  90.         }else if(Encode.GBK == request.getEncode()){  
  91.             output.write(request.getCommand().getBytes("GBK"));  
  92.         }  
  93.         output.flush();  
  94.     }  
  95.     /** 
  96.      * 序列化响应信息 
  97.      * @param output 
  98.      * @param response 
  99.      */  
  100.     public static void writeResponse(OutputStream output, Response response) throws IOException{  
  101.         //将response响应返回给客户端  
  102.         output.write(response.getEncode());  
  103.         //output.write(response.getResponseLength());直接write一个int类型会截取低8位传输丢弃高24位  
  104.         output.write(ByteUtils.int2ByteArray(response.getResponseLength()));  
  105.         if(Encode.UTF8 == response.getEncode()){  
  106.             output.write(response.getResponse().getBytes("UTF-8"));  
  107.         }else if(Encode.GBK == response.getEncode()){  
  108.             output.write(response.getResponse().getBytes("GBK"));  
  109.         }  
  110.         output.flush();  
  111.     }  
  112. }  

 

7、ByteUtils类的实现

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.lyz.utils;  
  2.   
  3. /** 
  4.  * 字节转化工具类 
  5.  * @author liuyazhuang 
  6.  * 
  7.  */  
  8. public final class ByteUtils {  
  9.     /** 
  10.      * 将byte数组转化为int数字 
  11.      * @param bytes 
  12.      * @return 
  13.      */  
  14.     public static int byte2Int(byte[] bytes){  
  15.         int num = bytes[3] & 0xFF;  
  16.         num |= ((bytes[2] << 8) & 0xFF00);  
  17.         num |= ((bytes[1] << 16) & 0xFF0000);  
  18.         num |= ((bytes[0] << 24) & 0xFF000000);  
  19.         return num;  
  20.     }  
  21.       
  22.     /** 
  23.      * 将int类型数字转化为byte数组 
  24.      * @param num 
  25.      * @return 
  26.      */  
  27.     public static byte[] int2ByteArray(int i){  
  28.         byte[] result = new byte[4];  
  29.         result[0]  = (byte)(( i >> 24 ) & 0xFF);  
  30.         result[1]  = (byte)(( i >> 16 ) & 0xFF);  
  31.         result[2]  = (byte)(( i >> 8 ) & 0xFF);  
  32.         result[3]  = (byte)(i & 0xFF);  
  33.         return result;  
  34.     }  
  35. }  

至此,我们这个应用层通信协议示例代码开发完成。

posted @ 2016-09-14 17:01  Mason.Ke  阅读(283)  评论(0编辑  收藏  举报