译 3个netty5的例子,简单介绍netty的用法
来源:互联网 发布:易语言挂机软件源码 编辑:程序博客网 时间:2024/05/22 13:50
这是一个netty快速入门的例子,也是我的学习笔记,比较简单,翻译于官方的文档整理后把所有代码注释放在每一行代码中间,简单明了地介绍一些基础的用法。
首页这是基于netty5的例子,如果需要使用请依赖netty5的包。maven引用方式
1
<
dependency
>
2
<
groupId
>io.netty</
groupId
>
3
<
artifactId
>netty-all</
artifactId
>
4
<
version
>5.0.0.Alpha2</
version
>
5
</
dependency
>
或者去下载最新的jar下载页面
1.DISCARD服务(丢弃服务,指的是会忽略所有接收的数据的一种协议)
001
import
io.netty.bootstrap.ServerBootstrap;
002
import
io.netty.channel.ChannelFuture;
003
import
io.netty.channel.ChannelInitializer;
004
import
io.netty.channel.ChannelOption;
005
import
io.netty.channel.EventLoopGroup;
006
import
io.netty.channel.nio.NioEventLoopGroup;
007
import
io.netty.channel.socket.SocketChannel;
008
import
io.netty.channel.socket.nio.NioServerSocketChannel;
009
010
/**
011
* 处理数据
012
*/
013
public
class
NettyServer {
014
private
int
port;
015
public
NettyServer(
int
port) {
016
this
.port = port;
017
}
018
public
void
run()
throws
Exception {
019
/***
020
* NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
021
* Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。
022
* 在这个例子中我们实现了一个服务端的应用,
023
* 因此会有2个NioEventLoopGroup会被使用。
024
* 第一个经常被叫做‘boss’,用来接收进来的连接。
025
* 第二个经常被叫做‘worker’,用来处理已经被接收的连接,
026
* 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
027
* 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
028
* 并且可以通过构造函数来配置他们的关系。
029
*/
030
EventLoopGroup bossGroup =
new
NioEventLoopGroup();
031
EventLoopGroup workerGroup =
new
NioEventLoopGroup();
032
System.out.println(
"准备运行端口:"
+ port);
033
try
{
034
/**
035
* ServerBootstrap 是一个启动NIO服务的辅助启动类
036
* 你可以在这个服务中直接使用Channel
037
*/
038
ServerBootstrap b =
new
ServerBootstrap();
039
/**
040
* 这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException: group not set异常
041
*/
042
b = b.group(bossGroup, workerGroup);
043
/***
044
* ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
045
* 这里告诉Channel如何获取新的连接.
046
*/
047
b = b.channel(NioServerSocketChannel.
class
);
048
/***
049
* 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。
050
* ChannelInitializer是一个特殊的处理类,
051
* 他的目的是帮助使用者配置一个新的Channel。
052
* 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
053
* 或者其对应的ChannelPipeline来实现你的网络程序。
054
* 当你的程序变的复杂时,可能你会增加更多的处理类到pipline上,
055
* 然后提取这些匿名类到最顶层的类上。
056
*/
057
b = b.childHandler(
new
ChannelInitializer<SocketChannel>() {
// (4)
058
@Override
059
public
void
initChannel(SocketChannel ch)
throws
Exception {
060
ch.pipeline().addLast(
new
DiscardServerHandler());
061
//ch.pipeline().addLast(new ResponseServerHandler());
062
// ch.pipeline().addLast(new TimeServerHandler());
063
}
064
});
065
/***
066
* 你可以设置这里指定的通道实现的配置参数。
067
* 我们正在写一个TCP/IP的服务端,
068
* 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
069
* 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
070
*/
071
b = b.option(ChannelOption.SO_BACKLOG,
128
);
072
/***
073
* option()是提供给NioServerSocketChannel用来接收进来的连接。
074
* childOption()是提供给由父管道ServerChannel接收到的连接,
075
* 在这个例子中也是NioServerSocketChannel。
076
*/
077
b = b.childOption(ChannelOption.SO_KEEPALIVE,
true
);
078
/***
079
* 绑定端口并启动去接收进来的连接
080
*/
081
ChannelFuture f = b.bind(port).sync();
082
/**
083
* 这里会一直等待,直到socket被关闭
084
*/
085
f.channel().closeFuture().sync();
086
}
finally
{
087
/***
088
* 优雅关闭
089
*/
090
workerGroup.shutdownGracefully();
091
bossGroup.shutdownGracefully();
092
}
093
}
094
095
public
static
void
main(String[] args)
throws
Exception {
096
int
port;
097
if
(args.length >
0
) {
098
port = Integer.parseInt(args[
0
]);
099
}
else
{
100
port =
8000
;
101
}
102
new
NettyServer(port).run();
103
}
104
}
01
import
io.netty.buffer.ByteBuf;
02
import
io.netty.channel.ChannelHandlerAdapter;
03
import
io.netty.channel.ChannelHandlerContext;
04
import
io.netty.util.CharsetUtil;
05
import
io.netty.util.ReferenceCountUtil;
06
07
/**
08
* 服务端处理通道.这里只是打印一下请求的内容,并不对请求进行任何的响应
09
* DiscardServerHandler 继承自 ChannelHandlerAdapter,
10
* 这个类实现了ChannelHandler接口,
11
* ChannelHandler提供了许多事件处理的接口方法,
12
* 然后你可以覆盖这些方法。
13
* 现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实现接口方法。
14
*
15
*/
16
public
class
DiscardServerHandler
extends
ChannelHandlerAdapter {
17
18
/***
19
* 这里我们覆盖了chanelRead()事件处理方法。
20
* 每当从客户端收到新的数据时,
21
* 这个方法会在收到消息时被调用,
22
* 这个例子中,收到的消息的类型是ByteBuf
23
* @param ctx 通道处理的上下文信息
24
* @param msg 接收的消息
25
*/
26
@Override
27
public
void
channelRead(ChannelHandlerContext ctx, Object msg) {
28
try
{
29
ByteBuf in = (ByteBuf) msg;
30
/* while (in.isReadable()) {
31
System.out.print((char) in.readByte());
32
System.out.flush();
33
}*/
34
//这一句和上面注释的的效果都是打印输入的字符
35
System.out.println(in.toString(CharsetUtil.US_ASCII));
36
}finally {
37
/**
38
* ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
39
* 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
40
*/
41
ReferenceCountUtil.release(msg);
42
}
43
}
44
45
/***
46
* 这个方法会在发生异常时触发
47
* @param ctx
48
* @param cause
49
*/
50
@Override
51
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
52
/***
53
* 发生异常后,关闭连接
54
*/
55
cause.printStackTrace();
56
ctx.close();
57
}
58
59
}
以上是一个丢弃服务的处理方式,你可以运行后通过telnet来发送消息,来查看是否正常运行,注意console里会打印你的输入内容。
2.ECHO服务(响应式协议)
到目前为止,我们虽然接收到了数据,但没有做任何的响应。然而一个服务端通常会对一个请求作出响应。让我们学习怎样在ECHO协议的实现下编写一个响应消息给客户端,这个协议针对任何接收的数据都会返回一个响应。
和discard server唯一不同的是把在此之前我们实现的channelRead()方法,返回所有的数据替代打印接收数据到控制台上的逻辑。
说明NettyServer 还是用上面已经提供的类,只是把这段里的注销部分修改成如下。
1
//ch.pipeline().addLast(new DiscardServerHandler());
2
ch.pipeline().addLast(
new
ResponseServerHandler());
3
//ch.pipeline().addLast(new TimeServerHandler());
下面是处理类ResponseServerHandler的代码
01
import
io.netty.channel.ChannelHandlerAdapter;
02
import
io.netty.channel.ChannelHandlerContext;
03
04
/**
05
* 服务端处理通道.
06
* ResponseServerHandler 继承自 ChannelHandlerAdapter,
07
* 这个类实现了ChannelHandler接口,
08
* ChannelHandler提供了许多事件处理的接口方法,
09
* 然后你可以覆盖这些方法。
10
* 现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实现接口方法。
11
* 用来对请求响应
12
*/
13
public
class
ResponseServerHandler
extends
ChannelHandlerAdapter {
14
15
/**
16
* 这里我们覆盖了chanelRead()事件处理方法。
17
* 每当从客户端收到新的数据时,
18
* 这个方法会在收到消息时被调用,
19
*ChannelHandlerContext对象提供了许多操作,
20
* 使你能够触发各种各样的I/O事件和操作。
21
* 这里我们调用了write(Object)方法来逐字地把接受到的消息写入
22
* @param ctx 通道处理的上下文信息
23
* @param msg 接收的消息
24
*/
25
@Override
26
public
void
channelRead(ChannelHandlerContext ctx, Object msg) {
27
ctx.write(msg);
28
//cxt.writeAndFlush(msg)
29
30
//请注意,这里我并不需要显式的释放,因为在定入的时候netty已经自动释放
31
// ReferenceCountUtil.release(msg);
32
}
33
34
/**
35
* ctx.write(Object)方法不会使消息写入到通道上,
36
* 他被缓冲在了内部,你需要调用ctx.flush()方法来把缓冲区中数据强行输出。
37
* 或者你可以在channelRead方法中用更简洁的cxt.writeAndFlush(msg)以达到同样的目的
38
* @param ctx
39
* @throws Exception
40
*/
41
@Override
42
public
void
channelReadComplete(ChannelHandlerContext ctx)
throws
Exception {
43
ctx.flush();
44
}
45
46
/**
47
* 这个方法会在发生异常时触发
48
*
49
* @param ctx
50
* @param cause
51
*/
52
@Override
53
public
void
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
54
/***
55
* 发生异常后,关闭连接
56
*/
57
cause.printStackTrace();
58
ctx.close();
59
}
60
61
}
同样以上运行后,可以通过telnet发送数据,console里会打印出你发送的数据,同时你的命令行界面里应该也会接收到相同的数据。
3.TIME服务(时间协议的服务)
在这个部分被实现的协议是TIME协议。和之前的例子不同的是在不接受任何请求时他会发送一个含32位的整数的消息,并且一旦消息发送就会立即关闭连接。在这个例子中,你会学习到如何构建和发送一个消息,然后在完成时主动关闭连接。
因为我们将会忽略任何接收到的数据,而只是在连接被创建发送一个消息,所以这次我们不能使用channelRead()方法了,代替他的是,我们需要覆盖channelActive()方法,下面的就是实现的内容:
说明NettyServer 还是用上面已经提供的类,只是把这段里的注销部分修改成如下。
1
//ch.pipeline().addLast(new DiscardServerHandler());
2
//ch.pipeline().addLast(new ResponseServerHandler());
3
ch.pipeline().addLast(
new
TimeServerHandler());
TimeServerHandler类的如下:
01
public
class
TimeServerHandler
extends
ChannelHandlerAdapter {
02
03
/**
04
* channelActive()方法将会在连接被建立并且准备进行通信时被调用。
05
* 因此让我们在这个方法里完成一个代表当前时间的32位整数消息的构建工作。
06
*
07
* @param ctx
08
*/
09
@Override
10
public
void
channelActive(
final
ChannelHandlerContext ctx) {
11
/**
12
* 为了发送一个新的消息,我们需要分配一个包含这个消息的新的缓冲。
13
* 因为我们需要写入一个32位的整数,因此我们需要一个至少有4个字节的ByteBuf。
14
* 通过ChannelHandlerContext.alloc()得到一个当前的ByteBufAllocator,
15
* 然后分配一个新的缓冲。
16
*/
17
final
ByteBuf time = ctx.alloc().buffer(
4
);
18
time.writeInt((
int
) (System.currentTimeMillis() / 1000L + 2208988800L));
19
/***
20
* 和往常一样我们需要编写一个构建好的消息
21
* 。但是等一等,flip在哪?难道我们使用NIO发送消息时不是调用java.nio.ByteBuffer.flip()吗?
22
* ByteBuf之所以没有这个方法因为有两个指针,
23
* 一个对应读操作一个对应写操作。
24
* 当你向ByteBuf里写入数据的时候写指针的索引就会增加,
25
* 同时读指针的索引没有变化。
26
* 读指针索引和写指针索引分别代表了消息的开始和结束。
27
* 比较起来,NIO缓冲并没有提供一种简洁的方式来计算出消息内容的开始和结尾,
28
* 除非你调用flip方法。
29
* 当你忘记调用flip方法而引起没有数据或者错误数据被发送时,
30
* 你会陷入困境。这样的一个错误不会发生在Netty上,
31
* 因为我们对于不同的操作类型有不同的指针。
32
* 你会发现这样的使用方法会让你过程变得更加的容易,
33
* 因为你已经习惯一种没有使用flip的方式。
34
* 另外一个点需要注意的是ChannelHandlerContext.write()(和writeAndFlush())方法会返回一个ChannelFuture对象,
35
* 一个ChannelFuture代表了一个还没有发生的I/O操作。
36
* 这意味着任何一个请求操作都不会马上被执行,
37
* 因为在Netty里所有的操作都是异步的。
38
* 因此你需要在write()方法返回的ChannelFuture完成后调用close()方法,
39
* 然后当他的写操作已经完成他会通知他的监听者。
40
*/
41
final
ChannelFuture f = ctx.writeAndFlush(time);
// (3)
42
/**
43
* 当一个写请求已经完成是如何通知到我们?
44
* 这个只需要简单地在返回的ChannelFuture上增加一个ChannelFutureListener。
45
* 这里我们构建了一个匿名的ChannelFutureListener类用来在操作完成时关闭Channel。
46
*/
47
f.addListener(
new
ChannelFutureListener() {
48
@Override
49
public
void
operationComplete(ChannelFuture future) {
50
assert
f == future;
51
/***
52
* 请注意,close()方法也可能不会立马关闭,他也会返回一个ChannelFuture。
53
*/
54
ctx.close();
55
}
56
});
57
}
58
@Override
59
public
void
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
60
cause.printStackTrace();
61
ctx.close();
62
}
63
}
4.Time客户端
不像DISCARD和ECHO的服务端,对于TIME协议我们需要一个客户端因为人们不能把一个32位的二进制数据翻译成一个日期或者日历。在这一部分,我们将会讨论如何确保服务端是正常工作的,并且学习怎样用Netty编写一个客户端。
在Netty中,编写服务端和客户端最大的并且唯一不同的使用了不同的BootStrap和Channel的实现。
01
public
class
TimeClient {
02
public
static
void
main(String[] args)
throws
Exception {
03
String host =
"127.0.0.1"
;
04
int
port =
8000
;
05
EventLoopGroup workerGroup =
new
NioEventLoopGroup();
06
try
{
07
/**
08
* 如果你只指定了一个EventLoopGroup,
09
* 那他就会即作为一个‘boss’线程,
10
* 也会作为一个‘workder’线程,
11
* 尽管客户端不需要使用到‘boss’线程。
12
*/
13
Bootstrap b =
new
Bootstrap();
// (1)
14
b.group(workerGroup);
// (2)
15
/**
16
* 代替NioServerSocketChannel的是NioSocketChannel,这个类在客户端channel被创建时使用
17
*/
18
b.channel(NioSocketChannel.
class
);
// (3)
19
/**
20
* 不像在使用ServerBootstrap时需要用childOption()方法,
21
* 因为客户端的SocketChannel没有父channel的概念。
22
*/
23
b.option(ChannelOption.SO_KEEPALIVE,
true
);
// (4)
24
b.handler(
new
ChannelInitializer<SocketChannel>() {
25
@Override
26
public
void
initChannel(SocketChannel ch)
throws
Exception {
27
ch.pipeline().addLast(
new
TimeClientHandler());
28
}
29
});
30
//用connect()方法代替了bind()方法
31
ChannelFuture f = b.connect(host, port).sync();
32
//等到运行结束,关闭
33
f.channel().closeFuture().sync();
34
}
finally
{
35
workerGroup.shutdownGracefully();
36
}
37
}
38
39
}
01
/**
02
*客户端处理类
03
*/
04
public
class
TimeClientHandler
extends
ChannelHandlerAdapter {
05
private
ByteBuf buf;
06
/**
07
* 开始处理的时候触发
08
*
09
* @param ctx
10
*/
11
@Override
12
public
void
handlerAdded(ChannelHandlerContext ctx) {
13
buf = ctx.alloc().buffer(
4
);
// 分配4个字节的空间给ByteBuf
14
}
15
16
/**
17
* 处理结束的时候触发
18
*
19
* @param ctx
20
*/
21
@Override
22
public
void
handlerRemoved(ChannelHandlerContext ctx) {
23
buf.release();
//释放ByteBuf的空间
24
buf =
null
;
25
}
26
27
@Override
28
public
void
channelRead(ChannelHandlerContext ctx, Object msg) {
29
ByteBuf m = (ByteBuf) msg;
30
/**
31
* 所有接收的数据都应该被累积在buf变量里
32
*/
33
buf.writeBytes(m);
34
m.release();
35
/**
36
* 处理器必须检查buf变量是否有足够的数据,在这个例子中是4个字节,
37
* 然后处理实际的业务逻辑。
38
* 否则,Netty会重复调用channelRead()当有更多数据到达直到4个字节的数据被积累。
39
*/
40
if
(buf.readableBytes() >=
4
) {
41
long
currentTimeMillis = (buf.readInt() - 2208988800L) * 1000L;
42
System.out.println(
new
Date(currentTimeMillis));
43
ctx.close();
44
}
45
}
46
47
@Override
48
public
void
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
49
cause.printStackTrace();
50
ctx.close();
51
}
52
}
总结
这里通过三个例子说明一下netty的用法,更多例子可以去官方下载io.netty.example
- 3个netty5的例子,简单介绍netty的用法
- 3个netty5的例子,简单介绍netty的用法
- 译 3个netty5的例子,简单介绍netty的用法
- Netty5的例子,简单介绍Netty的用法
- netty的简单介绍
- Netty5 最简单的例子 Writing a Discard Server
- Netty5用户手册之一:netty的作用
- Netty简单介绍和例子
- 一个简单的Netty服务器例子
- Netty 处理简单HTTP请求的例子
- Netty客户端和服务器简单的例子
- Netty开发的例子
- Netty5.0使用简单介绍
- Netty学习笔记<2>--简单的NIO例子
- Netty笔记一(可以运行看到结果的简单例子)
- 简单的LINQ用法例子
- Java NIO框架Netty(二)netty5例子,代码详解
- Netty的介绍
- Node.js + Web Socket 打造即时聊天程序嗨聊
- 多表左连接查询-MS-SQLServer/基础类
- main函数的多种写法
- js实用文档
- python urllib 和urllib2的区别
- 译 3个netty5的例子,简单介绍netty的用法
- 通过正则表达式获取标准url地址的域名和文件后缀名
- Java集合框架—List
- KendoUI使用指南
- 指针函数与函数指针区别
- 小Tips—为Apache 2.x添加压缩功能
- 代码资料
- fragment和activity通信问题
- 如何搞定毕业论文查重