java netty微服务demo

本文详细介绍了一个基于Netty的服务端开发案例,包括项目结构、依赖配置、核心代码实现及业务逻辑处理等内容。通过Server.java文件,展示了如何利用Netty进行高效并发处理,特别是针对HTTP请求的处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 项目结构

2. pom依赖

3. 入口代码 NettyInfo 

4. 响应工具类ResponseUtil

5. 锁请求


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());//响应消息
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小钻风巡山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值