在Netty聊天室應用程式中自定義事件處理程式和偵聽器

banq發表於2024-04-27

Netty是一個用 Java 構建高效能、可擴充套件的網路應用程式的框架。它的關鍵特性之一是事件驅動的架構,它使我們能夠有效地處理網路事件。在本文中,我們將深入探討如何在 Netty 聊天室應用程式中自定義事件處理程式和偵聽器。

Netty的事件模型
Netty 是圍繞通道和處理程式的概念構建的。當網路事件發生時(例如接收資料或新連線),Netty會觸發由特定事件處理程式處理的事件。可以自定義這些處理程式以根據事件型別執行各種任務。

什麼是事件處理程式
Netty 中的自定義事件處理程式是該ChannelHandler介面的實現。該介面定義了處理各種通道事件的方法,例如通道啟用、資料讀取、通道關閉和異常。透過擴充套件SimpleChannelInboundHandler,我們可以有選擇地重寫與我們想要處理的事件相對應的方法。

Netty中的監聽器可以使用該介面來實現ChannelFutureListener。這個介面允許我們指定當a上的某個操作ChannelFuture完成時應該執行的任務。例如,我們可以定義一個偵聽器來處理寫入操作完成後的操作。

使用 Netty 構建聊天室應用程式
讓我們使用 Netty 建立一個簡單的聊天室應用程式。我們將使用自定義事件處理程式來管理傳入訊息並向所有連線的客戶端廣播。下面詳細介紹瞭如何利用自定義事件處理程式和偵聽器來建立聊天室應用程式:

  • 聊天伺服器處理程式:此處理程式應擴充套件SimpleChannelInboundHandler<String>.它應該實現channelRead0處理傳入聊天訊息的方法。收到訊息後,它應該將其廣播給聊天室中所有連線的使用者。
  • 聊天客戶端處理程式:此處理程式還應該擴充套件SimpleChannelInboundHandler<String>.它應該處理從伺服器收到的傳入聊天訊息並將其顯示給使用者。此外,它應該實現處理使用者輸入並向伺服器傳送訊息的方法。
  • 使用者管理:伺服器需要維護已連線使用者的列表。這可以透過使用List<Channel>儲存已連線客戶端通道的列表來實現。

設定依賴關係
首先,我們確保已設定 Maven 來管理專案依賴項。在您的pom.xml(對於 Maven)中包含以下 Netty 依賴項:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.105.Final</version>
</dependency>

實現聊天伺服器
首先,我們將建立一個聊天伺服器ChatServer.java,將訊息回顯給所有連線的客戶端。

聊天伺服器
這是負責引導伺服器通道並啟動伺服器的伺服器程式碼。此程式碼將伺服器繫結到指定埠,它將在其中主動偵聽傳入連線。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import java.util.List;
import java.util.ArrayList;

public class ChatServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new ChatServerHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
            System.out.println(<font>"Chat server started successfully and is ready to accept clients.");
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

上面的程式碼塊設定並初始化一個基於 Netty 的聊天伺服器,定義它如何處理傳入連線、處理 I/O 操作和管理客戶端通訊。

首先,我們建立兩個EventLoopGroup例項:bossGroup用於處理傳入連線和workerGroup用於處理 I/O 工作。

我們初始化一個ServerBootstrap例項來設定和配置伺服器,並將 和 與 分別關聯bossGroup以workerGroup處理ServerBootstrap傳入連線和 I/O 操作。接下來,我們指定NioServerSocketChannel為通道型別,表示使用基於 NIO 的伺服器套接字來接受傳入連線。

接下來,我們設定一個來配置伺服器建立的ChannelInitializer新例項,併為每個例項定義一個管道,其中包括一個用於解碼傳入訊息的管道和一個用於編碼傳出訊息的管道。我們還向管道新增了一個自定義,負責處理傳入訊息和管理客戶端連線。SocketChannelSocketChannelStringDecoderStringEncoderChatServerHandler

8888接下來,我們使用繫結伺服器到埠serverBootstrap.bind(8888).sync(),啟動伺服器的監聽程序,並使用 ( channelFuture.channel().closeFuture().sync()) 保持伺服器執行直到終止。

聊天伺服器處理程式
這是聊天伺服器使用的處理程式。

class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    <font>// List of connected client channels.<i>
    static final List<Channel> connectedUsers = new ArrayList<>();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(
"Client connected: " + ctx.channel().remoteAddress());
        connectedUsers.add(ctx.channel());
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, String receivedMessage) throws Exception {

        System.out.println(
"Server Received message: " + receivedMessage);

       
// Broadcast the received message to all connected clients<i>
        for (Channel channel : connectedUsers) {
            channel.writeAndFlush(
" " + receivedMessage + '\n');
        }
    }

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

實現聊天客戶端
接下來,讓我們實現一個連線到我們的伺服器的簡單聊天客戶端。

聊天客戶端

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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;

public class ChatClient {

    static String clientName;

    public static void main(String[] args) throws Exception {

        <font>// Get name of the user for this chat session.       <i>
        Scanner scanner = new Scanner(System.in);
        System.out.println(
"Please enter your name: ");
        if (scanner.hasNext()) {
            clientName = scanner.nextLine();
            System.out.println(
"Welcome " + clientName);
        }

        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new ChatClientHandler());
                        }
                    });

            ChannelFuture cf = bootstrap.connect(
"localhost", 8888).sync();

            while (scanner.hasNext()) {
                String input = scanner.nextLine();
                Channel channel = cf.sync().channel();
                channel.writeAndFlush(
"[" + clientName + "]: " + input);
                channel.flush();
            }

            cf.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

上面的程式碼配置了一個Bootstrap用於設定客戶端的例項。在此類中,我們指定NioSocketChannel用於網路通訊的 a 並設定 aChannelInitializer來定義通道的初始化方式。StringDecoder並StringEncoder新增到管道中以處理入站和出站訊息。我們還ChatClientHandler向管道新增了一個自定義來處理傳入訊息。

接下來,我們使用配置localhost的埠連線到執行的聊天伺服器,並使用( ) 進行非同步操作並連線到伺服器。8888BootstrapChannelFuturecf

傳送訊息
該main方法透過要求使用者使用 輸入姓名來開始執行聊天客戶端Scanner。為了傳送訊息,程式進入一個迴圈,使用 連續讀取使用者輸入,從( )Scanner檢索通道,並以 格式將使用者輸入傳送到伺服器。ChannelFuturecf"[clientName]: message"

聊天客戶端處理程式類
下面是為客戶端列印聊天訊息的通道處理程式類。

class ChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, String receivedMessage) throws Exception {
        System.out.println(<font>"Received message: " + receivedMessage);
    }

}

執行聊天應用程式
以下是執行此示例的步驟。首先,使用以下命令在單獨的終端視窗中編譯並執行伺服器類 ( ChatServer.java),並等待它啟動並準備好接受客戶端。留意日誌中是否有任何訊息,如圖 1.0 所示


mvn exec:java -Dexec.mainClass=com.jcg.chatserver.ChatServer

伺服器啟動並執行後,在單獨的終端視窗中啟動兩個或多個客戶端例項,並ChatClient.java)使用以下命令執行 (:

mvn exec:java -Dexec.mainClass=com.jcg.chatclient.ChatClient

在控制檯中提示時輸入使用者名稱,然後透過控制檯輸入交換聊天訊息並觀察訊息傳遞到其他客戶端。

相關文章