半小时实现Java手撸Http协议,爽!!(附完整源码,建议收藏)「建议收藏」

半小时实现Java手撸Http协议,爽!!(附完整源码,建议收藏)「建议收藏」冰河就趁着周末,只用了几个Java类就简单的实现了Http协议,爽!!

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

大家好,我是冰河~~

很多小伙伴跟我说,学习网络太难了,怎么办?其实很多技术都是相通的,只要你理解了技术的本质,你自己都可以实现它。这不,冰河就趁着周末,只用了几个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;
import java.io.InputStream;
import java.io.OutputStream;

import com.binghe.constant.Encode;
import com.binghe.params.Request;
import com.binghe.params.Response;

/** * 协议工具类 * @author binghe * */
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();
	}
}

ByteUtils类的实现

package com.binghe.utils;

/** * 字节转化工具类 * @author binghe * */
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;
	}
}

至此,我们这个应用层通信协议示例代码开发完成,怎么样,小伙伴们,是不是很简单呢?赶紧打开你的环境,手撸个Http协议吧!!

写在最后

如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

好了,今天就到这儿吧,小伙伴们点赞、收藏、评论,一键三连走起呀,我是冰河,我们下期见~~
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/172003.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java线程和进程区别

    Java线程和进程区别什么是进程,什么是线程?进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。一个程序至少一个进程,一个进程至少一个线程。进程线程的区别1、地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。2、…

    2022年7月15日
    13
  • 汇编语言的乘法指令(乘法指令和除法指令)

    本文参考了马维华老师的《微机原理与接口技术》一书指令格式:MULREG/MEM;REG寄存器,MEM存储器IMULREG/MEMMUL和IMUL指令分别用于实现无符号数的乘法和有符号数的乘法运算。都只有一个源操作数,可以使寄存器或存储器,而且目标操作数隐含规定在累加器中。1)MUL指令(a)、字节乘法,则AL×R

    2022年4月16日
    116
  • visdom 使用教程

    visdom 使用教程visdom教程visdom安装与启动服务visdom常用功能image窗口:图像显示与更新窗口显示images窗口:多个图像显示与更新窗口显示text窗口:显示文本与更新文本line窗口:绘制折线图与更新折线图scatter窗口:绘制散点图与更新散点图visdom安装与启动服务安装visdompipinstallvisdom打开服务python-mvisdom.server…

    2022年6月24日
    33
  • win10修改视频默认播放器_win10无法更改默认播放器

    win10修改视频默认播放器_win10无法更改默认播放器你要用到WindowsPowerShell,它是win10系统自带的一个应用,要打开它,就单击开始菜单中的“所有应用”,然后找到WindowsPowerShell的文件夹,右键单击WindowsPowerShell(注意不是WindowsPowerShellISE),然后单击以管理员身份运行,就打开了。输入以下:get-appxpackage*zunevideo*|r

    2022年9月1日
    4
  • 基于图同构网络(GIN)的图表征网络的实现

    基于图同构网络(GIN)的图表征网络的实现基于图同构网络的图表征学习主要包含以下两个过程:首先计算得到节点表征;其次对图上各个节点的表征做图池化(GraphPooling),或称为图读出(GraphReadout),得到图的表征(GraphRepresentation)。在这里,我们将采用自顶向下的方式,来学习基于图同构模型(GIN)的图表征学习方法。我们首先关注如何基于节点表征计算得到图的表征,而忽略计算结点表征的方法。基于图同构网络的图表征模块(GINGraphReprModu

    2022年4月8日
    59
  • mysql econnreset_Nodejs 套接字报错处理 Error: read ECONNRESET

    mysql econnreset_Nodejs 套接字报错处理 Error: read ECONNRESET错误信息:Error:readECONNRESETatTCP.onStreamRead(internal/stream_base_commons.js:162:27)出现上述情况一般是客户端关闭了socket连接导致的错误,这个错误会导致程序的异常退出解决办法:varpReq=http.request(options,function(pRes){cSock.writeHead…

    2022年6月17日
    83

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号