目录
1. 项目结构
2. pom依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.77.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
<!--打可执行jar包-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
3. 入口代码 NettyInfo
package org.example;
import com.alibaba.fastjson.JSONObject;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class NettyInfo {
public static void main(String[] args) throws Exception {
ServerBootstrap http = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(4);
NioEventLoopGroup workGroup = new NioEventLoopGroup(4);
try {
http.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<io.netty.channel.socket.SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpRequestDecoder());//请求解码器
pipeline.addLast(new HttpResponseEncoder());//响应编码器
pipeline.addLast(new HttpObjectAggregator(1024 * 1024 * 1024));//请求提最大100M
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new FormatRequestParamHandler());// 解析参数
pipeline.addLast(new CheckPermissionsAndUriHandler());// 逻辑处理
///...
}
});
Channel ch = http.bind(80).sync().channel();//设置访问端口
System.gc();//先回收资源
ch.closeFuture().sync(); // 等待服务端监听到端口关闭
} finally {
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
/**
* 格式化请求参数
*/
public static class FormatRequestParamHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
if (request.uri().equals("/favicon.ico")) {//过滤图标请求
ResponseUtil.responseString(ctx, "ok");
return;
}
Map<String, Object> requestParam = formatMap(request);//格式化请求参数
ctx.fireChannelRead(requestParam);//将数据往下发
//当前类出现异常 调用 exceptionCaught,可实现方法重写
}
/**
* 异常捕获
*
* @param ctx 响应对象
* @param e 异常对象
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
if (!ctx.channel().isActive()) {
System.out.println("主动关闭客户端");
} else {
ResponseUtil.responseString(ctx, e.getMessage());
}
}
/**
* 解析请求参数
*
* @param request 请求对象
* @return 返回请求的参数
* @throws Exception 异常信息
*/
private static Map<String, Object> formatMap(FullHttpRequest request) throws Exception {
Map<String, Object> requestParams = new HashMap<>();
Map<String, String> headers = request.headers().entries().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
requestParams.put("headers", headers);//解析请求头
requestParams.put("uri", request.uri().split("\\?")[0]);//解析请求地址
//解析url后面的参数
Map<String, List<String>> paramList = new QueryStringDecoder(request.uri()).parameters();// 把URI后面的参数串,分割成key-value形式.提取key-value形式的参数串
Map<String, String> urlParams = paramList.entrySet().stream().map(unit -> new HashMap.SimpleEntry<>(unit.getKey(), unit.getValue().get(0))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Map<String, Object> postData = new HashMap<>(urlParams);
requestParams.put("body", postData);
if (HttpMethod.POST.equals(request.method())) {//处理POST请求
postData.putAll(dataFromPost(request));//获取请求体数据到 map
}
return requestParams;
}
/**
* 解析post请求体
*
* @param request 请求对象
* @return 返回请求体参数
* @throws Exception 异常信息
*/
private static Map<String, Object> dataFromPost(FullHttpRequest request) throws Exception {
Map<String, Object> postData = null;
String contentType = request.headers().get("Content-Type");
int index = contentType.indexOf(";");
if (index != -1) contentType = contentType.substring(0, index);
switch (contentType) {
case "application/x-www-form-urlencoded"://普通form表单数据,非multipart形式表单
case "multipart/form-data"://multipart形式表单
postData = formBodyDecode(request);
break;
case "application/json":// json数据
postData = jsonBodyDecode(request);
break;
case "text/plain"://文本
ByteBuf content = request.content();
byte[] reqContent = new byte[content.readableBytes()];
content.readBytes(reqContent);
String text = new String(reqContent, StandardCharsets.UTF_8);
postData = new HashMap<>();
postData.put("text", text);
break;
}
return postData;
}
/**
* 解析表单数据
*
* @param request 请求对象
* @return 返回表单数据组合的Map
* @throws Exception 异常信息
*/
public static Map<String, Object> formBodyDecode(FullHttpRequest request) throws Exception {
Map<String, Object> body = new HashMap<>(); //存放参数对象
HttpDataFactory factory = new DefaultHttpDataFactory(true);//创建HTTP对象工厂
HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(factory, request);//使用HTTP POST解码器
httpDecoder.setDiscardThreshold(0);
httpDecoder.offer(request);//加载对象到加载器。
List<InterfaceHttpData> InterfaceHttpDataList = httpDecoder.getBodyHttpDatas();//通过迭代器获取HTTP的内容
Map<String, FileUpload> files = new HashMap<>();//存放文件对象
for (InterfaceHttpData data : InterfaceHttpDataList) {
if (data == null) continue;
if (InterfaceHttpData.HttpDataType.FileUpload.equals(data.getHttpDataType())) {//如果数据类型为文件类型,则保存到files对象中
FileUpload fileUpload = (FileUpload) data;
files.put(data.getName(), fileUpload);
}
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {//如果数据类型为参数类型,则保存到body对象中
Attribute attribute = (Attribute) data;
body.put(attribute.getName(), attribute.getValue());
}
}
if (!files.isEmpty()) body.put("files", files);
return body;
}
private static Map<String, Object> jsonBodyDecode(FullHttpRequest request) throws Exception {
ByteBuf content = request.content();
byte[] reqContent = new byte[content.readableBytes()];
if (reqContent.length == 0) return null;
content.readBytes(reqContent);
String jsonBody = new String(reqContent, StandardCharsets.UTF_8);
JSONObject json = JSONObject.parseObject(jsonBody);
return json.getInnerMap();
}
}
/**
* 逻辑处理
*/
public static class CheckPermissionsAndUriHandler extends SimpleChannelInboundHandler<Map<String, Object>> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Map<String, Object> msg) throws Exception {
// ResponseUtil.responseStreamMap(ctx, msg);//流式响应
ResponseUtil.responseString(ctx, msg.toString());//响应消息
// ResponseUtil.fileResponse(ctx, null, false);//响应文件
}
}
}
4. 响应工具类ResponseUtil
package org.example;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
/**
* 响应数据的工具类
*/
public class ResponseUtil {
/**
* 直接响应
*
* @param ctx 响应对象
* @param msg 响应内容
*/
public static void responseString(ChannelHandlerContext ctx, String msg) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(CONNECTION, CLOSE);// 如果不是长连接,设置 connection:close 头部
ByteBuf byteBuf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8);
response = response.replace(byteBuf);
HttpUtil.setContentLength(response, response.content().readableBytes());//设置消息请求长度
response.headers().set(CONTENT_TYPE, "application/json; charset=UTF-8");//设置响应头
ChannelFuture writePromise = ctx.channel().writeAndFlush(response); //发送内容
writePromise.addListener(ChannelFutureListener.CLOSE); // 如果不是长连接,发送完成之后,关闭连接
}
/**
* 流式响应
*
* @param ctx 响应对象
* @param msg 响应内容
*/
public static void responseStreamMap(ChannelHandlerContext ctx, Map<String, Object> msg) {
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set("Content-Type", "text/event-stream; charset=UTF-8");//设置内容长度
ctx.write(response);//设置响应格式
for (Map.Entry<String, Object> entry : msg.entrySet()) {
if (!ctx.channel().isActive()) {
System.out.println("主动关闭客户端");
break;
}
ctx.writeAndFlush(Unpooled.copiedBuffer((entry.toString() + "\n").getBytes()));//写入数据
ctx.flush();
}
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
lastContentFuture.addListener(ChannelFutureListener.CLOSE); //发送完成之后,关闭连接
}
/**
* 响应文件
*
* @param ctx 响应对象
* @param file 响应文件
* @param download 是否是下载类型
* @throws Exception
*/
public static void fileResponse(ChannelHandlerContext ctx, File file, boolean download) throws Exception {
file = new File("C:\\Users\\admin\\Desktop\\1\\1.png");
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "r");
long fileLength = raf.length();
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
HttpUtil.setContentLength(response, fileLength);
String contentType = download ? "application/octet-stream" : Files.probeContentType(Paths.get(file.getName()));
response.headers().set(CONTENT_TYPE, contentType);//video/mp4
response.headers().set("Accept-Ranges", "bytes");
response.headers().set("Content-Disposition", download ? "attachment" : "inline" + ";filename=" + file.getName());//下载
ctx.write(response);//设置响应类型
//开始响应文件
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
lastContentFuture.addListener(ChannelFutureListener.CLOSE); //发送完成之后,关闭连接
} finally {
if (raf != null) raf.close();
}
}
}
5. 锁请求
相同的请求不允许重复执行查询逻辑,将重复的请求中的一个用来执行查询,其他的请求使用第一个请求查询获取到的数据
import com.libii.util.ResponseUtil;
import io.netty.channel.ChannelHandlerContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 锁请求, 相同的请求只会执行一次并,并且将相同请求获取的结果返回给每一个请求对象
*
* @param <T> 请求对象类型
*/
@Slf4j
@Data
public class LockModel<T> {
private Map<String, List<T>> requestModels = new HashMap<>();
private Map<String, String> lockMap = new HashMap<>();
/**
* 校验请求是否存在
*
* @param key 请求参数转化唯一标识
* @param model 请求的对象
*/
public boolean checkRequestModel(String key, T model) {
String lock = getLockObj(key);//获取锁对象
totals();
synchronized (lock) {
if (requestModels.containsKey(key)) {
log.info("存在重复的请求,记录并且跳出后需逻辑");
addModel(key, model);
return true;
} else {
log.info("新的请求执行处理逻辑");
createModel(key);
addModel(key, model);
return false;
}
}
}
/**
* 全局锁 所有请求都要锁
*
* @param key 请求锁标识
*/
private synchronized String getLockObj(String key) {
if (!lockMap.containsKey(key)) lockMap.put(key, key);
return lockMap.get(key);
}
public synchronized void addModel(String key, T model) {
requestModels.get(key).add(model);
}
public synchronized void createModel(String key) {
requestModels.put(key, new ArrayList<>());
}
public synchronized List<T> delModel(String key) {
lockMap.remove(key);
return requestModels.remove(key);
}
public synchronized void totals() {
int totals = 0;
for (Map.Entry<String, List<T>> entry : requestModels.entrySet()) {
totals += entry.getValue().size();
}
log.info("累计任务数: {}", totals);
}
public static void main(String[] args) {
ChannelHandlerContext ctx = null;//模拟请求对象
Map<String, Object> requestParam = new HashMap<>();//模拟请求参数
LockModel<ChannelHandlerContext> lockModel = new LockModel<>();
boolean lockStatus = lockModel.checkRequestModel(requestParam.toString(), ctx);
if (lockStatus) return;//已存在
List<ChannelHandlerContext> ctxList = lockModel.delModel(requestParam.toString());//需要设置响应的列表
for (ChannelHandlerContext ctxUnit : ctxList) {
ResponseUtil.responseString(ctxUnit, requestParam.toString());//响应消息
}
}
}