1. 程式人生 > >03_netty實現聊天室功能

03_netty實現聊天室功能

class ktr 連接 bind line 服務 RKE 客戶端 chatroom

【概述】

聊天室主要由兩塊組成:聊天服務器端(ChatRoomServer)和聊天客戶端(ChatClient)。

[ 聊天服務器(ChatRoomServer)功能概述 ]

1.監聽所有客戶端的接入、斷線

2.有客戶端A接入聊天室時,將接入消息發給除了客戶端A的其他客戶端

3.當客戶端A退出聊天室時,將退出消息發給除了客戶端A的其他客戶端

4.當客戶端A發送消息到聊天室時,將消息轉發給除了客戶端A的其他客戶端

[ 聊天客戶端(ChatClient)功能概述 ]

1.發送消息至聊天服務器

2.接收聊天服務器發送過來的所有消息

【聊天服務端 ChatRoomServer】

/**
 * 聊天室服務端
 */
public class ChatRoomServer {

    private final int port ;


    public ChatRoomServer(int port) {
        this.port = port;
    }

    public void start(){
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        
try{ ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(boss,worker) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitialize()) .option(ChannelOption.SO_BACKLOG, 128) .option(ChannelOption.SO_KEEPALIVE,
true); ChannelFuture future = bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); }catch (Exception e){ e.printStackTrace(); }finally { boss.shutdownGracefully(); worker.shutdownGracefully(); } } public static void main(String[] args) { new ChatRoomServer(9999).start(); //服務端監聽本地的9999端口 } }

【ChatServerInitialize】

public class ChatServerInitialize extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        System.out.println("用戶【"+channel.remoteAddress()+"】接入聊天室......");
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder",new StringDecoder());
        pipeline.addLast("encoder",new StringEncoder());
        pipeline.addLast("handler",new ChatServerHandler());
    }
}

【ChatServerHandler】

/**
 * 聊天服務器對各種情況的處理
 */
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    public static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 當從服務端收到新的客戶端連接時
     * 客戶端的 Channel 存入 channels 列表中,並通知列表中的其他客戶端 Channel
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel clientChannel = ctx.channel();
        channels.add(clientChannel);
        for (Channel ch : channels) {
            if (ch != clientChannel) {  //通知除了自己以外的其他用戶
                ch.writeAndFlush("【提示】:用戶【" + clientChannel.remoteAddress() + "】進入聊天室...\n");
            }
        }
    }


    /**
     * 每當從服務端收到客戶端斷開時
     * 客戶端的 Channel 自動從 channels 列表中移除了,並通知列表中的其他客戶端 Channel
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel clientChannel = ctx.channel();
        channels.remove(clientChannel);
        for (Channel ch : channels) {
            if (ch != clientChannel) {  //通知除了自己以外的其他用戶
                ch.writeAndFlush("【提示】:用戶【" + clientChannel.remoteAddress() + "】退出聊天室...\n");
            }
        }
    }

    /**
     * 接受到客戶端發出的消息
     * 判斷channel是否是
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel clientChannel = ctx.channel();
        for (Channel ch : channels) {
            if (ch != clientChannel) {
                ch.writeAndFlush("用戶【" + clientChannel.remoteAddress() + "】說:" + msg + "\n");
            } else {
                ch.writeAndFlush("【我】說:" + msg + "\n");
            }
        }
    }

    /**
     * 服務端監聽到客戶端活動
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel clientChannel = ctx.channel();
        System.out.println("用戶【"+clientChannel.remoteAddress()+"】在線中...");
    }

    /**
     * 服務端監聽到客戶端 不活動
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel clientChannel = ctx.channel();
        System.out.println("用戶【 " +clientChannel.remoteAddress()+"】:離線了");

    }
}

【ChatClient 聊天客戶端】

/**
 * 聊天客戶端
 */
public class ChatClient {

    private final String host;

    private final int port;

    public ChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() {
        EventLoopGroup worker = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();

        try{
            bootstrap.group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());
            Channel channel  = bootstrap.connect(host,port).sync().channel();
            //客戶端從鍵盤輸入數據
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            while(true){
                channel.writeAndFlush(input.readLine()+"\n");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            worker.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new ChatClient("127.0.0.1",9999).start(); //連接服務器端
    }

}

【ChatClientInitializer 】

public class ChatClientInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //當有客戶端連接服務器時,netty會調用這個初始化器的 initChannel方法
        System.out.println("客戶端開始初始化......");

        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder",new StringDecoder());
        pipeline.addLast("encoder",new StringEncoder());
        pipeline.addLast("handler",new ChatClientHandler());
    }
}

【ChatClientHandler】

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

    /**
     * 打印服務端發送過來的數據
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        System.out.println(s);
    }
}

【運行結果】

[1.啟動聊天服務器]

技術分享圖片

[2.啟動一個客戶端A]

技術分享圖片

技術分享圖片

[3.再啟動一個客戶端B]

技術分享圖片

技術分享圖片

技術分享圖片

[4.客戶端A發送消息]

技術分享圖片

技術分享圖片

[5.客戶端A關閉]

技術分享圖片

技術分享圖片

03_netty實現聊天室功能