最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Netty快速實(shí)現(xiàn)一個(gè)群聊功能的示例詳解

 更新時(shí)間:2023年11月22日 08:20:53   作者:王二蛋呀  
這篇文章主要為大家詳細(xì)介紹了如何利用?Netty?框架開(kāi)發(fā)一個(gè)?WebSocket?服務(wù)端,從而實(shí)現(xiàn)一個(gè)簡(jiǎn)單的在線聊天功能,感興趣的小伙伴可以了解下

前言

通過(guò)之前的文章介紹,我們可以深刻認(rèn)識(shí)到Netty在網(wǎng)絡(luò)編程領(lǐng)域的卓越表現(xiàn)和強(qiáng)大實(shí)力。這篇文章將介紹如何利用 Netty 框架開(kāi)發(fā)一個(gè) WebSocket 服務(wù)端,從而實(shí)現(xiàn)一個(gè)簡(jiǎn)單的在線聊天功能。

聲明

文章中所提供的代碼僅供參考,旨在幫助無(wú) Netty 經(jīng)驗(yàn)的開(kāi)發(fā)人員快速上手。請(qǐng)注意,這些代碼并不適用于實(shí)際應(yīng)用中。

功能說(shuō)明

聊天頁(yè)面

  • 用戶進(jìn)入頁(yè)面后,會(huì)看到一個(gè)簡(jiǎn)單的文本框,可以用來(lái)發(fā)送消息。
  • 頁(yè)面下方會(huì)顯示聊天的消息內(nèi)容。

服務(wù)端主要有以下三個(gè)功能:

  • 響應(yīng)聊天頁(yè)面:用來(lái)接收和響應(yīng)聊天頁(yè)面的請(qǐng)求。
  • 處理消息:對(duì)接收到的消息進(jìn)行處理。
  • 實(shí)現(xiàn)群聊功能:提供群聊的功能,使多個(gè)用戶能夠在同一個(gè)聊天室中進(jìn)行交流。

功能很簡(jiǎn)單,但是可以通過(guò)這個(gè)示例實(shí)現(xiàn)更多復(fù)雜的場(chǎng)景。

實(shí)現(xiàn)步驟

創(chuàng)建一個(gè)簡(jiǎn)單的 Maven 項(xiàng)目,直接引入 netty-all 包即可編碼。

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

實(shí)現(xiàn)該功能共有五個(gè)類,如下:

├── MakeIndexPage.java
├── ProcessWsIndexPageHandler.java
├── ProcesssWsFrameHandler.java
├── WebSocketServer.java
└── WebSocketServerInitializer.java

下面對(duì)實(shí)現(xiàn)該功能所涉及的五個(gè)類的代碼進(jìn)行詳細(xì)說(shuō)明

WebSocket 服務(wù)啟動(dòng)

這個(gè)類是一個(gè)基于 Netty 啟動(dòng)的常規(guī)服務(wù)端。它包含了一些配置項(xiàng),包括 Reactor 模式、IO 類型以及消息處理配置,大部分都是這樣。代碼如下:

/**
 * 類說(shuō)明:
 */
public final class WebSocketServer {

