介绍
Netty 的 HTTP 协议栈无论是在性能上还是可靠性上,都表现优异,非常适合非 Web 容器的场景下应用,相比于传统的 Tomcat,Jetty 等 Web 容器,它更加轻量和小巧,并且灵活性和定制性也很好.下面我们看下 Netty 的核心的 API,并手动实现两个功能建立一个更深的认识。
buffer
顾名思义为缓冲区的意思,主要与 Channel 进行交互,数据是从 Channel 读入缓冲区,再从缓冲区写入 Channel 中
flip()
该方法的主要作用是:反转此缓冲区,将 position 给 limit,然后将 position 置为 0(其实就是切换读写模式)
clear()
清除此缓冲区,将 position 置为 0,把 capacity 的值给 limit
rewind()
重绕此缓冲区,将 position 位置置为 0
directByteBuffer
可以减少一次系统空间到用户空间的拷贝。但 Buffer 的创建和销毁成本更高,不可控,通常会用内存池来提高性能。直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型并且持久的缓冲区。如果在数据量比较小的中小应用情况下,可以考虑使用 heapBuffer 由 JVM 进行管理。
channel()
表示 I/O 源与目标打开的连接,是双向的,但是不能直接访问数据,只能与 Buffer 进行交互。通过源码我们可以看到 FileChannel 的 read 方法和 write 方法都导致了数据复制了两次。
selector
可使一个单独的线程管理多个 Channel
- open 方法可以创建 Selector
- register 方法向多路复用器注册通道,可以用来监听事件(包括:读、写、连接、accept),注册事件后会产生一个 SelectionKey;它表示 SelectableChannel 和 Selector 之间的注册关系。
- wakeup 方法:使尚未返回的第一个选择操作立即返回。唤醒的原因是注册了新的 Channel 或者事件 Channel 关闭,取消注册。(优先级更高的事件触发,比如定时器事件,希望可以被及时处理)
selector的作用
Selector 在 Linux 的实现类是 EPollSelectorImpl,委托给 EPollArrayWrapper 实现,其中三个 native 方法是对 epoll 的封装,而 EpollSelectorImpl.register 方法,通过调用 epoll_ctl 向 epoll 实例中注册事件,还将注册的文件描述符(fd)与 SelectionKey 的对应关系添加到 fdToKey 中,这个 map 维护了文件描述符与 SelectionKey 的映射。 fdToKey 有时会变得非常大,因为注册到 Selector 上的 Channel 非常多(百万连接),如果过期或失效的 Channel 没有及时关闭。fdToKey 总是串行读取的,而读取是在 select 方法中进行 的,该方法是非线程安全的。
pipe
两个线程之间的单向数据连接,数据会被写到 sink 通道,从 source 通道读取。
功能需求
- Netty 服务器在 8080 端口监听
- 浏览器发出请求 "http://localhost:8080/"
- 服务器可以回复消息给客户端 "Hello!我是 Netty 服务器",并对特定请求资源进行过滤
实现
public class NettyHttpServer {
//端口号
private int port;
public NettyHttpServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
//1. 创建bossGroup线程组: 处理网络事件--连接事件
EventLoopGroup bossGroup = null;
//2. 创建workerGroup线程组: 处理网络事件--读写事件 2*处理器线程数
EventLoopGroup workerGroup = null;
try {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
//3. 创建服务端启动助手
ServerBootstrap serverBootstrap = new ServerBootstrap();
//4. 设置bossGroup线程组和workerGroup线程组
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) //5. 设置服务端通道
// 实现为NIO
.option(ChannelOption.SO_BACKLOG, 128)//6. 参数设置
.childOption(ChannelOption.SO_KEEPALIVE,
Boolean.TRUE)//6. 参数设置
.childHandler(new ChannelInitializer<SocketChannel>() {
//7. 创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel ch) throws
Exception {
//8. 向pipeline中添加自定义业务处理handler
//添加编解码器
ch.pipeline().addLast(new HttpServerCodec());
// 添加自定义的业务处理类
ch.pipeline().addLast(new NettyHTTPServerHandler());
}
});
//9. 启动服务端并绑定端口,同时将异步改为同步
ChannelFuture future = serverBootstrap.bind(port);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws
Exception {
if (future.isSuccess()) {
System.out.println("端口绑定成功!");
} else {
System.out.println("端口绑定失败!");
}
}
});
System.out.println("HTTP服务端启动成功.");
//10. 关闭通道(并不是真正意义上关闭,而是监听通道关闭的状态)和关闭连接池
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new NettyHttpServer(8080).run();
}
}
public class NettyHTTPServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/**
* 读取就绪事件
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 判断请求是不是http请求
if(msg instanceof HttpRequest){
DefaultHttpRequest request = (DefaultHttpRequest) msg;
System.out.println("浏览器请求路径:"+request.uri());
if("/favicon.ico".equals(request.uri())){
System.out.println("图标不响应");
return;
}
// 给浏览器进行响应
ByteBuf byteBuf = Unpooled.copiedBuffer("Hello!我是 Netty 服务器", CharsetUtil.UTF_8);
DefaultHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
// 设置响应头
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=utf-8").set(HttpHeaderNames.CONTENT_LENGTH,byteBuf.readableBytes());
ctx.writeAndFlush(response);
}
}
}
基于Netty的WebSocket开发网页版聊天室
WebSocket 介绍
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 WebSocket 应用场景十分广泛:
- 社交订阅
- 协同编辑/编程
- 股票基金报价
- 体育实况更新
- 多媒体聊天
- 在线教育
WebSocket 和 HTTP 的区别
HTTP 协议是用在应用层的协议,他是基于 TCP 协议的,HTTP 协议建立连接也必须要有三次握手才能发送信息。其中HTTP 连接分为短连接,长连接。短连接是每次请求都要三次握手才能发送自己的信息。即每一个 request 对应一个 response。长连接是在一定的期限内保持连接,保持 TCP 连接不断开。 客户端与服务器通信,必须要由客户端先发起,然后服务器返回结果。客户端是主动的,服务器是被动的。 客户端要想实时获取服务端消息就得不断发送长连接到服务端。
而 WebSocket 实现了多路复用,他是全双工通信。在 WebSocket 协议下服务端和客户端可以同时发送信息。建立了 WebSocket 连接之后, 服务端可以主动发送信息到客户端。而且信息当中不必在带有 head 的部分信息了与 HTTP 的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。