netty4 的同步调用

netty是一个基于nio的socket框架,其调用方式也是异步调用。客户端建立和服务端的链接后,客户端向服务的发送消息,客户端并不等待服务端的的返回,而是在另外一个监听线程中监听服务的的返回。所以客户端的信息发送和信息返回是在两个线程中进行的。

但是在很多时候客户端需要等待服务端的消息,那么如何操作呢,就是使用闭锁等待。具体示例:

先定义一个消息类 NettyMessage

public class NettyMessage implements Serializable {
    private static final long serialVersionUID = -8478844905397248186L;

    private String id;
    private String serviceCode;
    private byte[] value;

}

服务端代码:

public class NettyServer {
    private static final String IP = "127.0.0.1";
    private static final int PORT = 9999;
    static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2; 
    static final int BIZTHREADSIZE = 4;

    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);
    private static final EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE);

    protected static void run(final NettyServerListener listener) throws Exception {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup);
        b.channel(NioServerSocketChannel.class);
        b.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                pipeline.addLast("encoder", new ObjectEncoder());
                pipeline.addLast(new NettyServerHandler(listener));
            }
        });

        b.bind(IP, PORT).sync();
        System.out.println("TCP服务器已启动");
    }

    protected static void shutdown() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        System.out.println("开始启动TCP服务器...");
        NettyServer.run(new NettyServerListener() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, NettyMessage message) {
                try {
                    String serviceCode = message.getServiceCode();
                    //业务分发
                    switch (serviceCode) {
                        case "add_user":
                            break;
                        case "update_user":
                            break;
                        case "print_user":
                            String userName = new String(message.getValue(), "UTF-8");
                            System.out.println(userName);
                            userName = "Hello " + userName + " !";
                            message.setValue(userName.getBytes("UTF-8"));
                            //返回处理结果
                            ctx.channel().writeAndFlush(message).sync();
                            break;
                        default:
                            message.setValue("{error}".getBytes("UTF-8"));
                            ctx.channel().writeAndFlush(message).sync();
                            break;
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
//      TcpServer.shutdown();
    }
}

其中NettyServerHandler为继承SimpleChannelInboundHandler的监听类:

public class NettyServerHandler extends SimpleChannelInboundHandler<Object> {
    private NettyServerListener listener;

    public NettyServerHandler(NettyServerListener listener) {
        this.listener = listener;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if (msg != null) {
            if (msg instanceof NettyMessage) {
                listener.channelRead(ctx, (NettyMessage) msg);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) throws Exception {
        System.out.println("Unexpected exception from downstream.");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("registered: " + ctx.channel().remoteAddress());
    }
}

NettyServerListener为只有一个channelRead的接口:

public interface NettyServerListener {
    void channelRead(ChannelHandlerContext ctx, NettyMessage message);
}

客户端代码:

同样定义一个监听接口:

public interface NettyClientListener {
    void channelRead(NettyMessage msg);
}

继承SimpleChannelInboundHandler:

public class NettyClientHandler extends SimpleChannelInboundHandler<Object> {
    private NettyClientListener listener;

    public NettyClientHandler(NettyClientListener listener) {
        this.listener = listener;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if (msg != null) {
            if (msg instanceof NettyMessage) {
                listener.channelRead((NettyMessage) msg);
            }
        }

    }

}

客户端连接发送接收信息部分:

public class NettyClient {
    public static String HOST = "127.0.0.1";
    public static int PORT = 9999;

    public static Channel channel = null;

    public static void connect(final NettyClientListener listener) {
        try {
            EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class);
            bootstrap.handler(new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                    pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                    pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                    pipeline.addLast("encoder", new ObjectEncoder());
                    pipeline.addLast("handler", new NettyClientHandler(listener));
                }
            });

            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);

            channel = bootstrap.connect(HOST, PORT).sync().channel();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static AtomicInteger id = new AtomicInteger(1);

    private static ConcurrentHashMap<String, NettyResult> result = new ConcurrentHashMap<String, NettyResult>();

    public static void main(String[] args) throws Exception {
        connect(new NettyClientListener() {
            @Override
            public void channelRead(NettyMessage msg) {
                NettyResult nettyResult = result.get(msg.getId());
                if (nettyResult != null) {
                    nettyResult.setValue(msg.getValue());
                    nettyResult.getLatch().countDown();
                }
            }
        });

        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    String idStr = String.valueOf(new Integer(id.getAndAdd(1)));
                    try {
                        NettyMessage message = new NettyMessage();
                        message.setId(idStr);
                        message.setServiceCode("print_user");
                        message.setValue(("mcyxxx:" + idStr).getBytes("UTF-8"));

                        NettyResult nresult = new NettyResult();
                        CountDownLatch latch = new CountDownLatch(1);
                        nresult.setLatch(latch);
                        result.put(idStr, nresult);
                        channel.writeAndFlush(message).sync();
                        System.out.println("id:" + idStr + " tid:" + Thread.currentThread().getId());
                        latch.await(10000, TimeUnit.MILLISECONDS);
                        latch.countDown();

                        byte[] res = result.get(idStr).getValue();
                        if (res != null) {
                            System.out.println(new String(res, "UTF-8") + " tid:" + Thread.currentThread().getId());
                        }


                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        result.remove(idStr);
                    }
                }
            });
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.start();
        }
    }
}

其中重点是定义了一个CountDownLatch闭锁,在发送了消息之后进行闭锁等待,在监听线程中监听到服务端的消息返回时latchcountDown闭锁等待结束。

但是监听线程是实时在监听消息的,如何知道服务端返回的消息是哪个线程的客户端的呢?这里在NettyMessage中定义了个id属性,每次发送消息时对其赋个唯一序列值,最后通过这个id值确定哪个线程的客户端。这些都是存在一个全局的ConcurrentHashMap中key值为id,最后客户端不管调用成功与否都需要remove掉map里面的key。

这样就简单实现了netty4的同步调用服务端,性能方面未做测试,而且略显繁琐,不知道有没有其它更好的方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值