    /*創(chuàng)建 DefaultChannelGroup,用來(lái)保存所
    有已經(jīng)連接的 WebSocket Channel,群發(fā)和一對(duì)一功能可以用上*/
    private final static ChannelGroup channelGroup =
            new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);

    static final boolean SSL = false;//是否啟用ssl
    /*通過(guò)ssl訪問(wèn)端口為8443,否則為8080*/
    static final int PORT
            = Integer.parseInt(
                    System.getProperty("port", SSL? "8443" : "80"));

    public static void main(String[] args) throws Exception {
        /*SSL配置*/
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(),
                    ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new WebSocketServerInitializer(sslCtx,channelGroup));

            Channel ch = b.bind(PORT).sync().channel();

            System.out.println("打開(kāi)瀏覽器訪問(wèn): " +
                    (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');

            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

Channel 初始化

這個(gè)類的主要功能是創(chuàng)建了一個(gè) ChannelInitializer,用于初始化 ChannelPipeline,并添加了一些通道處理器。這些處理器包括由Netty提供的處理SSL協(xié)議、處理HTTP協(xié)議和支持WebSocket協(xié)議的功能,還有一些由業(yè)務(wù)自定義的處理器,用于處理頁(yè)面展示和處理WebSocket數(shù)據(jù)。代碼如下:

/**
 * 類說(shuō)明:增加handler
 */
public class WebSocketServerInitializer
        extends ChannelInitializer<SocketChannel> {

    private final ChannelGroup group;

    /*websocket訪問(wèn)路徑*/
    private static final String WEBSOCKET_PATH = "/chat";

    private final SslContext sslCtx;

    public WebSocketServerInitializer(SslContext sslCtx,ChannelGroup group) {
        this.sslCtx = sslCtx;
        this.group = group;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        /*增加對(duì)http的支持*/
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));

        /*Netty提供,支持WebSocket應(yīng)答數(shù)據(jù)壓縮傳輸*/
        pipeline.addLast(new WebSocketServerCompressionHandler());
        /*Netty提供,對(duì)整個(gè)websocket的通信進(jìn)行了初始化(發(fā)現(xiàn)http報(bào)文中有升級(jí)為websocket的請(qǐng)求)
        ,包括握手,以及以后的一些通信控制*/
        pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH,
                null, true));

        /*瀏覽器訪問(wèn)時(shí)展示index頁(yè)面*/
        pipeline.addLast(new ProcessWsIndexPageHandler(WEBSOCKET_PATH));

        /*對(duì)websocket的數(shù)據(jù)進(jìn)行處理*/
        pipeline.addLast(new ProcesssWsFrameHandler(group));
    }
}

HTTP 請(qǐng)求處理

這個(gè)類的主要功能是在收到 HTTP 請(qǐng)求時(shí),當(dāng) URI 為“/”或“/index.html”時(shí),會(huì)返回一個(gè)聊天界面作為響應(yīng)。代碼如下:

/**
 * 類說(shuō)明:對(duì)http請(qǐng)求,將index的頁(yè)面返回給前端
 */
public class ProcessWsIndexPageHandler
        extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String websocketPath;

    public ProcessWsIndexPageHandler(String websocketPath) {
        this.websocketPath = websocketPath;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx,
                                FullHttpRequest req) throws Exception {
        // 處理錯(cuò)誤或者無(wú)法解析的http請(qǐng)求
        if (!req.decoderResult().isSuccess()) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
            return;
        }

        //只允許Get請(qǐng)求
        if (req.method() != GET) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
            return;
        }

        // 發(fā)送index頁(yè)面的內(nèi)容
        if ("/".equals(req.uri()) || "/index.html".equals(req.uri())) {
            //生成WebSocket的訪問(wèn)地址,寫(xiě)入index頁(yè)面中
            String webSocketLocation
                    = getWebSocketLocation(ctx.pipeline(), req,
                    websocketPath);
            System.out.println("WebSocketLocation:["+webSocketLocation+"]");
            //生成index頁(yè)面的具體內(nèi)容,并送往瀏覽器
            ByteBuf content
                    = MakeIndexPage.getContent(
                            webSocketLocation);
            FullHttpResponse res = new DefaultFullHttpResponse(
                    HTTP_1_1, OK, content);

            res.headers().set(HttpHeaderNames.CONTENT_TYPE,
                    "text/html; charset=UTF-8");
            HttpUtil.setContentLength(res, content.readableBytes());

            sendHttpResponse(ctx, req, res);
        } else {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND));
        }
    }

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

    /*發(fā)送應(yīng)答*/
    private static void sendHttpResponse(ChannelHandlerContext ctx,
                                         FullHttpRequest req,
                                         FullHttpResponse res) {
        // 錯(cuò)誤的請(qǐng)求進(jìn)行處理 (code<>200).
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),
                    CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpUtil.setContentLength(res, res.content().readableBytes());
        }

        // 發(fā)送應(yīng)答.
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        //對(duì)于不是長(zhǎng)連接或者錯(cuò)誤的請(qǐng)求直接關(guān)閉連接
        if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /*根據(jù)用戶的訪問(wèn),告訴用戶的瀏覽器,WebSocket的訪問(wèn)地址*/
    private static String getWebSocketLocation(ChannelPipeline cp,
                                               HttpRequest req,
                                               String path) {
        String protocol = "ws";
        if (cp.get(SslHandler.class) != null) {
            protocol = "wss";
        }
        return protocol + "://" + req.headers().get(HttpHeaderNames.HOST)
                + path;
    }
}

