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、协议请求的定义
协议的请求主要包括:编码、命令和命令长度三个字段。
- package com.lyz.params;
- /**
- * 协议请求的定义
- * @author liuyazhuang
- *
- */
- public class Request {
- /**
- * 协议编码
- */
- private byte encode;
- /**
- * 命令
- */
- private String command;
- /**
- * 命令长度
- */
- private int commandLength;
- public Request() {
- super();
- }
- public Request(byte encode, String command, int commandLength) {
- super();
- this.encode = encode;
- this.command = command;
- this.commandLength = commandLength;
- }
- public byte getEncode() {
- return encode;
- }
- public void setEncode(byte encode) {
- this.encode = encode;
- }
- public String getCommand() {
- return command;
- }
- public void setCommand(String command) {
- this.command = command;
- }
- public int getCommandLength() {
- return commandLength;
- }
- public void setCommandLength(int commandLength) {
- this.commandLength = commandLength;
- }
- @Override
- public String toString() {
- return "Request [encode=" + encode + ", command=" + command
- + ", commandLength=" + commandLength + "]";
- }
- }
2、响应协议的定义
协议的响应主要包括:编码、响应内容和响应长度三个字段。
- package com.lyz.params;
- /**
- * 协议响应的定义
- * @author liuyazhuang
- *
- */
- public class Response {
- /**
- * 编码
- */
- private byte encode;
- /**
- * 响应内容
- */
- private String response;
- /**
- * 响应长度
- */
- private int responseLength;
- public Response() {
- super();
- }
- public Response(byte encode, String response, int responseLength) {
- super();
- this.encode = encode;
- this.response = response;
- this.responseLength = responseLength;
- }
- public byte getEncode() {
- return encode;
- }
- public void setEncode(byte encode) {
- this.encode = encode;
- }
- public String getResponse() {
- return response;
- }
- public void setResponse(String response) {
- this.response = response;
- }
- public int getResponseLength() {
- return responseLength;
- }
- public void setResponseLength(int responseLength) {
- this.responseLength = responseLength;
- }
- @Override
- public String toString() {
- return "Response [encode=" + encode + ", response=" + response
- + ", responseLength=" + responseLength + "]";
- }
- }
3、编码常量定义
编码常量的定义主要包括UTF-8和GBK两种编码。
- package com.lyz.constant;
- /**
- * 常量类
- * @author liuyazhuang
- *
- */
- public final class Encode {
- //UTF-8编码
- public static final byte UTF8 = 1;
- //GBK编码
- public static final byte GBK = 2;
- }
4、客户端的实现
客户端先构造一个request请求,通过Socket接口将其发送到远端,并接收远端的响应信息,并构造成一个Response对象。
- package com.lyz.protocol.client;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.net.Socket;
- import com.lyz.constant.Encode;
- import com.lyz.params.Request;
- import com.lyz.params.Response;
- import com.lyz.utils.ProtocolUtils;
- /**
- * 客户端代码
- * @author liuyazhuang
- *
- */
- public final class Client {
- public static void main(String[] args) throws IOException{
- //请求
- Request request = new Request();
- request.setCommand("HELLO");
- request.setCommandLength(request.getCommand().length());
- request.setEncode(Encode.UTF8);
- Socket client = new Socket("127.0.0.1", 4567);
- OutputStream out = client.getOutputStream();
- //发送请求
- ProtocolUtils.writeRequest(out, request);
- //读取响应数据
- InputStream in = client.getInputStream();
- Response response = ProtocolUtils.readResponse(in);
- System.out.println("获取的响应结果信息为: " + response.toString());
- }
- }
5、服务端的实现
服务端接收客户端的请求,根据接收命令的不同,响应不同的消息信息,如果是“HELLO”命令,则响应“hello”信息,否则响应“bye bye”信息。
- package com.lyz.protocol.server;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- import com.lyz.constant.Encode;
- import com.lyz.params.Request;
- import com.lyz.params.Response;
- import com.lyz.utils.ProtocolUtils;
- /**
- * Server端代码
- * @author liuyazhuang
- *
- */
- public final class Server {
- public static void main(String[] args) throws IOException{
- ServerSocket server = new ServerSocket(4567);
- while (true) {
- Socket client = server.accept();
- //读取请求数据
- InputStream input = client.getInputStream();
- Request request = ProtocolUtils.readRequest(input);
- System.out.println("收到的请求参数为: " + request.toString());
- OutputStream out = client.getOutputStream();
- //组装响应数据
- Response response = new Response();
- response.setEncode(Encode.UTF8);
- if("HELLO".equals(request.getCommand())){
- response.setResponse("hello");
- }else{
- response.setResponse("bye bye");
- }
- response.setResponseLength(response.getResponse().length());
- ProtocolUtils.writeResponse(out, response);
- }
- }
- }
6、ProtocolUtils工具类的实现
ProtocolUtils的readRequest方法将从传递进来的输入流中读取请求的encode、command和commandLength三个参数,进行相应的编码转化,构造成Request对象返回。而writeResponse方法则是将response对象的字段根据对应的编码写入到响应的输出流中。
有一个细节需要重点注意:OutputStream中直接写入一个int类型,会截取其低8位,丢弃其高24位,所以,在传递和接收数据时,需要进行相应的转化操作。
- package com.lyz.utils;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import com.lyz.constant.Encode;
- import com.lyz.params.Request;
- import com.lyz.params.Response;
- /**
- * 协议工具类
- * @author liuyazhuang
- *
- */
- public final class ProtocolUtils {
- /**
- * 从输入流中反序列化Request对象
- * @param input
- * @return
- * @throws IOException
- */
- public static Request readRequest(InputStream input) throws IOException{
- //读取编码
- byte[] encodeByte = new byte[1];
- input.read(encodeByte);
- byte encode = encodeByte[0];
- //读取命令长度
- byte[] commandLengthBytes = new byte[4];
- input.read(commandLengthBytes);
- int commandLength = ByteUtils.byte2Int(commandLengthBytes);
- //读取命令
- byte[] commandBytes = new byte[commandLength];
- input.read(commandBytes);
- String command = "";
- if(Encode.UTF8 == encode){
- command = new String(commandBytes, "UTF-8");
- }else if(Encode.GBK == encode){
- command = new String(commandBytes, "GBK");
- }
- //组装请求返回
- Request request = new Request(encode, command, commandLength);
- return request;
- }
- /**
- * 从输入流中反序列化Response对象
- * @param input
- * @return
- * @throws IOException
- */
- public static Response readResponse(InputStream input) throws IOException{
- //读取编码
- byte[] encodeByte = new byte[1];
- input.read(encodeByte);
- byte encode = encodeByte[0];
- //读取响应长度
- byte[] responseLengthBytes = new byte[4];
- input.read(responseLengthBytes);
- int responseLength = ByteUtils.byte2Int(responseLengthBytes);
- //读取命令
- byte[] responseBytes = new byte[responseLength];
- input.read(responseBytes);
- String response = "";
- if(Encode.UTF8 == encode){
- response = new String(responseBytes, "UTF-8");
- }else if(Encode.GBK == encode){
- response = new String(responseBytes, "GBK");
- }
- //组装请求返回
- Response resp = new Response(encode, response, responseLength);
- return resp;
- }
- /**
- * 序列化请求信息
- * @param output
- * @param response
- */
- public static void writeRequest(OutputStream output, Request request) throws IOException{
- //将response响应返回给客户端
- output.write(request.getEncode());
- //output.write(response.getResponseLength());直接write一个int类型会截取低8位传输丢弃高24位
- output.write(ByteUtils.int2ByteArray(request.getCommandLength()));
- if(Encode.UTF8 == request.getEncode()){
- output.write(request.getCommand().getBytes("UTF-8"));
- }else if(Encode.GBK == request.getEncode()){
- output.write(request.getCommand().getBytes("GBK"));
- }
- output.flush();
- }
- /**
- * 序列化响应信息
- * @param output
- * @param response
- */
- public static void writeResponse(OutputStream output, Response response) throws IOException{
- //将response响应返回给客户端
- output.write(response.getEncode());
- //output.write(response.getResponseLength());直接write一个int类型会截取低8位传输丢弃高24位
- output.write(ByteUtils.int2ByteArray(response.getResponseLength()));
- if(Encode.UTF8 == response.getEncode()){
- output.write(response.getResponse().getBytes("UTF-8"));
- }else if(Encode.GBK == response.getEncode()){
- output.write(response.getResponse().getBytes("GBK"));
- }
- output.flush();
- }
- }
7、ByteUtils类的实现
- package com.lyz.utils;
- /**
- * 字节转化工具类
- * @author liuyazhuang
- *
- */
- public final class ByteUtils {
- /**
- * 将byte数组转化为int数字
- * @param bytes
- * @return
- */
- public static int byte2Int(byte[] bytes){
- int num = bytes[3] & 0xFF;
- num |= ((bytes[2] << 8) & 0xFF00);
- num |= ((bytes[1] << 16) & 0xFF0000);
- num |= ((bytes[0] << 24) & 0xFF000000);
- return num;
- }
- /**
- * 将int类型数字转化为byte数组
- * @param num
- * @return
- */
- public static byte[] int2ByteArray(int i){
- byte[] result = new byte[4];
- result[0] = (byte)(( i >> 24 ) & 0xFF);
- result[1] = (byte)(( i >> 16 ) & 0xFF);
- result[2] = (byte)(( i >> 8 ) & 0xFF);
- result[3] = (byte)(i & 0xFF);
- return result;
- }
- }
至此,我们这个应用层通信协议示例代码开发完成。
RPC之——HTTP协议栈的更多相关文章
- 简易RPC框架-私有协议栈
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- Python爬取CSDN博客文章
0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.win ...
- java笔记--问题总结
1. 垃圾回收算法 标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和 ...
- java后端研发经典面试题总结
垃圾回收算法 1.标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和清 ...
- 基于netty轻量的高性能分布式RPC服务框架forest<上篇>
工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...
- RPC 框架通信原理
RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据: ...
- RPC(Remote Procedure Call Protocol)——远程过程调用协议 学习总结
首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需 ...
- We are doomed, and RPC does not help
第一种死法:Big ball of Mud 架构里最常用的反面案例是 big ball of mud.很大程度上可以说打格子,把复杂的系统拆解成小格子是架构师最重要的工作.这个小格子有很多种名字,比如 ...
- RPC、SQL、NFS属于OSI的哪一层
第一层:物理层 第二层:数据链路层 802.2.802.3ATM.HDLC.FRAME RELAY 第三层:网络层 IP.IPX.ARP.APPLETALK.ICMP 第四层:传输层 TCP.UDP. ...
随机推荐
- 百度地图api的实现
获取客户端IP地址经纬度所在城市 <?php $getIp=$_SERVER["REMOTE_ADDR"]; echo 'IP:',$getIp; echo ''; $con ...
- 1018. Public Bike Management (30)
时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue There is a public bike service i ...
- AJAX 小实例(转摘)
最近老总提了一个小功能,在搜索网吧列表的时候加上网吧所属代理商这个条件,原有的搜索条件是一个地区二级联动,现在需要根据不同的地区显示不同的代理商集合.即在触发地区下拉框的onchange事件时,代理商 ...
- Oracle中SAVEPOINT和ROLLBACK用法
savepoint是事务内部允许部分rollback的标志符.因为事务中对记录做了修改,我们可以在事务中创建savepoint来标识不同的点.如果遇到错误,就可以rollback到不同的点或直接回来事 ...
- PDF合并
要求:将多个table导出到一个PDF里,然后打印. 问题分析:要求将四个table放一个PDF打印,四个table的列各不相同,第一个是表头,其他三个是列表,列比表头多很多,如果直接生成一个exce ...
- SVN 提交必填备注Commit
操作方法:在SVN的Repositories下,找到要配置的项目,在项目目录下找到hooks文件夹,在其下创建pre-commit.bat文件,把下面复制进去就可以了(无需重启,如果改动,保存bat文 ...
- 文档翻译-Minimizing your app's Memory Footprint
原文地址:https://developer.apple.com/library/ios/technotes/tn2434/_index.html#//apple_ref/doc/uid/DTS400 ...
- embed标签遮住div层
依然是上次的那个返工友情项目,当时帮忙用jquery ui写了一个漂浮的投票箱,就是类似点击一个项目然后就收藏到了投票箱中的效果.. 虽然不是很复杂,但是由于页面上有大面积的由kindeditor上传 ...
- Linux学习笔记(5)-进程管理
进程简介 进程是正在执行的一个程序或命令,每一个进程都有自己的地址空间,并占有一定的系统资源.感性的认识,进程就是一个正在运行的程序 进程管理的作用 判断服务器的运行状态 查看系统中有哪些进程 杀死进 ...
- What we learned in Seoul with AlphaGo
What we learned in Seoul with AlphaGo March 16, 2016 Go isn’t just a game—it’s a living, breathing c ...