一 netty简单示例
先写一个简单的netty长连接Demo
服务端主要包括连接类(bootstrap)和业务处理类(channelHandler),另外一个server启动类,可以与连接类合并。
公用的类为消息和消息编码,消息解码类。
消息类:
public class RequestInfoVO {
private String body;
private int Type;
private int Sequence;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public int getType() {
return Type;
}
public void setType(int type) {
Type = type;
}
public int getSequence() {
return Sequence;
}
public void setSequence(int sequence) {
Sequence = sequence;
}
}
消息解码器:
public class MessageDecoder extends ByteToMessageDecoder {
private static final int MAGIC_NUMBER = 0x0CAFFEE0;
public MessageDecoder() {
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 14) {
return;
}
// 标记开始读取位置
in.markReaderIndex();
int magic_number = in.readInt();
if (MAGIC_NUMBER != magic_number) {
ctx.close();
return;
}
@SuppressWarnings("unused")
byte version = in.readByte();
byte type = in.readByte();
int squence = in.readInt();
int length = in.readInt();
if (length < 0) {
ctx.close();
return;
}
if (in.readableBytes() < length) {
// 重置到开始读取位置
in.resetReaderIndex();
return;
}
byte[] body = new byte[length];
in.readBytes(body);
RequestInfoVO req = new RequestInfoVO();
req.setBody(new String(body, "utf-8"));
req.setType(type);
req.setSequence(squence);
out.add(req);
}
}
消息编码器:
public class MessageEncoder extends MessageToByteEncoder<RequestInfoVO> {
private static final String DEFAULT_ENCODE = "utf-8";
private static final int MAGIC_NUMBER = 0x0CAFFEE0;
public MessageEncoder() {
}
@Override
protected void encode(ChannelHandlerContext ctx, RequestInfoVO msg, ByteBuf out) throws Exception {
@SuppressWarnings("resource")
ByteBufOutputStream writer = new ByteBufOutputStream(out);
byte[] body = null;
if (null != msg && null != msg.getBody() && "" != msg.getBody()) {
body = msg.getBody().getBytes(DEFAULT_ENCODE);
}
writer.writeInt(MAGIC_NUMBER);
writer.writeByte(1);
writer.writeByte(msg.getType());
writer.writeInt(msg.getSequence());
if (null == body || 0 == body.length) {
writer.writeInt(0);
} else {
writer.writeInt(body.length);
writer.write(body);
}
}
}
服务端连接类:
public class NettyServerBootstrap {
private Integer port;
private SocketChannel socketChannel;
public NettyServerBootstrap(Integer port) throws Exception {
this.port = port;
bind(port);
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public SocketChannel getSocketChannel() {
return socketChannel;
}
public void setSocketChannel(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
private void bind(int serverPort) throws Exception {
// 连接处理group
EventLoopGroup boss = new NioEventLoopGroup();
// 事件处理group
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
// 绑定处理group
bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class);
// 保持连接数
bootstrap.option(ChannelOption.SO_BACKLOG, 1024 * 1024);
// 有数据立即发送
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 保持连接
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// 处理新连接
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 增加任务处理
ChannelPipeline p = sc.pipeline();
/**
* 这里要注意的地方:
* 1.顺序是要注意的,解码器必须排在入境处理类之前,因为入境数据的流动时从做到右;
* 编码器必须放在出境处理类之前,因为出境数据的流动时从右到左
* 2.同时配置几个解码器或者同时配置几个编码器都很容易出现问题,一般只配置一个解码器和一个编码器
*/
p.addLast(new MessageDecoder(), new MessageEncoder(), new NettyServerHandler());
}
});
ChannelFuture f = bootstrap.bind(serverPort).sync();
if (f.isSuccess()) {
System.out.println("long connection started success");
} else {
System.out.println("long connection started fail");
}
}
服务端业务处理handler:
public class NettyServerHandler extends SimpleChannelInboundHandler<RequestInfoVO> {
// private static final Log log = LogFactory.getLog(NettyServerHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, RequestInfoVO msg) throws Exception {
System.out.println(msg.getBody());
//
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
}
}
客户端:
客户端与服务端共用消息类和解码器,编码器。
客户端连接类:
public class NettyClientBootstrap {
private int port;
private String host;
private SocketChannel socketChannel;
public NettyClientBootstrap(int port, String host) throws Exception {
this.host = host;
this.port = port;
start();
}
private void start() throws Exception {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.group(eventLoopGroup);
bootstrap.remoteAddress(this.host, this.port);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MessageDecoder(), new MessageEncoder(), new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect(this.host, this.port).sync();
if (future.isSuccess()) {
socketChannel = (SocketChannel) future.channel();
System.out.println("connect server success|");
}
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public SocketChannel getSocketChannel() {
return socketChannel;
}
public void setSocketChannel(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}
客户端业务处理handler:
public class NettyClientHandler extends SimpleChannelInboundHandler<RequestInfoVO> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RequestInfoVO msg)
throws Exception {
System.out.println(msg.getBody());
RequestInfoVO req = new RequestInfoVO();
req.setSequence(msg.getSequence());
req.setType(msg.getType());
if (2 == msg.getType()) {
req.setBody("client");
ctx.channel().writeAndFlush(req);
} else if (3 == msg.getType()) {
req.setBody("zpksb");
ctx.channel().writeAndFlush(req);
}
}
}
最后是启动类:
服务端:
public class Server {
public static void main(String[] args) {
try {
new NettyServerBootstrap(9999);
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
public class Client {
public static void main(String[] args) throws Exception {
NettyClientBootstrap bootstrap = new NettyClientBootstrap(9999, "127.0.0.1");
int i = 1;
while (true) {
TimeUnit.SECONDS.sleep(2);
System.out.println("send heartbeat!");
RequestInfoVO req = new RequestInfoVO();
req.setSequence(123456);
req.setType((byte) 1);
req.setSequence(0);
req.setBody(String.valueOf((new Date()).getTime()));
TestVO vo = new TestVO();
vo.setReq(1);
vo.setName("582552252525");
// bootstrap.getSocketChannel().write(req);
bootstrap.getSocketChannel().writeAndFlush(vo);
i++;
}
}
}
上面代码的主要功能为一个实现长连接,同时客户端定时会向服务端发送心跳包类检测连接是否正常。
有几个需要注意的地方:
1.服务器的连接类中有一行关键的代码:
p.addLast(new MessageDecoder(), new MessageEncoder(), new NettyServerHandler());
这一行代码是一行关键的代码,理解了这一行代码,对netty的整个工作流程的理解非常有帮助:
解码器MessageDecoder,编码器MessageEncoder,业务处理NettyServerHandler三个都是ChannelHandler,那么一条消息发送到服务端以后是如何处理的一个流程呢,先要了解一下netty的消息处理机制
对于一条消息,netty是通过pipeline事件传递的方式处理。
netty是采用双向链表的方式来处理消息事件,用到了责任链模式。还有一个概念,就是消息也有分为:入境(inbound)消息和出境(outbound)消息,而handler则也分为入境(inbound)处理handler和出境(outbound)处理handler。
处理过程为:
一个入境消息是从左到右(header开始向后遍历),依次被每个为入境类型handler处理(处理过程中的handler也可以选择终止消息传播,直接返回)。最后一个handler处理后返回。即一条入境消息沿着handler链条从左到右处理。
一个出境消息是从右到左(tail开始向前遍历),依次被每个为出境类型handler处理(处理过程中的handler也可以选择终止消息传播,直接返回)。最后一个handler处理后返回。一条出境消息沿着handler链条从右到左处理。
再来看上面的情况:
上面的三个handler组成一条链条:MessageDecoder->MessageEncoder->NettyServerHandler
其中两个入境handler:MessageDecoder和NettyServerHandler,一个出境handler:MessageEncoder
一个客户端发送一个消息过来,对于服务端来说这是一个入境事件,所以先后会经过MessageDecoder和NettyServerHandler处理,MessageDecoder先将网络发送过来的字节数组进行解码(反序列化)成java对象。然后交给业务处理类NettyServerHandler处理
现在如果假设把顺序替换一下。成下面这样
p.addLast(new NettyServerHandler(),new MessageDecoder(), new MessageEncoder());
然业务处理类NettyServerHandler在解码类MessageDecoder前面,会发现什么状况?
这是消息首先经过NettyServerHandler,但是这个时候消息并不是对象形式的,而是字节数组形式的,所以NettyServerHandler并不处理消息,而是将其传播到解码器MessageDecoder。这时候入境消息就没有被处理。
可见在加载handler链条的时候,要确保解码器类在入境业务处理类的前面(入境消息从左到右处理);同理编码器要放在出境业务处理类的前面(出境消息从右到左处理)。
还有一点,业务处理类NettyServerHandler中有RequestInfoVO的泛型,这个表示该业务处理类只会拦截解码后为RequestInfoVO类型的对象进行处理,如果解码后为其他类型的对象,则不处理,向下一个入境handler传播消息。
多个解码器的情况
上面是一个RequestInfoVO消息的情况,其可以处理查询用户信息请求,假设用户现在需要退出登录,有两种方式:
1.将RequestInfoVO扩展,让其也可以表示退出登陆消息
这个时候只需要对解码器进行相应的修改,让其处理扩展后的对象
2.新建一个消息类RequetLogoutVO类,然其表示退出登陆消息
这种情况下,需要新增一个解码器MessageDecoder2,然解码器来处理RequetLogoutVO类型的消息
这就引申出了一个问题,当存在多个解码器的时候流程如何处理?
假设现在新增了一个解码器,变为:
p.addLast(new MessageDecoder(),new MessageDecoder2(),new MessageEncoder(),new NettyServerHandler());
现在RequetLogoutVO类字节数组消息发送到服务端后:
先经过MessageDecoder解码,这个时候消息变为RequetLogoutVO类JAVA对象,其再经过解码器MessageDecoder2,这个时候解码器会对JAVA对象RequetLogoutVO进行再一次的解码,变为一个为止的消息,这个消息到达处理类NettyServerHandler已经变为乱码,无法正常处理。
所以在一个系统中,只定义一个解码器和一个编码器,要处理多种不同类型的消息是通过扩展消息类来实现,即一个通用的消息类可以表示各种类型的业务处理消息。
综合上面的分析,总结一下:
1.pipeline加载handler链的顺序有要求,解码器要在入境业务处理类前面;编码器要在出境业务处理类前面;
2.业务处理类拦截的是解码后为其类泛型中定义的java对象,解码不为其泛型定义的java对象的,其不处理,向后传播消息;
3.一个系统中只定义一个解码器和一个编码器,定义多个解码器和多个编码器会产生不可预知的错误。