springboot 集成 Netty 進行消息發(fā)送和接收

概述

本文通過一個簡單的 demo 來介紹 Netty 在 spring boot 項目中的使用,其中包括了服務(wù)器端和客戶端的啟動代碼登失,客戶端向服務(wù)器端發(fā)送文本消息遏佣。

maven 依賴

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

關(guān)鍵點

服務(wù)器端在啟動的時候開放一個端口:19080
客戶端在啟動的時候通過 ip 和 端口連上服務(wù)器端
客戶端和服務(wù)器端都通過 Channel 對象向彼此發(fā)送數(shù)據(jù)
服務(wù)器和客戶端都通過繼承 ChannelInboundHandlerAdapter 類實現(xiàn)對消息的讀取和回寫等操作
服務(wù)器和客戶端都通過 StringDecoder 和 StringEncoder 實現(xiàn)對消息的解碼和轉(zhuǎn)碼操作
服務(wù)器和客戶端啟動的時候都會阻塞當前線程,因此需要在一個單獨的線程中進行啟動

消息發(fā)送的例子

本例是一個 spring boot web 項目揽浙,項目占用了 8080 端口
服務(wù)器端在啟動的時候開放 19080 端口(注意不要和 web 端口沖突了)
客戶端在啟動的時候連上服務(wù)器端
通過 web api 向客戶端發(fā)送數(shù)據(jù)状婶,客戶端再通過 Channel 對象向服務(wù)器端發(fā)送數(shù)據(jù)
服務(wù)器接收到客戶端數(shù)據(jù)后也通過 Channel 對象向客戶端發(fā)送數(shù)據(jù)

server 服務(wù)器端

通過 @PostConstruct 注解的方法進行啟動,具體如下

package me.yizhou.test.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.concurrent.ForkJoinPool;

@Slf4j
@Component
public class HelloWorldServer {

    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private static final EventLoopGroup workerGroup = new NioEventLoopGroup();

    @Resource
    private HelloWorldServerHandler helloWorldServerHandler;

    public void startServer(int port) {
        try {
            ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
//                            ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder());
                            ch.pipeline().addLast(helloWorldServerHandler);
                        }

                    }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            // 綁定端口馅巷,開始接收進來的連接
            sbs.bind(port).addListener(future -> {
                log.info(String.format("服務(wù)器啟動成功膛虫,并監(jiān)聽端口:%s ", port));
            });

        } catch (Exception e) {
            log.error("啟動 netty 服務(wù)器端出現(xiàn)異常", e);
        }
    }

    // 服務(wù)器端啟動,并綁定 19080 端口
    @PostConstruct
    public void init() {
        ForkJoinPool.commonPool().submit(() -> startServer(19080));
    }

    @PreDestroy
    public void destroy() {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

服務(wù)器端 HelloWorldServerHandler 如下

package me.yizhou.test.server;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@ChannelHandler.Sharable
@Slf4j
public class HelloWorldServerHandler extends ChannelInboundHandlerAdapter {

    // 服務(wù)器端讀取到 客戶端發(fā)送過來的數(shù)據(jù)钓猬,然后通過 Channel 回寫數(shù)據(jù)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info(String.format("服務(wù)器端讀取到從客戶端:%s 發(fā)送過來的數(shù)據(jù):%s", ctx.channel().remoteAddress(), msg.toString()));
        ctx.channel().writeAndFlush(String.format("server write:%s", msg));
    }

    // 捕獲到異常的處理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}

client 客戶端

通過 @PostConstruct 注解的方法進行啟動稍刀,具體如下

package me.yizhou.test.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.ForkJoinPool;

@Slf4j
@Component
public class HelloWorldClient {

    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    private final EventLoopGroup group = new NioEventLoopGroup();
    private ChannelFuture mChannelFuture = null;
    private final ThreadLocal<Channel> mChannel = new ThreadLocal<>();

    @Resource
    private HelloWorldClientHandler helloWorldClientHandler;

    public void startClient(String host, int port) {
        // Configure the client.
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast("decoder", new StringDecoder());
                            p.addLast("encoder", new StringEncoder());
                            p.addLast(helloWorldClientHandler);
                        }
                    });

            mChannelFuture = b.connect(host, port).addListener(future -> {
                log.info(String.format("客戶端啟動成功,并監(jiān)聽端口:%s ", port));
            });
        } catch (Exception e) {
            log.error("啟動 netty 客戶端出現(xiàn)異常", e);
        }
    }

    /**
     * 客戶端通過 Channel 對象向服務(wù)器端發(fā)送數(shù)據(jù)
     * @param data 文本數(shù)據(jù)
     */
    public void send(String data) {
        try {
            if (mChannel.get() == null) {
                mChannel.set(mChannelFuture.channel());
            }
            mChannel.get().writeAndFlush(data);
        } catch (Exception e) {
            log.error(this.getClass().getName().concat(".send has error"), e);
        }
    }

    // 客戶端啟動敞曹,并連上服務(wù)器端
    @PostConstruct
    public void init() {
        ForkJoinPool.commonPool().submit(() -> startClient("127.0.0.1", 19080));
    }

    @PreDestroy
    public void destroy() {
        group.shutdownGracefully();
    }

}

