Netty實現簡單羣聊

場景

使用Netty實現簡單羣聊。服務端實現監控客戶端上下線及通知、羣聊消息轉發。

實現

客戶端與服務端使用String類型的消息進行發送與接收,因此客戶端與服務端需要首先添加Netty封裝的用於網絡傳輸的編碼解密處理器,否則將無法成功打印消息。

.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
        pipeline.addLast("decoder", new StringDecoder());
        //字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
        pipeline.addLast("encoder", new StringEncoder());
        pipeline.addLast(new ServerHandler());
    }
});

服務端

package others.netty.groupChat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Netty羣聊服務端實現
 * 根據不同事件驅動通知客戶端 上下線狀態,消息轉發
 *
 * @author makeDoBetter
 * @version 1.0
 * @date 2021/4/25 10:36
 * @since JDK 1.8
 */
public class Sever {
    private int port;

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

    public void run(){
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap sever = new ServerBootstrap();
        try {
            sever.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE ,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
                            pipeline.addLast("decoder", new StringDecoder());
                            //字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
                            pipeline.addLast("encoder", new StringEncoder());
                            pipeline.addLast(new ServerHandler());
                        }
                    });
            ChannelFuture channelFuture = sever.bind(port).sync();
            //添加一個監聽器
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()){
                        System.out.println("服務端啓動完成");
                    } else {
                        System.out.println("服務端啓動失敗");
                    }
                }
            });
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

    public static void main(String[] args) {
        new Sever(1234).run();
    }
}

自定義服務處理器,用於處理客戶端連接與斷開、消息轉發等。

package others.netty.groupChat;

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;

/**
 * 實現客戶端上下線狀態、消息通知
 *
 * @author makeDoBetter
 * @version 1.0
 * @date 2021/4/25 10:47
 * @since JDK 1.8
 */
public class ServerHandler extends SimpleChannelInboundHandler<String> {
    /**
     * 定義一個全局的單線程的靜態變量,用於存儲整個連接的客戶端集合
     */
    private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //不需要遍歷,group可以直接處理
        group.writeAndFlush("[客戶端]" + channel.remoteAddress() + "上線");
        group.add(channel);
        System.out.println(group.toString());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //不需要手動remove()當前channel,group會自動剔除離線channel
        group.writeAndFlush("[客戶端]" + channel.remoteAddress() + "下線");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel self = ctx.channel();
        //實現消息的轉發,忽略自身
        group.forEach(channel -> {
            if (self != channel) {
                System.out.println(msg + "發送到客戶端" + channel.remoteAddress());
                channel.writeAndFlush("[客戶端]" + self.remoteAddress() + "說:" + msg + "\n");
            }
        });
    }
}

客戶端

package others.netty.groupChat;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

/**
 * 客戶端實現服務端連接,消息發送、接收打印
 *
 * @author makeDoBetter
 * @version 1.0
 * @date 2021/4/25 11:23
 * @since JDK 1.8
 */
public class Client {
    private String host;
    private int port;

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

    public void run(){
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
                            pipeline.addLast("decoder", new StringDecoder());
                            //字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
                            pipeline.addLast("encoder", new StringEncoder());
                            pipeline.addLast(new ClientHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()){
                        System.out.println("====連接成功=====");
                    } else {
                        System.out.println("客戶端連接失敗");
                    }
                }
            });
            Channel channel = channelFuture.channel();
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
                String line = scanner.nextLine();
                channel.writeAndFlush(line);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new Client("127.0.0.1", 1234).run();
    }
}

客戶端處理程序,打印轉發到當前客戶端的消息。

package others.netty.groupChat;

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

/**
 * 客戶端讀取數據事件
 *
 * @author makeDoBetter
 * @version 1.0
 * @date 2021/4/25 11:37
 * @since JDK 1.8
 */
public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg.trim());
    }
}

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