【弄nèng - Netty】使用Netty构建HTTP Server服务

之前写过Springboot整合netty,地址如下:
https://blog.csdn.net/yy756127197/article/details/98091432
参考:https://blog.csdn.net/qazwsxpcm/article/details/78364023

文章注释非常详细,就不过多解释。

1. 背景

	由于Netty的HTTP协议栈是基于Netty的IO通信框架开发的,因此,Netty的HTTP协议也是异步非阻塞的。
	Netty的HTTP协议栈无论在性能上还是可靠性上,都表现优异,非常适合在非WEB容器的场景下应用,
	相比与传统的Tomcat,Jetty等WEB容器,它更加轻量和小巧,灵活性和定制性更好。

2. POM

 		<!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.31.Final</version>
        </dependency>

3. 服务端

核心类 HttpNettyServer.java
HttpNettyServer 与之前demo没啥区别

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * @author 司马缸砸缸了
 * @description NETTY服务端 ,HTTP服务
 * @date 2019/8/1 10:10
 */
@Service
@Slf4j
public class HttpNettyServer {
    //boss事件轮询线程组 ,处理连接事件
    private EventLoopGroup bossGroup = new NioEventLoopGroup();
    //worker事件轮询线程组, 用于数据处理
    private EventLoopGroup workerGroup = new NioEventLoopGroup();

    @Autowired
    private HttpNettyServerInitializer nettyServerInitializer;

    @Value("${netty.port}")
    private Integer port;

    /**
     * 开启Netty服务
     *
     * @return
     */
    public void start() {
        try {
            //启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //设置参数,组配置
            serverBootstrap.group(bossGroup, workerGroup)
                    //socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
                    // 如果未设置或所设置的值小于1,Java将使用默认值50。
                    //
                    // 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                    // 建议长连接的时候打开,心跳检测
                    // .childOption(ChannelOption.SO_KEEPALIVE, true)

                    // 构造channel通道工厂//bossGroup的通道,只是负责连接
                    .channel(NioServerSocketChannel.class)
                    // 设置通道处理者ChannelHandlerworkerGroup的处理器
                    .childHandler(nettyServerInitializer);
            // 绑定端口,开始接收进来的连接
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            log.info("netty服务启动: [port:" + port + "]");
            // 等待服务器socket关闭
            // 应用程序会一直等待,直到channel关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("netty服务启动异常-" + e.getMessage());
        } finally {
            // 优雅的关闭服务端
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

初始化类 HttpNettyServerInitializer.java

import com.it.cloud.netty.http.server.HttpNettyServerHandler;
import com.it.cloud.netty.simple.server.AcceptorIdleStateTrigger;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author 司马缸砸缸了
 * @description service初始化配置
 * @date 2019/8/1 10:30
 */

@Component
public class HttpNettyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    private HttpNettyServerHandler nettyServerHandler;

    /**
     * 初始化channel
     */
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //处理http服务编码解码
        pipeline.addLast("http-encoder",new HttpResponseEncoder());
        pipeline.addLast("http-decoder",new HttpRequestDecoder());
        // 将多个消息转换为单一的FullHttpRequest 或FullHttpResponse,
        // 原因是HTTP解码器在每个HTTP消息中会生成多个消息对象
        // 参数 maxContentLength
        pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
        // 支持异步发送大的码流,例如文件传输
        // pipeline.addLast("http-chunked", new ChunkedWriteHandler());

        pipeline.addLast(nettyServerHandler);
    }

}

处理类 HttpNettyServerHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;

/**
 * @author 司马缸砸缸了
 * @description 服务端处理器
 * @date 2019/8/1 10:42
 */
@Slf4j
@Component
// 这个注解适用于标注一个channel handler可以被多个channel安全地共享
// 也可以使用new NettyServerHandler()方式解决
@ChannelHandler.Sharable
public class HttpNettyServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private String result = "";

    /**
     * 收到消息时,返回信息
     *
     * @param ctx
     * @param httpRequest
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
        try {
            if (httpRequest.decoderResult().isFailure()) {
                result = "未知请求!";
                send(ctx, result, HttpResponseStatus.BAD_REQUEST);
                return;
            }

            String path = httpRequest.uri();            //获取路径
            String body = getBody(httpRequest);    //获取参数
            HttpMethod method = httpRequest.method();//获取请求方法
            //如果不是这个路径,就直接返回错误
            if (!"/test".equalsIgnoreCase(path)) {
                result = "非法请求!";
                send(ctx, result, HttpResponseStatus.BAD_REQUEST);
                return;
            }
            System.out.println("接收到:" + method + " 请求");
            //如果是GET请求
            if (HttpMethod.GET.equals(method)) {
                //接受到的消息,做业务逻辑处理...
                System.out.println("body:" + body);
                result = "GET请求";
                send(ctx, result, HttpResponseStatus.OK);
                return;
            }
            //如果是POST请求
            if (HttpMethod.POST.equals(method)) {
                //接受到的消息,做业务逻辑处理...
                System.out.println("body:" + body);
                result = "POST请求";
                send(ctx, result, HttpResponseStatus.OK);
                return;
            }

            //如果是PUT请求
            if (HttpMethod.PUT.equals(method)) {
                //接受到的消息,做业务逻辑处理...
                System.out.println("body:" + body);
                result = "PUT请求";
                send(ctx, result, HttpResponseStatus.OK);
                return;
            }
            //如果是DELETE请求
            if (HttpMethod.DELETE.equals(method)) {
                //接受到的消息,做业务逻辑处理...
                System.out.println("body:" + body);
                result = "DELETE请求";
                send(ctx, result, HttpResponseStatus.OK);
                return;
            }
        } catch (Exception e) {
            System.out.println("处理请求失败!");
            e.printStackTrace();
        } finally {
            //释放请求
            httpRequest.release();
        }
    }

    /**
     * 获取body参数
     *
     * @param request
     * @return
     */
    private String getBody(FullHttpRequest request) {
        ByteBuf buf = request.content();
        return buf.toString(CharsetUtil.UTF_8);
    }

    /**
     * 发送的返回值
     *
     * @param ctx     返回
     * @param context 消息
     * @param status  状态
     */
    private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
    
    /**
     * 客户端去和服务端连接成功时触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        log.info("收到客户端[ip:" + clientIp + "]连接");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 当出现异常就关闭连接
        ctx.close();
        //把客户端的通道关闭
        ctx.channel().close();
    }

}

3. 启动类

我是基于Springboot启动的,也可以直接main函数启动,区别在于 初始化器和处理器的注入就得改成new的方式

import com.it.cloud.netty.http.server.HttpNettyServer;
import com.it.cloud.netty.simple.server.NettyServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class NettyApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(NettyApplication.class, args);
    }

    @Autowired
    private NettyServer nettyServer;

    @Autowired
    private HttpNettyServer httpNettyServer;

    @Override
    public void run(String... args) {
        // B/S服务    
        // nettyServer.start();
        
        // Http服务
        httpNettyServer.start();
    }
}

3. 测试

HTTP测试工具发送Get和Post请求
在这里插入图片描述
控制台响应
在这里插入图片描述


源码地址

传送门
开源项目,持续不断更新中,喜欢请 Star~

项目推荐

IT-CLOUD :IT服务管理平台,集成基础服务,中间件服务,监控告警服务等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值