work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Netty 多客户端连接与通信

Posted on 2019-08-03 22:22  work hard work smart  阅读(3362)  评论(0编辑  收藏  举报

实现场景: 聊天

服务端,客户端A,客户端B,客户端C。当客户端发送消息给服务端后,服务端在将这条消息广播个所有客户端户端A,客户端B,客户端C。

需求1: 客户端上线后,会通知所有客户端上线。

如客户端A先建立连接,不需要通知。

当客户端B与服务端建立连接,服务端告诉A,客户端B上线。

A和B建立连接后,客户端C和服务端建立连接。服务端广播一条信息给A和B。

需求2: A、B、C都已经建立连接,当A发送一条给服务端,服务端广播这条消息给所有客户端,客户端A会提示这条消息是自己发送的。

 

一、服务端程序的编写

1、MyChartServer 类

public class MyChartServer {
    public static void main(String[] args) throws  Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new MyChatServerInitializer());

            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

  

  

2、MyChatServerInitializer 类

public class MyChatServerInitializer extends ChannelInitializer<SocketChannel>{

    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new MyChartServerHandle());
    }
}

  

 

3、MyChartServerHandle 类

public class MyChartServerHandle extends SimpleChannelInboundHandler<String>{


    //用于保存所有Channel对象
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //channel为当前客户端发给服务端的channel
        Channel channel = ctx.channel();
        channelGroup.forEach(ch -> {

            if(channel != ch){
                ch.writeAndFlush(channel.remoteAddress() + " 发送的消息:" + msg + "\n");
            } else{
                ch.writeAndFlush("[自己]" + msg + " \n");
            }
        });

    }

    //表示连接建立
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
       //chanel可以理解成Connection
        Channel channel = ctx.channel();
        //广播消息给所有的客户端
        channelGroup.writeAndFlush("[服务器] - " + channel.remoteAddress() + " 加入\n");
        channelGroup.add(channel);
    }

    //表示连接断掉了
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //广播消息给所有的客户端
        channelGroup.writeAndFlush("[服务器] - " + channel.remoteAddress() + " 离开\n");
        //下面这行代码Netty会自动调用
        //channelGroup.remove(channel);
    }

    //表示连接时活动状态
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //广播消息给所有的客户端
        CommonUtil.println( channel.remoteAddress() + " 上线 \n");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //广播消息给所有的客户端
        CommonUtil.println( channel.remoteAddress() + " 下线 \n");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();;
        ctx.close();
    }
}

  

  二、客户端程序编写

1、MyChatClient类

public class MyChatClient {
    public static void main(String[] args) throws  Exception{
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new MyChatClientInitializer());

            //channel表示与服务端的Connection
            Channel channel = bootstrap.connect("localhost",8899).sync().channel();
            //不断读取客户端输入的内容
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

            for(;;){
                channel.writeAndFlush(br.readLine() + "\r\n");
            }

        }finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

  

  2、MyChatClientInitializer  类

public class MyChatClientInitializer  extends ChannelInitializer<SocketChannel> {



    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new MyChartClientHandle());
    }
}

  

3、MyChartClientHandle 类

public class MyChartClientHandle extends SimpleChannelInboundHandler<String> {

    // 对于客户端来说,输入来自控制台
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //仅仅打印来自服务端的信息
        CommonUtil.println(msg);

    }


}

  

三、测试

1、启动MyChartServer,然后启动MyChatClient

MyChartServer打印 59786 上线

 

2、再启动一个客户端

可以发现60635 上线,

 

并且第一个客户端提示 60635 加入

 

3、再启动一个客户端

提示60966上线

 

第一个客户端 提示60966 加入

第二个客户端 提示60966 加入

 

四、测试2

1、客户端A发送消息: 大家好,我是客户端A

客户端A接收到写信息:  [自己]大家好,我是客户端A 

 

客户端B接收到信息

 

客户端C接收到的信息

 

自此,实现了通过多个Socket实现的通信的过程