三、Netty多客戶端連接與通信

一、概述

netty作爲服務端時,可以連接多個客戶端。利用此特性,可以開發一個簡單的羣聊應用。包含以下功能:

  • 服務端記錄接入
  • 上線下線提醒
  • 消息羣發

功能分析:

  • 服務端記錄接入: netty的handle中SimpleChannelInboundHandler的channelActive方法可以監聽channel是否接入,利用此回調方法來監聽客戶端是否接入
  • 上線下線提醒:handleAdded方法可以監聽接入的客戶端是否活動,此方法可以監聽上線與下線
  • 消息羣發:channelRead0可以實現服務端和客戶端的相互通信,客戶端利用channelRead0中的ChannelHandlerContext對象向服務端發送消息,服務端接收到消息後利用ChannelHandlerContext向客戶端回寫消息;ChannelGroup用於存儲當前與服務端連接的客戶端channel,遍歷該group便可以發送不同的消息給客戶端

二、代碼實現

1. 服務端

1.1 主程序
package com.learner.netty.ch3.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.channels.SocketChannel;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-04
 */
public class Server {

    public static void main(String[] args) throws InterruptedException {
        // 1. 創建兩個線程組
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            // 2. 創建ServerBootStrap
            ServerBootstrap bootstrap = new ServerBootstrap();
            ChannelFuture channelFuture = bootstrap.group(boosGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ServerInitializer())
                    .bind(8899)
                    .sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 3. 關閉
            boosGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }
}
1.2 ServerInitializer
package com.learner.netty.ch3.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-04
 */
public class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // ★ 分隔符解碼器
        pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ServerHandle());
    }
}
1.3 ServerHandle
package com.learner.netty.ch3.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.logging.SocketHandler;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-04
 */
public class ServerHandle extends SimpleChannelInboundHandler<String> {

    /**  **/
    private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 1. 獲取channel
        Channel channel = ctx.channel();
        channels.forEach(ch -> {
            if (ch != channel) {
                ch.writeAndFlush("【" + channel.remoteAddress() + "】:" + msg + "\n");
            } else {
                ch.writeAndFlush("【自己】發送的消息:" + msg + "\n");

            }
        });
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 1. 獲取channel
        Channel channel = ctx.channel();
        // 2. 服務器發送通知
        channels.writeAndFlush("【服務器】 - " + channel.remoteAddress() + " 加入\n");
        // 3. 將新的channel添加至channelGroup
        channels.add(channel);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 1. 獲取channel
        Channel channel = ctx.channel();
        // 2. 服務器發送通知
        channels.writeAndFlush("【服務器】 - " + channel.remoteAddress() + " 離開\n");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + " 上線");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + " 下線");
    }

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

2. 客戶端

2.1 客戶端主程序
package com.learner.netty.ch3.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-07
 */
public class Client {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 1. 創建EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // 2. 創建BootStrap
            Bootstrap bootstrap = new Bootstrap();

            // 3. 註冊initializer,handle
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            // 4. 獲取channel
            Channel channel = bootstrap.connect("localhost", 8899).sync().channel();

            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            for(;;) {
                channel.writeAndFlush( reader.readLine() + "\r\n");
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}
2.2 ClientInitializer
package com.learner.netty.ch3.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-07
 */
public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    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 ClientHandle());
    }
}
2.3 ClientHandle
package com.learner.netty.ch3.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-12-07
 */
public class ClientHandle extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg);
    }
}

啓動多個客戶端,在控制檯輸入消息,便可以接收到信息了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章