半小时实现Java手撸Http协议,爽!(含完整源码)

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

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

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

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

我们接下来用Java实现这个简单的应用层通信协议,说干就干,走起~~

微信图片_20211121153105.jpg

协议请求的定义

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

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协议吧!!

相关文章
|
20天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
56 7
|
1月前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
31 4
|
5天前
|
域名解析 缓存 网络协议
Web基础与HTTP协议
通过掌握这些基础知识和技术,开发者可以更加高效地构建和优化Web应用,提供更好的用户体验和系统性能。
42 15
|
3天前
|
缓存 网络协议 算法
从零开始掌握HTTP协议
本文介绍HTTP协议的演变,从HTTP1.0到HTTP2.0。HTTP1.0为无状态连接,每次请求独立;HTTP1.1引入持久连接、管道化请求和更多状态码;HTTP2.0采用二进制分帧、多路复用、头部压缩及服务器主动推送,大幅提升性能与用户体验。了解这些区别有助于开发者优化应用和服务。
|
12天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
71 13
|
3天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
25 1
|
25天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
53 12
|
1月前
|
Dubbo 安全 应用服务中间件
Apache Dubbo 正式发布 HTTP/3 版本 RPC 协议,弱网效率提升 6 倍
在 Apache Dubbo 3.3.0 版本之后,官方推出了全新升级的 Triple X 协议,全面支持 HTTP/1、HTTP/2 和 HTTP/3 协议。本文将围绕 Triple 协议对 HTTP/3 的支持进行详细阐述,包括其设计目标、实际应用案例、性能测试结果以及源码架构分析等内容。
|
20天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
29天前
|
安全 搜索推荐 网络安全
HTTPS协议是**一种通过计算机网络进行安全通信的传输协议
HTTPS协议是**一种通过计算机网络进行安全通信的传输协议
55 11