SpringBoot使用netty

Netty是由JBOSS提供的一個(gè)java開源框架。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。也就是說,Netty 是一個(gè)基于NIO的客戶、服務(wù)器端編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶、服務(wù)端應(yīng)用。Netty相當(dāng)于簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過程,例如:基于TCP和UDP的socket服務(wù)開發(fā)?!翱焖佟焙汀昂?jiǎn)單”并不用產(chǎn)生維護(hù)性或性能上的問題。Netty 是一個(gè)吸收了多種協(xié)議(包括FTP、SMTP、HTTP等各種二進(jìn)制文本協(xié)議)的實(shí)現(xiàn)經(jīng)驗(yàn),并經(jīng)過相當(dāng)精心設(shè)計(jì)的項(xiàng)目。最終,Netty 成功的找到了一種方式,在保證易于開發(fā)的同時(shí)還保證了其應(yīng)用的性能,穩(wěn)定性和伸縮性

本文將帶領(lǐng)大家學(xué)習(xí)如何在SpringBoot項(xiàng)目中集成Netty

一、Netty服務(wù)端

由于是SpringBoot項(xiàng)目,因此這里不展示SpringBoot相關(guān)的依賴

1、導(dǎo)入依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.36.Final</version>
</dependency>

2、編寫netty處理器

/**
 * Socket攔截器,用于處理客戶端的行為
 *
 * @author Gjing
 **/
@Slf4j
public class SocketHandler extends ChannelInboundHandlerAdapter {
    public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 讀取到客戶端發(fā)來的消息
     *
     * @param ctx ChannelHandlerContext
     * @param msg msg
     * @throws Exception e
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 由于我們配置的是 字節(jié)數(shù)組 編解碼器,所以這里取到的用戶發(fā)來的數(shù)據(jù)是 byte數(shù)組
        byte[] data = (byte[]) msg;
        log.info("收到消息: " + new String(data));
        // 給其他人轉(zhuǎn)發(fā)消息
        for (Channel client : clients) {
            if (!client.equals(ctx.channel())) {
                client.writeAndFlush(data);
            }
        }
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("新的客戶端鏈接:" + ctx.channel().id().asShortText());
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        clients.remove(ctx.channel());
    }

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

3、編寫netty初始化器

/**
 * Socket 初始化器,每一個(gè)Channel進(jìn)來都會(huì)調(diào)用這里的 InitChannel 方法
 * @author Gjing
 **/
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 添加對(duì)byte數(shù)組的編解碼,netty提供了很多編解碼器,你們可以根據(jù)需要選擇
        pipeline.addLast(new ByteArrayDecoder());
        pipeline.addLast(new ByteArrayEncoder());
        // 添加上自己的處理器
        pipeline.addLast(new SocketHandler());
    }
}

4、編寫netty服務(wù)

/**
 * @author Gjing
 **/
@Slf4j
@Component
public class SocketServer {
    @Resource
    private SocketInitializer socketInitializer;

    @Getter
    private ServerBootstrap serverBootstrap;

    /**
     * netty服務(wù)監(jiān)聽端口
     */
    @Value("${netty.port:8088}")
    private int port;
    /**
     * 主線程組數(shù)量
     */
    @Value("${netty.bossThread:1}")
    private int bossThread;

    /**
     * 啟動(dòng)netty服務(wù)器
     */
    public void start() {
        this.init();
        this.serverBootstrap.bind(this.port);
        log.info("Netty started on port: {} (TCP) with boss thread {}", this.port, this.bossThread);
    }

    /**
     * 初始化netty配置
     */
    private void init() {
        // 創(chuàng)建兩個(gè)線程組,bossGroup為接收請(qǐng)求的線程組,一般1-2個(gè)就行
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThread);
        // 實(shí)際工作的線程組
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        this.serverBootstrap = new ServerBootstrap();
        this.serverBootstrap.group(bossGroup, workerGroup) // 兩個(gè)線程組加入進(jìn)來
                .channel(NioServerSocketChannel.class)  // 配置為nio類型
                .childHandler(this.socketInitializer); // 加入自己的初始化器
    }
}

5、啟動(dòng)netty

由于使用SpringBoot,因此我們可以監(jiān)聽項(xiàng)目啟動(dòng)成功后觸發(fā)啟動(dòng)Netty服務(wù)器,這時(shí)候只要SpringBoot啟動(dòng)就行了

/**
 * 監(jiān)聽Spring容器啟動(dòng)完成,完成后啟動(dòng)Netty服務(wù)器
 * @author Gjing
 **/
@Component
public class NettyStartListener implements ApplicationRunner {
    @Resource
    private SocketServer socketServer;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.socketServer.start();
    }
}
  • 效果圖


    image.png

二、Netty客戶端

客戶端這里以NIO來編寫,就不寫Netty了,在實(shí)際工作中,其實(shí)也都是Netty服務(wù)端,客戶端可能是 WebSocket 或者 Socket,我們這里就以 Socket 為例,由于 NIO 是Java提供的,所以我們不需要引入什么依賴

1、編寫客戶端線程

因?yàn)槲覀儾荒茏枞?主線程,因此需要開啟子線程來作為客戶端

/**
 * @author Gjing
 **/
public class ClientThread implements Runnable{
    private final Selector selector;

    public ClientThread(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            for (; ; ) {
                int channels = selector.select();
                if (channels == 0) {
                    continue;
                }
                Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeySet.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey selectionKey = keyIterator.next();

                    // 移除集合當(dāng)前得selectionKey,避免重復(fù)處理
                    keyIterator.remove();
                    if (selectionKey.isReadable()) {
                        this.handleRead(selector, selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 處理可讀狀態(tài)
    private void handleRead(Selector selector, SelectionKey selectionKey) throws IOException {
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        StringBuilder message = new StringBuilder();
        if (channel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            message.append(StandardCharsets.UTF_8.decode(byteBuffer));
        }
        // 再次注冊(cè)到選擇器上,繼續(xù)監(jiān)聽可讀狀態(tài)
        channel.register(selector, SelectionKey.OP_READ);
        System.out.println(message);
    }
}

2、客戶端邏輯

/**
 * 聊天客戶端
 *
 * @author Gjing
 **/
public class ChatClient {

    public void start(String name) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8088));
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);

        // 監(jiān)聽服務(wù)端發(fā)來得消息
        new Thread(new ClientThread(selector)).start();
        // 監(jiān)聽用戶輸入
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String message = scanner.nextLine();
            if (StringUtils.hasText(message)) {
                socketChannel.write(StandardCharsets.UTF_8.encode(name + ": " + message));
            }
        }
    }
}

3、客戶端1

/**
 * @author Gjing
 **/
public class Client1 {
    public static void main(String[] args) throws IOException {
        new ChatClient().start("李四");
    }
}

4、客戶端2

/**
 * @author Gjing
 **/
public class Client2 {
    public static void main(String[] args) throws IOException {
        new ChatClient().start("張三");
    }
}

5、啟動(dòng)這兩個(gè)客戶端

image.png

服務(wù)端也觸發(fā)了日志打印,監(jiān)聽到客戶端加入


image.png

現(xiàn)在我們通過客戶端2,發(fā)一條消息看看


image.png

客戶端1 成功收到了 客戶端2的消息,同理我們通過客戶端1 發(fā)送

image.png

服務(wù)端也輸出了消息日志

image.png

文章到此結(jié)束啦,這就是一個(gè)普通的小案例哦,更多知識(shí)大家可以通過官網(wǎng)進(jìn)行學(xué)習(xí),Demo源代碼地址:SpringBoot-Netty

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容