=Start=
缘由:
Netty 是一个高性能、异步事件驱动的 NIO 框架。在学习 Netty 的时候,发现 Netty 也可以实现HTTP服务,于是把学习的过程记录整理一下形成一篇文章,方便以后参考。
正文:
参考解答:
所有的 Netty 服务器都需要以下2个部分:
- ServerBootstrap :这是配置服务器的启动代码。它会将服务器绑定到它要监听连接请求的端口上;
- 至少一个 ChannelHandler :该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
这里将Netty服务器放在了3个class中,分别是:
- NettyServer #创建ServerBootstrap用于引导服务器
- NettyServerChannel #新建channel并用handler进行初始化(可简化放在NettyServer中)
- NettyServerHandler #通过覆写部分方法实现具体的业务逻辑
Netty服务器的引导过程:
- 创建 ServerBootstrap 的实例来引导和绑定服务器。
- 创建 NioEventLoopGroup 进行事件的处理,包括接收新的连接,处理读写。
- 使用同一个NettyServerHandler实例初始化每一个新的Channel。
- bind()绑定服务器端口进行监听。
package com.ixyzero.learn.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
private static final int port = 8081; //设置服务监听端口
private static ServerBootstrap b = new ServerBootstrap();
private static EventLoopGroup group = new NioEventLoopGroup(); // 通过NIO方式来接收连接和处理连接
/**
* Netty创建全部都是实现自 AbstractBootstrap。
* 客户端的是 Bootstrap,服务端的则是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerChannel()); //为新的连接创建新的channel
// 服务器绑定端口监听
ChannelFuture f = b.bind(port).sync();
System.out.println("服务端启动成功,端口是: "+port);
// 监听服务器关闭监听
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully(); //关闭EventLoopGroup,释放掉所有资源包括创建的线程
}
}
}
&
在上面的NettyServer.java里有一个childHandler()调用,用于添加 NettyServerHandler 到 Channel 的 ChannelPipeline ,是通过新建这里的 NettyServerChannel 类(继承自ChannelInitializer)实现的。
package com.ixyzero.learn.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
public class NettyServerChannel extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
//处理http服务的关键handler
ph.addLast("encoder",new HttpResponseEncoder());
ph.addLast("decoder",new HttpRequestDecoder());
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
ph.addLast("handler", new NettyServerHandler());// 服务端业务逻辑
}
}
&
由于我们的样例很简单,只需要继承 ChannelInboundHandlerAdapter 就行了。这个类提供了默认 ChannelInboundHandler 的实现,所以只需要部分覆写下面的方法:
- channelRead() – 每个信息入站都会调用;
- channelReadComplete() – 通知处理器最后的 channelread() 是当前批处理中的最后一条消息时调用;
- exceptionCaught()- 读操作时捕获到异常时调用;
package com.ixyzero.learn.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
import java.net.InetAddress;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private String result="";
/*
* 收到消息时,返回信息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(! (msg instanceof FullHttpRequest)){
result = "未知请求!";
System.out.println("msg not FullHttpRequest");
send(ctx, result, HttpResponseStatus.BAD_REQUEST);
return;
}
FullHttpRequest httpRequest = (FullHttpRequest)msg;
try{
String path = httpRequest.uri(); //获取路径
System.out.println("URI: " + path);
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);
}
/*
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客户端"+ InetAddress.getLocalHost().getHostName() + "成功与服务端建立连接! ");
super.channelActive(ctx);
}
}
参考链接:
- Netty4 学习笔记之四: Netty HTTP服务的实现#主要参考
- Netty4.0学习笔记系列之二:Handler的执行顺序
- 写一个 echo 服务器
- 基于Netty4构建HTTP服务—-浏览器访问和Netty客户端访问
- 基于Netty的简单HTTP服务例子
- Netty实现Echo服务器和客户端#nice
- (高级篇 Netty多协议开发和应用)第十章-Http协议开发应用(基于Netty的HttpServer和HttpClient的简单实现)
- netty简介及HTTP实现Demo#几个总结不错
- Netty 系列之 Netty 高性能之道
=END=
《 “Netty HTTP服务的实现样例” 》 有 3 条评论
Netty实现Http高性能服务器
https://juejin.im/post/5bbf58fd5188255c877e6892
https://sanshengshui.github.io/2018/10/11/netty%E5%AE%9E%E7%8E%B0%E9%AB%98%E6%80%A7%E8%83%BDHttp%E6%9C%8D%E5%8A%A1%E5%99%A8/
Netty实践学习案例,是Netty初学者及核心技术巩固的最佳实践。
https://github.com/sanshengshui/netty-learning-example
Netty4.0学习笔记系列之二:Handler的执行顺序
https://blog.csdn.net/u013252773/article/details/21195593
Netty学习笔记
https://blog.gmem.cc/netty-study-note
Netty in Action笔记(一)
https://fangjian0423.github.io/2016/08/19/netty-in-action-note1/
Netty in Action笔记(二)
https://fangjian0423.github.io/2016/08/29/netty-in-action-note2/
Netty的单元测试
https://fangjian0423.github.io/2016/09/03/netty-unittest/
Netty channelread confusion
https://stackoverflow.com/questions/29343224/netty-channelread-confusion
# Netty是什么?
Netty 介绍
https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-5pmx2891.html
# Netty有什么特点?
Netty 系列之 Netty 高性能之道
http://wiki.jikexueyuan.com/project/java-special-topic/netty.html
# Netty开发
netty之SimpleChannelInboundHandler
https://blog.csdn.net/u012734441/article/details/76040020
Netty的单元测试
https://fangjian0423.github.io/2016/09/03/netty-unittest/