编写一个应答服务器
编写一个应答服务器
写一个Netty服务器主要由两部分组成:
- 配置服务器功能,如线程、端口
- 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
启动服务器
通过创建ServerBootstrap对象来启动服务器,然后配置这个对象的相关选项,如端口、线程模式、事件循环,并且添加逻辑处理程序用来处理业务逻辑
package netty.example;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
//create ServerBootstrap instance
erverBootstrap b = new ServerBootstrap();
//Specifies NIO transport, local socket address
//Adds handler to channel pipeline
b.group(group).channel(NioServerSocketChannel.class).localAddress(port)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler());
}
});
//Binds server, waits for server to close, and releases resources
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() + "started and listen on “" + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new EchoServer(65535).start();
}
- HTTP消息聚合
处理HTTP时可能接收HTTP消息片段,Netty需要缓冲直到接收完整个消息。要完成的处理HTTP消息,并且内存开销也不会很大,Netty为此提供了HttpObjectAggregator。通过HttpObjectAggregator,Netty可以聚合HTTP消息,使用FullHttpResponse和FullHttpRequest到ChannelPipeline中的下一个ChannelHandler,这就消除了断裂消息,保证了消息的完整。
通常,使用到的基本语句如下:
pipeline.addLast("aggegator", new HttpObjectAggregator(512 * 1024));
- HTTP压缩
使用HTTP时建议压缩数据以减少传输流量,压缩数据会增加CPU负载,现在的硬件设施都很强大,大多数时候压缩数据时一个好主意。Netty支持“gzip”和“deflate”,为此提供了两个ChannelHandler实现分别用于压缩和解压。
pipeline.addLast("decompressor", new HttpContentDecompressor());
- 使用HTTPS
网络中传输的重要数据需要加密来保护,使用Netty提供的SslHandler可以很容易实现:
private final SSLContext context;
SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode(client);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addFirst("ssl", new SslHandler(engine));
- 处理空闲连接和超时
处理空闲连接和超时是网络应用程序的核心部分。当发送一条消息后,可以检测连接是否还处于活跃状态,若很长时间没用了就可以断开连接。使用心跳机制IdleStateEvent来进行检测并作出处理。
引导Netty应用程序
Netty提供了简单统一的方法来引导服务器和客户端。引导是配置Netty服务器和客户端程序的一个过程,Bootstrap允许这些应用程序很容易的重复使用。Netty程序的客户端和服务器都可以使用Bootstrap,其目的是简化编码过程,Bootstrap还提供了一个机制就是让一些组件(channels,pipeline,handlers等等)都可以在后台工作。
Netty包含了2个不同类型的引导,第一个是使用服务器的ServerBootstrap,用来接受客户端连接以及为已接受的连接创建子通道;第二个是用于客户端的Bootstrap,不接受新的连接,并且是在父通道类完成一些操作。
我们在前面讨论了许多用于客户端和服务器的知识,为了对客户端和服务器之间的关系提供了一个共同点,Netty使用AbstractBootstrap类。通过一个共同的父类,在本章中讨论的客户端和服务器的引导程序能够重复使用通用功能,而无需复制代码或逻辑。通常情况下,多个通道使用相同或非常类似的设置时有必要的。而不是为每一个通道创建一个新的引导,Netty使得AbstractBootstrap可复制。也就是说克隆一个已配置的引导,其返回的是一个可重用而无需配置的引导。Netty的克隆操作只能浅拷贝引导的EventLoopGroup,也就是说EventLoopGroup在所有的克隆的通道中是共享的。这是一个好事情,克隆的通道一般是短暂的,例如一个通道创建一个HTTP请求。
引导客户端
- 创建Bootstrap实例使用new关键字,下面是Bootstrap的方法:
group(...),设置EventLoopGroup,EventLoopGroup用来处理所有通道的IO事件
channel(...),设置通道类型
channelFactory(...),使用ChannelFactory来设置通道类型
localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
option(ChannelOption<T>, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除
handler(ChannelHandler),设置ChannelHandler用于处理请求事件
clone(),深度复制Bootstrap,Bootstrap的配置相同
remoteAddress(...),设置连接地址
connect(...),连接远程通道
bind(...),创建一个新的Channel并绑定
使用ServerBootstrap引导服务器
- 先看看ServerBootstrap提供了哪些方法
group(...),设置EventLoopGroup事件循环组
channel(...),设置通道类型
channelFactory(...),使用ChannelFactory来设置通道类型
localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
option(ChannelOption<T>, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
childOption(ChannelOption<T>, T),设置子通道选项
attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除
childAttr(AttributeKey<T>, T),设置子通道属性
handler(ChannelHandler),设置ChannelHandler用于处理请求事件
childHandler(ChannelHandler),设置子ChannelHandler
clone(),深度复制ServerBootstrap,且配置相同
bind(...),创建一个新的Channel并绑定
child*方法是在子Channel上操作,通过ServerChannel来管理。
-
从Channel引导客户端
有时候需要从另一个Channel引导客户端,例如写一个代理或需要从其他系统检索数据。从其他系统获取数据时比较常见的,有很多Netty应用程序必须要和企业现有的系统集成,如Netty程序与内部系统进行身份验证,查询数据库等。
当然,你可以创建一个新的引导,这样做没有什么不妥,只是效率不高,因为要为新创建的客户端通道使用另一个EventLoop,如果需要在已接受的通道和客户端通道之间交换数据则需要切换上下文线程。Netty对这方面进行了优化,可以讲已接受的通道通过eventLoop(...)传递到EventLoop,从而使客户端通道在相同的EventLoop里运行。这消除了额外的上下文切换工作,因为EventLoop继承于EventLoopGroup。除了消除上下文切换,还可以在不需要创建多个线程的情况下使用引导。
为什么要共享EventLoop呢?一个EventLoop由一个线程执行,共享EventLoop可以确定所有的Channel都分配给同一线程的EventLoop,这样就避免了不同线程之间切换上下文,从而减少资源开销。
下图显示相同的EventLoop管理两个Channel:
-
添加多个ChannelHandler
在所有的例子代码中,我们在引导过程中通过handler(...)或childHandler(...)都只添加了一个ChannelHandler实例,对于简单的程序可能足够,但是对于复杂的程序则无法满足需求。例如,某个程序必须支持多个协议,如HTTP、WebSocket。若在一个ChannelHandler中处理这些协议将导致一个庞大而复杂的ChannelHandler。Netty通过添加多个ChannelHandler,从而使每个ChannelHandler分工明确,结构清晰。
Netty的一个优势是可以在ChannelPipeline中堆叠很多ChannelHandler并且可以最大程度的重用代码。如何添加多个ChannelHandler呢?Netty提供ChannelInitializer抽象类用来初始化ChannelPipeline中的ChannelHandler。ChannelInitializer是一个特殊的ChannelHandler,通道被注册到EventLoop后就会调用ChannelInitializer,并允许将ChannelHandler添加到ChannelPipeline;完成初始化通道后,这个特殊的ChannelHandler初始化器会从ChannelPipeline中自动删除。
handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。
-
使用通道选项和属性
比较麻烦的是创建通道后不得不手动配置每个通道,为了避免这种情况,Netty提供了ChannelOption来帮助引导配置。这些选项会自动应用到引导创建的所有通道,可用的各种选项可以配置底层连接的详细信息,如通道“keep-alive(保持活跃)”或“timeout(超时)”的特性。
Netty应用程序通常会与组织或公司其他的软件进行集成,在某些情况下,Netty的组件如通道、传递和Netty正常生命周期外使用;在这样的情况下并不是所有的一般属性和数据时可用的。这只是一个例子,但在这样的情况下,Netty提供了通道属性(channel attributes)。
属性可以将数据和通道以一个安全的方式关联,这些属性只是作用于客户端和服务器的通道。例如,例如客户端请求web服务器应用程序,为了跟踪通道属于哪个用户,应用程序可以存储用的ID作为通道的一个属性。任何对象或数据都可以使用属性被关联到一个通道。
使用ChannelOption和属性可以让事情变得很简单,例如Netty WebSocket服务器根据用户自动路由消息,通过使用属性,应用程序能在通道存储用户ID以确定消息应该发送到哪里。应用程序可以通过使用一个通道选项进一步自动化,给定时间内没有收到消息将自动断开连接。
** 获取SSLContext需要相关的keystore文件,这里没有关于 HTTPS可以查阅相关资料,这里只介绍在 Netty 中如何使用 **