转载请注明出处: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、协议请求的定义

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

  1. package com.lyz.params;
  2. /**
  3. * 协议请求的定义
  4. * @author liuyazhuang
  5. *
  6. */
  7. public class Request {
  8. /**
  9. * 协议编码
  10. */
  11. private byte encode;
  12. /**
  13. * 命令
  14. */
  15. private String command;
  16. /**
  17. * 命令长度
  18. */
  19. private int commandLength;
  20. public Request() {
  21. super();
  22. }
  23. public Request(byte encode, String command, int commandLength) {
  24. super();
  25. this.encode = encode;
  26. this.command = command;
  27. this.commandLength = commandLength;
  28. }
  29. public byte getEncode() {
  30. return encode;
  31. }
  32. public void setEncode(byte encode) {
  33. this.encode = encode;
  34. }
  35. public String getCommand() {
  36. return command;
  37. }
  38. public void setCommand(String command) {
  39. this.command = command;
  40. }
  41. public int getCommandLength() {
  42. return commandLength;
  43. }
  44. public void setCommandLength(int commandLength) {
  45. this.commandLength = commandLength;
  46. }
  47. @Override
  48. public String toString() {
  49. return "Request [encode=" + encode + ", command=" + command
  50. + ", commandLength=" + commandLength + "]";
  51. }
  52. }

2、响应协议的定义

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

  1. package com.lyz.params;
  2. /**
  3. * 协议响应的定义
  4. * @author liuyazhuang
  5. *
  6. */
  7. public class Response {
  8. /**
  9. * 编码
  10. */
  11. private byte encode;
  12. /**
  13. * 响应内容
  14. */
  15. private String response;
  16. /**
  17. * 响应长度
  18. */
  19. private int responseLength;
  20. public Response() {
  21. super();
  22. }
  23. public Response(byte encode, String response, int responseLength) {
  24. super();
  25. this.encode = encode;
  26. this.response = response;
  27. this.responseLength = responseLength;
  28. }
  29. public byte getEncode() {
  30. return encode;
  31. }
  32. public void setEncode(byte encode) {
  33. this.encode = encode;
  34. }
  35. public String getResponse() {
  36. return response;
  37. }
  38. public void setResponse(String response) {
  39. this.response = response;
  40. }
  41. public int getResponseLength() {
  42. return responseLength;
  43. }
  44. public void setResponseLength(int responseLength) {
  45. this.responseLength = responseLength;
  46. }
  47. @Override
  48. public String toString() {
  49. return "Response [encode=" + encode + ", response=" + response
  50. + ", responseLength=" + responseLength + "]";
  51. }
  52. }

3、编码常量定义

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

  1. package com.lyz.constant;
  2. /**
  3. * 常量类
  4. * @author liuyazhuang
  5. *
  6. */
  7. public final class Encode {
  8. //UTF-8编码
  9. public static final byte UTF8 = 1;
  10. //GBK编码
  11. public static final byte GBK = 2;
  12. }

4、客户端的实现

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

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

5、服务端的实现

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

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

6、ProtocolUtils工具类的实现

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

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

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

7、ByteUtils类的实现

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

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

RPC之——HTTP协议栈的更多相关文章

  1. 简易RPC框架-私有协议栈

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  2. Python爬取CSDN博客文章

    0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.win ...

  3. java笔记--问题总结

    1. 垃圾回收算法 标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和 ...

  4. java后端研发经典面试题总结

    垃圾回收算法 1.标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和清 ...

  5. 基于netty轻量的高性能分布式RPC服务框架forest<上篇>

    工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...

  6. RPC 框架通信原理

    RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据: ...

  7. RPC(Remote Procedure Call Protocol)——远程过程调用协议 学习总结

        首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需 ...

  8. We are doomed, and RPC does not help

    第一种死法:Big ball of Mud 架构里最常用的反面案例是 big ball of mud.很大程度上可以说打格子,把复杂的系统拆解成小格子是架构师最重要的工作.这个小格子有很多种名字,比如 ...

  9. RPC、SQL、NFS属于OSI的哪一层

    第一层:物理层 第二层:数据链路层 802.2.802.3ATM.HDLC.FRAME RELAY 第三层:网络层 IP.IPX.ARP.APPLETALK.ICMP 第四层:传输层 TCP.UDP. ...

随机推荐

  1. HTMLEncode httpencode UTF8Encode

    1.引用单元:  httpApp; 2. 对于 http Post的提交内容,应该是:   HttpEncode(Utf8Encode(StrValue));   不然与web方式的 Url_enco ...

  2. CentOS7下源码安装mysql5.6

    目录 准备工作 运行环境 确认你的安装版本 下载mysql 安装mysql 准备安装环境 编译和安装 配置mysql 单实例配置      单实例配置方法          添加防火墙         ...

  3. Linux进程间通信IPC学习笔记之管道

    基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...

  4. ASP.NET 页面传值得9种方式

    1. Get(即使用QueryString显式传递)     方式:在url后面跟参数.     特点:简单.方便.     缺点:字符串长度最长为255个字符:数据泄漏在url中.     适用数据 ...

  5. Java学习之路:1、HelloWorld

    似乎每种语言都是从HelloWorld开始的,所以,我的第一个java程序,也应该是这样开始的! 1.配置好jdk后,开始编写HelloWorld.java package second;//这个应该 ...

  6. 微信支付配置信息,JSAPI接口,H5调用微信js接口支付,微信公众号支付

    微信支付已经做完了,没接触过微信的我,经历了非常艰难的3天,才把微信支付给做出来,对于专业的人来说,估计就是一小时就搞定的事情了,虽然说做了很长时间,但是确实也学到东西了,也收获了不少,下面跟大家分享 ...

  7. UIApplication深入研究

    我们偶尔会调用这个类的api来实现一些功能,但是这个类是iOS编程中很重要的一个概念,所以总结以下这个类的信息,不对的地方请留言. UIApplication的核心作用是提供了iOS程序运行期间的控制 ...

  8. myeclipse2013 for linux及其破解补丁百度网盘下载

    FQ下载1.1G的东西不是开玩笑的,用GA下载了两回均失败,还是用了某某门在win下下载好的,来之不易,所以特意上传分享给大家,免得FQ.破解文件也一并附上: 注意:本人这个是在原文件基础上bzip2 ...

  9. First Groovy

    class Sample { def names = ["anna", "annie", "tommy", "bobby" ...

  10. 怎样修改Windows7环境变量

    在使用电脑的时候要运行某些特定的应用程序时需要修改系统的环境变量,例如安装JAVA时我们就需要配置系统的环境变量.那什么是环境变量呢?环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,比 ...