并发编程 | Netty - [简易群聊示例]

@

§1 需求

  • 可以支持多人群聊
  • 有客户端加入群聊时,会有广播通知
  • 有客户端离开群聊时,会有广播通知
  • 有客户端发言后,会有广播通知

§2 GeneralNettyChannelInitializer & NamableEntry

无论是 Server 端还是 Client 端,都需要配置 handler
在配置 handler 时,常规写法如下

  • 匿名内部类的逻辑高度重复
  • 不够优雅
bootstrap.group(boss,worker)
		// 其他配置
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast("name",new HttpServerCodec());
                ch.pipeline().addLast("name",new NettySimpleHttpServerHandler());
                ch.pipeline().addLast(new IdleStateHandler(1,2,3));
                ......
            }
        });

尝试通过下面的方式进行优化:

  • GeneralNettyChannelInitializer 用于提供通用的 ChannelInitializer
    • 提供静态方法获取示例,每次获取的实例都是新的,防止线程问题
  • NamableEntry 用于提供对 name / handler 的映射
    • 支持有名 handler 和无名 handler 两种封装
    • handler 使用 class 而不是具体实例
      每次使用时会通过 newInstance() 获取示例,因此要求无参构造
      可以防止线程问题

GeneralNettyChannelInitializer

public class GeneralNettyChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final Collection<NamableEntry<? extends ChannelHandler>> handlers = new ArrayList<>();

    public GeneralNettyChannelInitializer add(NamableEntry<? extends ChannelHandler> entry) {
        this.handlers.add(entry);
        return this;
    }

    public static GeneralNettyChannelInitializer build(NamableEntry<? extends ChannelHandler> entry) {
        GeneralNettyChannelInitializer initializer = new GeneralNettyChannelInitializer();
        initializer.add(entry);
        return initializer;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        for(NamableEntry<? extends ChannelHandler> entry: handlers){
            if(entry.isInstance()){
                ch.pipeline().addLast(entry.getName(), entry.getConstructor().newInstance(entry.getParams()));
            }else
                ch.pipeline().addLast(entry.getName(),entry.getReal().newInstance());
        }
    }
}

NamableEntry

public class NamableEntry<T> {
    private String name;
    private Class<T> real;
    private Constructor<T> constructor;
    private Object[] params;

    /* *******************************
     * 以下是构造
     ******************************* */
    private NamableEntry(Class<T> real){
        this.real = real;
    }
    private NamableEntry(String name, Class<T> handler){
        this.name = name;
        this.real = handler;
    }

    public NamableEntry(Constructor<T> constructor, Object[] params) {
        this.constructor = constructor;
        this.params = params;
    }

    public NamableEntry(String name, Constructor<T> constructor, Object[] params) {
        this.name = name;
        this.constructor = constructor;
        this.params = params;
    }

    /* *******************************
     * 以下是工具
     ******************************* */
    public boolean isNamed(){
        return StrUtil.isNotEmpty(name);
    }
    public boolean isInstance() { return null!=constructor; }

    public static<T> NamableEntry<T> named(String name, Class<T> real){
        return new NamableEntry<T>(name,real);
    }
    public static<T> NamableEntry<T> unnamed(Class<T> real){
        return new NamableEntry<T>(real);
    }
    public static<T> NamableEntry<T> named(String name, Constructor<T> constructor, Object[] params){
        return new NamableEntry<T>(name,constructor,params);
    }
    public static<T> NamableEntry<T> unnamed(Constructor<T> constructor, Object[] params){
        return new NamableEntry<T>(constructor,params);
    }

    /* *******************************
     * 以下是 setter/getter
     ******************************* */

    public String getName() {
        return name;
    }

    public Class<T> getReal() {
        return real;
    }

    public Constructor<T> getConstructor() {
        return constructor;
    }

    public Object[] getParams() {
        return params;
    }
}

最终,调整配置方式为

ServerBootstrap starter = new ServerBootstrap()
        .group(boss,workers)
		// 其他配置
        .childHandler(
               GeneralNettyChannelInitializer
                       .build(NamableEntry.named("decoder",StringDecoder.class))
                       .add(NamableEntry.named("encoder", StringEncoder.class))
                       .add(NamableEntry.unnamed(NettyChatWorkerHandler.class))
                       .add(NamableEntry.unnamed(
                               IdleStateHandler.class.getConstructor(int.class,int.class,int.class),
                               new Object[]{1,2,3}))
                       .add(NamableEntry.named("heartStopEventHandler", NettyChatHeartStopEventHandler.class))
        );

§3 示例

server

public class NettyChatServer {

    public void start(int bossThreadCount, int workersThreadCount) {
        EventLoopGroup boss = new NioEventLoopGroup(bossThreadCount);
        EventLoopGroup workers = new NioEventLoopGroup(workersThreadCount);

        try {
            ServerBootstrap starter = new ServerBootstrap()
                    .group(boss,workers)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(
                            GeneralNettyChannelInitializer
                                    .build(NamableEntry.named("decoder",StringDecoder.class))
                                    .add(NamableEntry.named("encoder", StringEncoder.class))
                                    .add(NamableEntry.unnamed(NettyChatWorkerHandler.class))
                                    .add(NamableEntry.unnamed(
                                            IdleStateHandler.class.getConstructor(int.class,int.class,int.class),
                                            new Object[]{1,2,3}))
                                    .add(NamableEntry.named("heartStopEventHandler", NettyChatHeartStopEventHandler.class))
                    );
            ChannelFuture started = starter.bind(7777).sync();
            System.out.println("Server..............................");
            started.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            workers.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new NettyChatServer().start(1,0);
    }
}

server handler

public class NettyChatWorkerHandler extends SimpleChannelInboundHandler<String> {

    private static final DefaultChannelGroup CHANNELS = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /* *******************************
     * 每增加一个 channel 每增加一个handler 时触发
     *
     * PS:正常一个 channel 应该具有若干个 handler
     * 加入聊天的通知功能,应该只是某一个 handler 需要实现的功能
     * 只是这个例子中,都用一个 handler 集成了,实际中不是每个 handlerAdded 时都需要此方法的实现
     ******************************* */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" entereing................");
        CHANNELS.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" leveled................");
    }

    /* *******************************
     * handler 活跃
     * 与服务端连接成功时触发
     ******************************* */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" entered................");
        CHANNELS.add(ctx.channel());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        CHANNELS.writeAndFlush(msg, ChannelMatchers.isNot(ctx.channel()));
    }

}

client

public class NettyChatClient {

    public void start() {
        EventLoopGroup client = new NioEventLoopGroup();

        try (Scanner waiter = new Scanner(System.in)){
            Bootstrap starter = new Bootstrap().group(client)
                    .channel(NioSocketChannel.class)
                    .handler(
                            GeneralNettyChannelInitializer
                                    .build(NamableEntry.named("encoder", StringEncoder.class))
                                    .add(NamableEntry.named("decoder", StringDecoder.class))
                                    .add(NamableEntry.unnamed(NettyChatClientHandler.class))
                    );

            ChannelFuture channelFuture = starter.connect("192.168.3.17", 7777).sync();

            String msg = null;
            while(true){
                if(waiter.hasNextLine()){
                    msg = waiter.nextLine();
                    channelFuture.channel().writeAndFlush(msg);
                    System.out.println();
                }else {
                    TimeUnit.SECONDS.sleep(1);
                }
            }
//            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            client.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new NettyChatClient().start();
    }
}

client handler

public class NettyChatClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + " : " + msg);
    }
}
posted @ 2025-05-20 14:59  问仙长何方蓬莱  阅读(13)  评论(0)    收藏  举报