netty简介
netty是一个异步的基于事件驱动的网络框架,用于快速开发可维护的高性能协议的服务器和客户端。
netty是一个NIO客户端服务器框架,它使得网络框架的开发变得更加快速和简单,例如基于协议的服务端和客户端的开发,极大的简化了例如TCP和UDP socket服务等的网络编程。
运行环境
- mac pro
- Itellij Idea
- gradle
- jdk 8
程序所需jar包
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile (
"junit:junit:4.12",
"io.netty:netty-all:4.1.11.Final"
)
}
Netty服务端开发一般流程
- 编写服务端main方法,其中定义两个事件驱动循环组对象 NioEventLoopGroup,然后定义一个服务端启动类ServerBootstrap对象,设置通道(NioServerSocketChannel.class)并且设置childHandler对象,也就是第二步定义的Initializer类对应的对象,绑定端口。
- 新建一个Initializer类,该类一般继承ChannelInitializer
<
SocketChannel>
类。在initChannel方法里面添加对应的handler。 - 新建一个我们自己的handler,在channelRead0方法里面编写从客户端读取消息的处理逻辑。该方法的第二个参数就是从客户端读取的消息。
以上就是netty服务端开发的一般步骤。
简单Http服务的代码
main方法所在的启动类
package com.shengsiyuan.netty.firstexample;
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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* 实现一个http服务
* @Author: zhouwen
* @Date: 2017/6/9 21:49
*/
public class TestServer {
public static void main(String[] args) throws Exception {
//服务端一般都会定义两个循环事件组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//服务端启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new TestServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Initializer编写
package com.shengsiyuan.netty.firstexample;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* http服务的initializer
* @Author: zhouwen
* @Date: 2017/6/9 21:55
*/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//http的编解码
pipeline.addLast("httpServerCodec", new HttpServerCodec());
//处理http请求的handler
pipeline.addLast("testHttpServerHandler", new TestHttpServerHandler());
}
}
自定义的handler编写
package com.shengsiyuan.netty.firstexample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
/**
* http服务的的handler
* @Author: zhouwen
* @Date: 2017/6/9 21:59
*/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
System.out.println(msg.getClass().getName());
if (msg instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) msg;
System.out.println("请求方法名: " + httpRequest.method().name());
//对不同的路径进行处理
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())) {
System.out.println("请求favicon.ico");
}
// 返回的内容
ByteBuf content = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);
// http的响应
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
//响应头设置
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
// 把响应内容写回到客户端
ctx.writeAndFlush(response);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel active!");
super.channelActive(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel registered!");
super.channelRegistered(ctx);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handler add!");
super.handlerAdded(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel inactive!");
super.channelInactive(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel unregistered!");
super.channelUnregistered(ctx);
}
}
启动服务后进行测试
curl 'http://localhost:8899'
只需在命令行工具里面使用curl命令即可发送一个http请求。返回的结果为:
这里与我们在代码里面的返回结果是符合的。再看看控制台的输出:
从控制台也可以看出handler里面定义的事件的执行流程。使用curl命令发送http请求,每次请求完成链接都会断开,所以控制台一定会输出channel inactive和channel unregistered。但是使用浏览器就不一样了,因为我们使用的是http 1.1版本,用浏览器不会立马就断开连接。
打开Google浏览器,输入http://localhost:8899,并且打开调试工具查看,如下图:
发现了浏览器发送了两个请求,这时控制台输出如下:
发现并没有输出channel inactive和channel unregistered。说明浏览器访问的时候,http请求不是立马就断开的,这个是根据keep-alive的一个规则来的。
以上全部,就是一个使用netty开发一个简单的http服务的过程。