多人聊天-netty-多线程笔记

Netty 群聊的实现

Ⅰ 传入信息实体类

public class MessageProtocol {
    private int len;    //具体内容转化成字节后的长度
    private byte[] content; //具体内容转化成的字节数组

    public int getLen() {
        return len;
    }

    public void setLen(int len) {
        this.len = len;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }
}

Ⅱ、解码器

public class NettyDeCoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        //将得到的二进制码转化成具体的内容
        int length = byteBuf.readInt(); //获取第一个内容的字节长度
        byte[] content = new byte[length];
        byteBuf.readBytes(content);     //读取对应长度的字节到字节数组中

        //封装成 MessageProtocol 对象  传递给下一个 handler 业务
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(length);
        messageProtocol.setContent(content);
        list.add(messageProtocol);
    }
}

Ⅲ、编码器

public class NettyEnCoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
        byteBuf.writeInt(messageProtocol.getLen());
        byteBuf.writeBytes(messageProtocol.getContent());
    }
}

Ⅳ、服务器端

public class BossServer {
    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            //创建服务启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workGroup)    //设置两个线程组
                    .channel(NioServerSocketChannel.class)  //使用NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128)   //设置线程队列得到的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接的状态
                    .childHandler(new BossServerChannelInitializer());    //具体业务实现

            //绑定端口 并且生成了一个ChannelFuture 对象 启动服务
            ChannelFuture channelFuture = bootstrap.bind(8989).sync();
            //对关闭通道进行监听,并没有立即关闭
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            System.out.println("server 出现异常...");
        } finally{
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }
}
public class BossServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new NettyDeCoder());   //添加解码器
        pipeline.addLast(new NettyEnCoder());   //添加编码器
        pipeline.addLast(new BossServerHandler());     //添加具体业务

    }
}
public class BossServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    //定义一个 channel 组 管理所有的 channel
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //定义一个Map
    private static Map<Date,String> map = new HashMap<>();

    //接收数据并处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
        byte[] content = messageProtocol.getContent();

        String message = new String(content,"utf-8").toString();
        System.out.println("用户:"+channelHandlerContext.channel().remoteAddress()+" 服务端接收数据:"+ message+" channelGroup size:"+channelGroup.size());

        Channel currentChannel = channelHandlerContext.channel();
        for(Channel channel:channelGroup){  //群发
            if(channel != currentChannel){
                transpond("[陌生人]:"+message,channel); //发送给其他人
            }else{
                transpond("[自己]:"+message,channel); //发送给自己,根据自己的需要
            }
        }

    }

    //表示 channel 出于活跃状态,如提示 XX上线
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+"上线");
    }

    //表示 channel 出于活跃状态,如提示 XX下线
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+"下线");
    }

    //将当前的channel 加入到 channelGroup
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //将该客户加入聊天的信息推送个其它在线的客户端,该方法会将 channelGroup 中的所有channel 遍历,并发送消息
        channelGroup.writeAndFlush("[客户端] "+channel.remoteAddress()+" 加入聊天");
        channelGroup.add(ctx.channel());
    }

    //断开连接,将 xx 用户离开信息推送给当前在线的客户
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[客户端] "+channel.remoteAddress()+" 离开了 \n");
    }

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

    //转发给其它用户
    public void transpond(String message,Channel channel){
            byte[] content ;
            int length;
            content = message.getBytes(Charset.forName("utf-8"));
            length = message.getBytes(Charset.forName("utf-8")).length;
            MessageProtocol msgProtocol = new MessageProtocol();
            msgProtocol.setContent(content);
            msgProtocol.setLen(length);
            channel.writeAndFlush(msgProtocol);
    }
}

Ⅴ、客户端

public class WorkerClient {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try{
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new WorkerClientChannelInitializer());
            //启动客户端 连接服务器
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8989).sync();
            channelFuture.channel().closeFuture().sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
public class WorkerClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new NettyDeCoder());
        pipeline.addLast(new NettyEnCoder());
        pipeline.addLast(new WorkerClientHandler());

    }
}
public class WorkerClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {

        int length = messageProtocol.getLen();
        byte[] content = messageProtocol.getContent();

        String message = new String(content,"utf-8");
        System.out.println( message);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //客户端发送数据
        new Thread(() ->sendMessage(ctx)).start();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
    public void sendMessage(ChannelHandlerContext ctx){
        //客户端发送数据
        Scanner in = new Scanner(System.in);
        String message = null;
        try{
            while((message =in.nextLine())!=null) {
//                message = "测试...";
                byte[] content = message.getBytes(Charset.forName("utf-8"));
                int length = message.getBytes(Charset.forName("utf-8")).length;

                System.out.println("===> " +message+" ,remoteAdd :"+ctx.channel().remoteAddress()+",localAd:"+ctx.channel().localAddress());

                MessageProtocol messageProtocol = new MessageProtocol();
                messageProtocol.setContent(content);
                messageProtocol.setLen(length);
                ctx.channel().writeAndFlush(messageProtocol);
            }
        }finally {
            in.close();
        }
    }
}

特别注意:在 WorkerClientHandler 类方法 channelActive中,我这里使用的是 lambada 表达式 结合多线程,在这里千万不要直接使用:

while((message =in.nextLine())!=null)

这行代码,会出现问题。具体如下:
1.Server 和 Client 都正常启动,但 Client 启动后会监听键盘输入
2.Client 端正常输入,结束后又返回继续监听键盘;Server端接收到第一次传来的信息,群发(发给自己和其他的Client);在这里就出现问题了,Client 端的线程都在监听键盘输入,那哪个线程来处理 Server 端传来的信息了。
3.总结:所以我们看到的是 Client1 发送一段消息, Server 端接收到消息,但群发(转发)后的消息没有显示,Client1 、Client2 都没有显示Server 发来的消息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值