客戶端 HelloWorldClientHandler 實現(xiàn)如下

package me.yizhou.test.server;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@ChannelHandler.Sharable
@Slf4j
public class HelloWorldServerHandler extends ChannelInboundHandlerAdapter {

    // 服務(wù)器端讀取到 客戶端發(fā)送過來的數(shù)據(jù)账月,然后通過 Channel 回寫數(shù)據(jù)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info(String.format("服務(wù)器端讀取到從客戶端:%s 發(fā)送過來的數(shù)據(jù):%s", ctx.channel().remoteAddress(), msg.toString()));
        ctx.channel().writeAndFlush(String.format("server write:%s", msg));
    }

    // 捕獲到異常的處理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}

web api 數(shù)據(jù)發(fā)送入口

這里只是通過

package com.ckjava.test.web;

import com.ckjava.test.client.HelloWorldClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author yizhou
 * @date 2022/6/16 20:50
 */
@RequestMapping(produces = "application/json;charset=utf-8")
@RestController
public class HelloNettyController {

    @Resource
    private HelloWorldClient mHelloWorldClient;

    @GetMapping("/nettyClient")
    public void nettyClient(@RequestParam String data) throws Exception {
        mHelloWorldClient.send(data);
    }
}

啟動類

package me.yizhou.test;

import me.yizhou.test.server.HelloWorldServer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.Resource;

/**
 * @author yizhou
 * @date 2022/6/16 20:40
 */
@SpringBootApplication
public class TestNettyApplication implements CommandLineRunner {
    @Resource
    private HelloWorldServer discardServer;//echoServer

    public static void main(String[] args) {
        SpringApplication.run(TestNettyApplication.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        discardServer.startServer(8080);
    }
}

測試

執(zhí)行如下請求
GET localhost:8087/nettyClient?data=%E4%BD%A0%E5%A5%BD%20yizhou
Content-Type: application/json;charset=utf-8


1655371977(1).png

輸出如下


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市异雁,隨后出現(xiàn)的幾起案子捶障,更是在濱河造成了極大的恐慌,老刑警劉巖纲刀,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项炼,死亡現(xiàn)場離奇詭異担平,居然都是意外死亡,警方通過查閱死者的電腦和手機锭部,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門暂论,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拌禾,你說我怎么就攤上這事取胎。” “怎么了湃窍?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵闻蛀,是天一觀的道長。 經(jīng)常有香客問我您市,道長觉痛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任茵休,我火速辦了婚禮薪棒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榕莺。我一直安慰自己俐芯,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布钉鸯。 她就那樣靜靜地躺著吧史,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唠雕。 梳的紋絲不亂的頭發(fā)上扣蜻,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音及塘,去河邊找鬼莽使。 笑死,一個胖子當著我的面吹牛笙僚,可吹牛的內(nèi)容都是我干的芳肌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼肋层,長吁一口氣:“原來是場噩夢啊……” “哼亿笤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栋猖,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤净薛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒲拉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肃拜,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡痴腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了燃领。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片士聪。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猛蔽,靈堂內(nèi)的尸體忽然破棺而出剥悟,到底是詐尸還是另有隱情,我是刑警寧澤曼库,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布区岗,位于F島的核電站,受9級特大地震影響躏尉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜后众,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颅拦。 院中可真熱鬧蒂誉,春花似錦、人聲如沸距帅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碌秸。三九已至绍移,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讥电,已是汗流浹背蹂窖。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恩敌,地道東北人瞬测。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像纠炮,于是被迫代替她去往敵國和親月趟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 減少 HTTP 請求數(shù)量和提高網(wǎng)站加載速度的 8 種方法 1.刪除不必要的圖像 目標是最大限度地減少網(wǎng)頁必須加載的...
    Magento閱讀 109評論 0 0
  • 找開發(fā)公司做小程序你需要提前知道的那些事 這個話題前期我也有分享過幾次恢口,但是今天我跟大家再系統(tǒng)的講一講孝宗。你找到開發(fā)...
    酷酷的黑哥閱讀 77評論 0 0
  • 做這個的初心是希望能鞏固自己的基礎(chǔ)知識,也通過這種方式檢查自己知識的缺失點耕肩。目前還不完善因妇,后續(xù)會根據(jù)時間不斷更正和...
    lp_lp閱讀 35,315評論 7 147
  • 作者: 極客小俊一個把邏輯思維轉(zhuǎn)變?yōu)榇a的技術(shù)博主 前言 居然有人干了5年開發(fā)问潭,抓包都不會!?? 但是不要怕,不要哭...
    極客小俊閱讀 1,544評論 2 8
  • # day11【網(wǎng)絡(luò)編程】 ## 主要內(nèi)容 * 軟件架構(gòu)CS/BS * 網(wǎng)絡(luò)通信三要素 * TCP通信 * Soc...
    Andy_580b閱讀 146評論 0 0