HTTP 頁(yè)面內(nèi)容

這個(gè)類的主要目的是生成一個(gè)包含消息發(fā)送框和內(nèi)容展示功能的HTML頁(yè)面,并實(shí)現(xiàn)WebSocket的相關(guān)功能,包括建立連接、向服務(wù)端發(fā)送消息以及接收服務(wù)端的響應(yīng)。當(dāng)然,也可以單獨(dú)寫(xiě)一個(gè)HTML文件。代碼如下:

/**
 * 類說(shuō)明:生成index頁(yè)面的內(nèi)容
 */
public final class MakeIndexPage {

    private static final String NEWLINE = "\r\n";

    public static ByteBuf getContent(String webSocketLocation) {
        return Unpooled.copiedBuffer(
                "<html><head><title>Web Socket Test</title><meta charset=\"utf-8\" /></head>"
                        + NEWLINE +
                "<body>" + NEWLINE +
                "<script type=\"text/javascript\">" + NEWLINE +
                "var socket;" + NEWLINE +
                "if (!window.WebSocket) {" + NEWLINE +
                "  window.WebSocket = window.MozWebSocket;" + NEWLINE +
                '}' + NEWLINE +
                "if (window.WebSocket) {" + NEWLINE +
                "  socket = new WebSocket(\"" + webSocketLocation + "\");"
                        + NEWLINE +
                "  socket.onmessage = function(event) {" + NEWLINE +
                "    var ta = document.getElementById('responseText');"
                        + NEWLINE +
                "    ta.value = ta.value + '\\n' + event.data" + NEWLINE +
                "  };" + NEWLINE +
                "  socket.onopen = function(event) {" + NEWLINE +
                "    var ta = document.getElementById('responseText');"
                        + NEWLINE +
                "    ta.value = \"Web Socket opened!\";" + NEWLINE +
                "  };" + NEWLINE +
                "  socket.onclose = function(event) {" + NEWLINE +
                "    var ta = document.getElementById('responseText');"
                        + NEWLINE +
                "    ta.value = ta.value + \"Web Socket closed\"; "
                        + NEWLINE +
                "  };" + NEWLINE +
                "} else {" + NEWLINE +
                "  alert(\"Your browser does not support Web Socket.\");"
                        + NEWLINE +
                '}' + NEWLINE +
                NEWLINE +
                "function send(message) {" + NEWLINE +
                "  if (!window.WebSocket) { return; }" + NEWLINE +
                "  if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +
                "    socket.send(message);" + NEWLINE +
                "  } else {" + NEWLINE +
                "    alert(\"The socket is not open.\");" + NEWLINE +
                "  }" + NEWLINE +
                '}' + NEWLINE +
                "</script>" + NEWLINE +
                "<form onsubmit=\"return false;\">" + NEWLINE +
                "<input type=\"text\" name=\"message\" " +
                        "value=\"Hi, 你好啊\"/>" +
                "<input type=\"button\" value=\"發(fā)送\""
                        + NEWLINE +
                "       onclick=\"send(this.form.message.value)\" />"
                        + NEWLINE +
                "<h3>消息內(nèi)容</h3>" + NEWLINE +
                "<textarea id=\"responseText\" " +
                        "style=\"width:500px;height:300px;\"></textarea>"
                        + NEWLINE +
                "</form>" + NEWLINE +
                "</body>" + NEWLINE +
                "</html>" + NEWLINE, CharsetUtil.UTF_8);
    }

}

WebSocket 請(qǐng)求處理

這個(gè)類的主要功能是處理與 Channel 相關(guān)的事件。例如,當(dāng)一個(gè) Channel 連接成功時(shí),會(huì)將該 Channel 添加到一個(gè) ChannelGroup 中。當(dāng)接收到該 Channel 的數(shù)據(jù)時(shí),可以通過(guò)向 ChannelGroup 寫(xiě)入數(shù)據(jù)來(lái)實(shí)現(xiàn)群聊效果。代碼如下

