Netty的异步事件驱动模型主要涉及到下面几个核心的概念:
Channel:表示一个与socket关联的通道
ChannelPipeline: 管道,一个Channel拥有一个ChannelPipeline,ChannelPipeline维护着一个处理链(严格的说是两 个:upstream、downstream),
处理链是由很多处理句柄ChannelHandler所构成,每个ChannelHandler处理完以 后会传递给链中的下一个处理句柄继续处理。
ChannelHandler:处理句柄,用户可以定义自己的处理句柄来处理每个请求,或发出请求前进行预处理,典型的有编码/解码器:decoder、encoder。
ChannelEvent:事件,是整个模型的处理对象,当产生或触发(fire)一个事件时,该事件会沿着ChannelPipeline处理链依次被处理。
ChannelFuture: 异步结果,这个是异步事件处理的关键,当一个事件被处理时,可以直接以ChannelFuture的形式直接返回,不用在当前操作中被阻塞。
Channel:表示一个与socket关联的通道
ChannelPipeline: 管道,一个Channel拥有一个ChannelPipeline,ChannelPipeline维护着一个处理链(严格的说是两 个:upstream、downstream),
处理链是由很多处理句柄ChannelHandler所构成,每个ChannelHandler处理完以 后会传递给链中的下一个处理句柄继续处理。
ChannelHandler:处理句柄,用户可以定义自己的处理句柄来处理每个请求,或发出请求前进行预处理,典型的有编码/解码器:decoder、encoder。
ChannelEvent:事件,是整个模型的处理对象,当产生或触发(fire)一个事件时,该事件会沿着ChannelPipeline处理链依次被处理。
ChannelFuture: 异步结果,这个是异步事件处理的关键,当一个事件被处理时,可以直接以ChannelFuture的形式直接返回,不用在当前操作中被阻塞。
可以通过 ChannelFuture得到最终的执行结果,具体的做法是在ChannelFuture添加监听器listener,当操作最终被执行完 后,listener会被触发,
我们可以在listener的回调函数中预定义我们的业务代码。
Netty应用实例:
1. netty版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
2. 服务器端:
package com.xx.xx.web.nettypro.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import javax.annotation.PostConstruct;
/**
* Created by winy.
*/
@ChannelHandler.Sharable
public class EpayServer {
// 服务绑定端口
private int port;
public EpayServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
new EpayServer(5555).start();
}
/**
* 此处用的是main启动,在项目中时,start方法上可以用spring注解:@PostConstruct,然后把该类EpayServer交给spring管理即可
* 该注解的作用是spring装载bean的时候,在加载完EpayServer构造函数后执行
* @throws InterruptedException
*/
public void start() throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
// 服务端业务处理
final EpayServerHandler epayServerHandler = new EpayServerHandler();
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 配置
serverBootstrap.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(epayServerHandler);
}
});
// 异步
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully().sync();
}
}
}
3. 服务器端handler
package com.xx.xx.web.nettypro.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
/**
* Netty5: 取消了进站、出站的划分,统一为继承ChannelHandlerAdapter,原来的ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter被废弃
* Created by winy
*/
@ChannelHandler.Sharable
public class EpayServerHandler extends ChannelHandlerAdapter {
/**
* 服务端接收到消息后执行
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println("服务器接收到的信息====" + byteBuf.toString(CharsetUtil.UTF_8));
// 客户端请求服务器的请求信息 一般包含报文头+报文体
// 报文头为固定长度
// 报文体协定用&拼接
String msgServer = "服务器发送的消息:1";
byte[] bytes = msgServer.getBytes();
ByteBuf firstMessage = Unpooled.buffer(bytes.length);
firstMessage.writeBytes(bytes);
ctx.writeAndFlush(firstMessage);
}
/**
* 客户端读完(客户端channelRead方法执行完后)数据后执行
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
/**
* 异常的场合调用
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
4. 客户端
package com.xx.xx.web.nettypro.client;
import io.netty.bootstrap.Bootstrap;
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.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by winy.
*/
public class EpayClient {
private String host;
private int port;
public EpayClient(String host,int port) {
this.host = host;
this.port = port;
}
public static void main(String[] args) {
new EpayClient("服务器端ip",5555).start();
}
public void start() {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new EpayClientHandler());
}
});
// 此处为异步调用 若需要同步,可使用await()
ChannelFuture channelFuture = bootstrap.connect(host,port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
eventLoopGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5、客户端handler
package com.xx.xx.web.nettypro.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* Created by winy
*/
public class EpayClientHandler extends ChannelHandlerAdapter {
/**
* 连接服务器成功后执行发送信息
* 向服务器发送报文
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 客户端请求服务器的请求信息 一般包含报文头+报文体
// 报文头为固定长度
// 报文体协定用&拼接
String msgClient = "客户端信息1";
byte[] bytes = msgClient.getBytes();
ByteBuf firstMessage = Unpooled.buffer(bytes.length);
firstMessage.writeBytes(bytes);
ctx.writeAndFlush(firstMessage);
}
/**
* 服务端返回消息后执行
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//服务端返回消息后
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("client1 接收到的信息=" + body);
}
/**
* 异常的场合调用
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
注:
1. 本实例测试过多个客户端的情况,当有多个客户端连接的场合,server和server handler 需要使用注解:@ChannelHandler.Sharable
2. 经测试此处客户端连接为短连接,而不是传输层TCP/IP协议的长连接,即一次连接完成就会断开!
文中如有不对之处,望大家指正!