Netty/NIO实践

Netty的异步事件驱动模型主要涉及到下面几个核心的概念:
  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协议的长连接,即一次连接完成就会断开!



文中如有不对之处,望大家指正!




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值