当前位置 博文首页 > 冰河的专栏:半小时实现Java手撸Http协议,爽!!(附完整源码,

    冰河的专栏:半小时实现Java手撸Http协议,爽!!(附完整源码,

    作者:[db:作者] 时间:2021-08-06 09:44

    大家好,我是冰河~~

    很多小伙伴跟我说,学习网络太难了,怎么办?其实很多技术都是相通的,只要你理解了技术的本质,你自己都可以实现它。这不,冰河就趁着周末,只用了几个Java类就简单的实现了Http协议,爽!!小伙伴们点赞,收藏,评论,走起呀~~

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

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

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

    我们接下来用Java实现这个简单的应用层通信协议,说干就干,走起~~
    在这里插入图片描述

    协议请求的定义

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

    package com.binghe.params;
    /**
     * 协议请求的定义
     * @author binghe
     *
     */
    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 + "]";
    	}
    	
    }
    

    响应协议的定义

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

    package com.binghe.params;
    
    /**
     * 协议响应的定义
     * @author binghe
     *
     */
    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 + "]";
    	}
    	
    }
    

    编码常量定义

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

    package com.binghe.constant;
    
    /**
     * 常量类
     * @author binghe
     *
     */
    public final class Encode {
    	//UTF-8编码
    	public static final byte UTF8 = 1;
    	//GBK编码
    	public static final byte GBK = 2;
    }
    

    客户端的实现

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

    package com.binghe.protocol.client;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    import com.binghe.constant.Encode;
    import com.binghe.params.Request;
    import com.binghe.params.Response;
    import com.binghe.utils.ProtocolUtils;
    
    /**
     * 客户端代码
     * @author binghe
     *
     */
    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());
    	}
    }
    

    服务端的实现

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

    package com.binghe.protocol.server;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    import com.binghe.constant.Encode;
    import com.binghe.params.Request;
    import com.binghe.params.Response;
    import com.binghe.utils.ProtocolUtils;
    
    /**
     * Server端代码
     * @author binghe
     *
     */
    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);
    		}
    	}
    }
    

    ProtocolUtils工具类的实现

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

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

    package com.binghe.utils;
    
    import java.io.IOException