之前写过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服务管理平台,集成基础服务,中间件服务,监控告警服务等