/**
 * 類說(shuō)明:對(duì)websocket的數(shù)據(jù)進(jìn)行處理
 */
public class ProcesssWsFrameHandler
        extends SimpleChannelInboundHandler<WebSocketFrame> {

    private final ChannelGroup group;

    public ProcesssWsFrameHandler(ChannelGroup group) {
        this.group = group;
    }

    private static final Logger logger
            = LoggerFactory.getLogger(ProcesssWsFrameHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx,
                                WebSocketFrame frame) throws Exception {
        //判斷是否為文本幀,目前只處理文本幀
        if (frame instanceof TextWebSocketFrame) {
            // Send the uppercase string back.
            String request = ((TextWebSocketFrame) frame).text();
            logger.info("{} received {}", ctx.channel(), request);
//            ctx.channel().writeAndFlush(
//                    new TextWebSocketFrame(request.toUpperCase(Locale.CHINA)));
            /*群發(fā)實(shí)現(xiàn):一對(duì)一道理一樣*/
            group.writeAndFlush(new TextWebSocketFrame(
                    ctx.channel().remoteAddress() + " :" + request.toUpperCase(Locale.CHINA)));
        } else {
            String message = "unsupported frame type: "
                    + frame.getClass().getName();
            throw new UnsupportedOperationException(message);
        }
    }

    /*重寫(xiě) userEventTriggered()方法以處理自定義事件*/
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx,
                                   Object evt) throws Exception {
        /*檢測(cè)事件,如果是握手成功事件,做點(diǎn)業(yè)務(wù)處理*/
        if (evt == WebSocketServerProtocolHandler
                .ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {

            //通知所有已經(jīng)連接的 WebSocket 客戶端新的客戶端已經(jīng)連接上了
            group.writeAndFlush(new TextWebSocketFrame(
                    "Client " + ctx.channel().remoteAddress() + " joined"));

            //將新的 WebSocket Channel 添加到 ChannelGroup 中,
            // 以便它可以接收到所有的消息
            group.add(ctx.channel());
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

效果展示

服務(wù)端啟動(dòng)

聊天頁(yè)面1

聊天頁(yè)面2

總結(jié)

總的來(lái)說(shuō),基于 Netty 實(shí)現(xiàn)一個(gè) WebSocket 功能是非常方便且高效的,但是我們需要知其所以然,要理解 Websocket 協(xié)議,也要懂的在 Netty 中,通過(guò)添加 ChannelHandler 來(lái)處理各種異常情況,例如握手失敗、連接關(guān)閉等,當(dāng)然,還要考慮安全性問(wèn)題,例如處理跨站腳本攻擊(XSS)、防止惡意數(shù)據(jù)傳輸?shù)取?/p>

以上就是使用Netty快速實(shí)現(xiàn)一個(gè)群聊功能的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Netty實(shí)現(xiàn)群聊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IDEA中try catch拋異常快捷鍵分享

    IDEA中try catch拋異??旖萱I分享

    在編寫(xiě)Java代碼時(shí),使用IDEA的快捷鍵CTRL+ALT+t可以快速生成try..catch語(yǔ)句塊,有效提高編碼效率,首先選擇需要處理的代碼片段,然后按下快捷鍵,選擇try/catch選項(xiàng),即可自動(dòng)包圍選中代碼,這一快捷操作簡(jiǎn)化了異常處理步驟,減少了手動(dòng)編寫(xiě)的時(shí)間,是編程中的實(shí)用技巧
    2024-10-10
  • Java實(shí)戰(zhàn)之基于I/O流設(shè)計(jì)的圖書(shū)管理系統(tǒng)

    Java實(shí)戰(zhàn)之基于I/O流設(shè)計(jì)的圖書(shū)管理系統(tǒng)

    這篇文章主要介紹了Java實(shí)戰(zhàn)之基于I/O流設(shè)計(jì)的圖書(shū)館管理系統(tǒng),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • Java Web學(xué)習(xí)之Cookie和Session的深入理解

    Java Web學(xué)習(xí)之Cookie和Session的深入理解

    這篇文章主要給大家介紹了關(guān)于Java Web學(xué)習(xí)之Cookie和Session的相關(guān)資料,需要的朋友可以參考下
    2018-04-04
  • 在SpringBoot+MyBatis項(xiàng)目中實(shí)現(xiàn)MySQL讀寫(xiě)分離的實(shí)戰(zhàn)指南

    在SpringBoot+MyBatis項(xiàng)目中實(shí)現(xiàn)MySQL讀寫(xiě)分離的實(shí)戰(zhàn)指南

    在Spring Boot和MyBatis項(xiàng)目中實(shí)現(xiàn)MySQL讀寫(xiě)分離,主要有兩種思路:一種是在應(yīng)用層通過(guò)代碼和配置手動(dòng)控制,另一種是借助中間件自動(dòng)路由,本文介紹了幾種主流的實(shí)現(xiàn)方案,需要的朋友可以參考下
    2025-11-11
  • Java單例模式的五種實(shí)現(xiàn)方式

    Java單例模式的五種實(shí)現(xiàn)方式

    單例模式(Singleton Pattern)是Java中最簡(jiǎn)單的設(shè)計(jì)模式之一,這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式,下面這篇文章主要給大家介紹了關(guān)于Java單例模式的五種實(shí)現(xiàn)方式?,需要的朋友可以參考下
    2022-06-06
  • Java一行代碼搞定耗時(shí)性能追蹤

    Java一行代碼搞定耗時(shí)性能追蹤

    在開(kāi)發(fā)過(guò)程中,性能監(jiān)控和調(diào)試是我們經(jīng)常面對(duì)的問(wèn)題,雖然市面上有許多成熟的性能監(jiān)控工具,但有時(shí)我們需要一個(gè)輕量級(jí)、靈活且優(yōu)雅的解決方案,下面我們就來(lái)看看Java如何搞定耗時(shí)性能追蹤吧
    2024-11-11
  • SpringBoot實(shí)現(xiàn)接口防抖的實(shí)戰(zhàn)方案大全

    SpringBoot實(shí)現(xiàn)接口防抖的實(shí)戰(zhàn)方案大全

    這篇文章主要介紹了四種在SpringBoot中實(shí)現(xiàn)接口防抖的方案,包括前端防抖+后端令牌鎖、數(shù)據(jù)庫(kù)唯一約束、本地Guava緩存和Token令牌機(jī)制,每種方案都有其優(yōu)缺點(diǎn),選擇時(shí)應(yīng)根據(jù)業(yè)務(wù)重要性和系統(tǒng)架構(gòu)來(lái)決定,需要的朋友可以參考下
    2025-12-12
  • Maven的安裝+配置本地倉(cāng)庫(kù)路徑方式

    Maven的安裝+配置本地倉(cāng)庫(kù)路徑方式

    這篇文章主要介紹了Maven的安裝+配置本地倉(cāng)庫(kù)路徑方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • JAVA中的 map,list,set

    JAVA中的 map,list,set

    這篇文章主要介紹了JAVA中的 map、list、set這三個(gè)集合,下面文章圍繞JAVA中的三大集合 map、list、set詳情講解,需要的朋友可以參考一下
    2021-11-11
  • java程序中foreach用法示例

    java程序中foreach用法示例

    這篇文章主要介紹了java程序中foreach用法示例,需要的朋友可以參考下
    2014-04-04

最新評(píng)論

辉南县| 枣强县| 冀州市| 潮安县| 伽师县| 德昌县| 贡嘎县| 沅陵县| 荆门市| 凤冈县| 高台县| 宜宾市| 布尔津县| 五莲县| 盐源县| 乌拉特中旗| 广昌县| 马公市| 崇左市| 抚州市| 民乐县| 彰武县| 务川| 陇南市| 泗洪县| 岱山县| 肥西县| 淮南市| 镇巴县| 漳平市| 靖安县| 宁德市| 喀喇沁旗| 乌恰县| 湘潭县| 丁青县| 荣成市| 西乌珠穆沁旗| 庆云县| 桂阳县| 吉木乃县|