netty入门之EchoServer
学习一门新的语言,通常是以 HelloWorld 开始的。类似地,学习一个网络框架,通常是以 EchoServer 开始的。接下来我们就来看下如何通过 netty 实现一个 EchoServer。
动手实现一个EchoServer
EchoServer 是一个最简单的 server,它接收请求后将读取到的信息全部原样输出。对于客户端来说,输入一段文字后,服务端返回的还是这段文字,就像一个回音墙一样。
1 | Client Request: Hello |
实现一个 EchoServer 的主要步骤如下:
- 搭建项目,引入 netty 依赖
- 实现
EchoServerHandler
类,用于处理入站事件,对于读取到的请求信息,全部原样输出 - 实现
EchoServer
类:配置ServerBootstrap
的各项参数,指定 childHandler 的初始化方式,然后绑定到指定地址+端口启动服务
引入netty依赖
搭建好项目后,首先要引入 netty 依赖,你可以简单地加入 netty-all 依赖,不用关心具体是依赖 netty 的哪个模块,这里我们选择的是 4.1.51.Final 版本。
1 | <dependency> |
实现EchoServerHandler类
EchoServerHandler
用于在读到数据时进行相应处理,它关心的事件是 read 和 readComplete,这些事件均为入站(Inbound)事件,因此 EchoServerHandler
需要实现 ChannelInboundHandler
接口。
入站事件不只包括上述两种事件,还包括 channelRegistered、channelActive、channelWritabilityChanged 等,如果我们直接实现 ChannelInboundHandler
接口,那么就意味着要为每个入站事件提供一个对应的实现,这显然不是一个好的方式。
幸运地是,netty 提供了 ChannelInboundHandlerAdapter
,它为所有入站事件提供了默认实现,若要改变处理某个事件的行为,我们只需要针对该事件进行处理。因此我们选择继承 ChannelInboundHandlerAdapter
类。
EchoServerHandler
的实现如下:
1 |
|
channelRead
读取到数据时,将数据写入到输出缓冲区
channelReadComplete
数据读取完毕,将缓冲区的内存输出到网络
exceptionCaught
若处理过程中出现异常,则关闭channel
实现EchoServer类
EchoServer
的实现如下:
1 | public final class EchoServer { |
配置 bossGroup 和 workerGroup
bossGroup 用于处理连接请求,配置线程数为 1。workerGroup 用于已建立的连接,线程数配置为与 cpu 数相关。针对网络 IO 模型,我们选择的是 NIO,故 bossGroup 和 workerGroup 的实现为
NioEventLoopGroup
配置 channel
前面已经选择好了 IO 模型,channel 也要与之配套,故 channel 的实现类是
NioServerSocketChannel.class
配置 option
option 有很多配置项,可根据需求配置。对于 EchoServer 来说,可以全部使用默认值,即什么也不配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public static final ChannelOption<Boolean> AUTO_CLOSE = valueOf("AUTO_CLOSE");
public static final ChannelOption<Boolean> SO_BROADCAST = valueOf("SO_BROADCAST");
public static final ChannelOption<Boolean> SO_KEEPALIVE = valueOf("SO_KEEPALIVE");
public static final ChannelOption<Integer> SO_SNDBUF = valueOf("SO_SNDBUF");
public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");
public static final ChannelOption<Boolean> SO_REUSEADDR = valueOf("SO_REUSEADDR");
public static final ChannelOption<Integer> SO_LINGER = valueOf("SO_LINGER");
public static final ChannelOption<Integer> SO_BACKLOG = valueOf("SO_BACKLOG");
public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");
public static final ChannelOption<Integer> IP_TOS = valueOf("IP_TOS");
public static final ChannelOption<InetAddress> IP_MULTICAST_ADDR = valueOf("IP_MULTICAST_ADDR");
public static final ChannelOption<NetworkInterface> IP_MULTICAST_IF = valueOf("IP_MULTICAST_IF");
public static final ChannelOption<Integer> IP_MULTICAST_TTL = valueOf("IP_MULTICAST_TTL");
public static final ChannelOption<Boolean> IP_MULTICAST_LOOP_DISABLED = valueOf("IP_MULTICAST_LOOP_DISABLED");
public static final ChannelOption<Boolean> TCP_NODELAY = valueOf("TCP_NODELAY");
public static final ChannelOption<Boolean> TCP_FASTOPEN_CONNECT = valueOf("TCP_FASTOPEN_CONNECT");配置 handler
对于每一个连接请求,我们可以将请求的事件打印出来,因此这里添加了 一个
LoggingHandler
配置 childHandler
当连接建立后,我们需要处理读写事件了,这里就需要把 EchoServerHandler 添加上去了。netty 支持添加多个 handler 到 pipeline 上,它是通过
ChannelInitializer
实现的,我们只需要按照下面的方式添加即可。1
2
3
4
5
6
7
8.childHandler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// 初始化channelHandler时,将EchoServerHandler添加到pipeline上
p.addLast(serverHandler);
}
});
验证效果
至此,我们已经完成了一个 EchoServer。如何验证呢?我们可以使用 telnet (Telecommunications Network)命令
1 | - Telnet to a specific port of a host: |
执行 EchoServer
的 main 方法启动 server,可以看到如下输出,说明服务已经启动成功
1 | 15:12:04.140 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x99a8234b] REGISTERED |
输入 telnet localhost 8007
连接到 server
1 | Trying ::1... |
接下来我们就可以输出文字并查看效果了,server 会返回你所输入的内容
1 | How are you? |
若要在 server 端打印读取到的字符串,则可以对 EchoServerHandler 的 channelRead 方法进行修改
1 |
|
当你在 telnet 窗口中输入 How are you?
时,你将会在 server 端的控制台输出中看到如下信息
1 | read from client: How are you? |
参考
- 2021-04-14
Netty server 在启动过程中会触发一系列的 Inbound 事件,它的流程是怎样的呢?
- 2022-01-07
兴趣是最好的老师,当你对一件事情感兴趣的时候,你就更容易长久地坚持下去,从而达到一个更高的高度,或许还能够取得意想不到的效果。接下来我会从实现一个 echo server 开始 netty 的探索旅程,如果你也对 netty 感兴趣,那么我们就开始吧。
- 2021-01-26
ServerBootstrap 启动时需要初始化 ServerSocketChannel 并将其绑定到 EventLoop 上,用于处理该 channel 上产生的各种事件。那么 ServerSocketChannel 是如何完成创建和初始化的?又是如何绑定到 EventLoop 上的?
- 2021-02-24
Netty 中通过在 pipeline 上添加各种 handler 组合来实现不同的逻辑,handler 又可以分为
ChannelInboundHandler
和ChannelOutboundHandler
,它们分别用于处理入站事件和出站事件。 - 2021-02-05
netty 中每个 channel 都会绑定了一个 pipeline,当有入站事件或出站操作时,会由 pipeline 中的 handler 进行拦截处理。