如何在SpringBoot中寥茫,使用Netty實現(xiàn)遠(yuǎn)程調(diào)用遣蚀?

前言

眾所周知我們在進(jìn)行網(wǎng)絡(luò)連接的時候,建立套接字連接是一個非常消耗性能的事情纱耻,特別是在分布式的情況下芭梯,用線程池去保持多個客戶端連接,是一種非常消耗線程的行為弄喘。那么我們該通過什么技術(shù)去解決上述的問題呢玖喘,那么就不得不提一個網(wǎng)絡(luò)連接的利器——Netty.

Netty

Netty是一個NIO客戶端服務(wù)器框架:

  • 它可快速輕松地開發(fā)網(wǎng)絡(luò)應(yīng)用程序,例如協(xié)議服務(wù)器和客戶端限次。

  • 它極大地簡化和簡化了網(wǎng)絡(luò)編程芒涡,例如TCP和UDP套接字服務(wù)器。

NIO是一種非阻塞IO 卖漫,它具有以下的特點

  • 單線程可以連接多個客戶端费尽。

  • 選擇器可以實現(xiàn)單線程管理多個Channel,新建的通道都要向選擇器注冊羊始。

  • 一個SelectionKey鍵表示了一個特定的通道對象和一個特定的選擇器對象之間的注冊關(guān)系旱幼。

  • selector進(jìn)行select()操作可能會產(chǎn)生阻塞,但是可以設(shè)置阻塞時間突委,并且可以用wakeup()喚醒selector柏卤,所以NIO是非阻塞IO。

Netty模型selector模式

它相對普通NIO的在性能上有了提升匀油,采用了:

  • NIO采用多線程的方式可以同時使用多個selector

  • 通過綁定多個端口的方式缘缚,使得一個selector可以同時注冊多個ServerSocketServer

  • 單個線程下只能有一個selector,用來實現(xiàn)Channel的匹配及復(fù)用

image

半包問題

TCP/IP在發(fā)送消息的時候敌蚜,可能會拆包桥滨,這就導(dǎo)致接收端無法知道什么時候收到的數(shù)據(jù)是一個完整的數(shù)據(jù)。在傳統(tǒng)的BIO中在讀取不到數(shù)據(jù)時會發(fā)生阻塞弛车,但是NIO不會齐媒。為了解決NIO的半包問題,Netty在Selector模型的基礎(chǔ)上纷跛,提出了reactor模式喻括,從而解決客戶端請求在服務(wù)端不完整的問題。

netty模型reactor模式

  • 在selector的基礎(chǔ)上解決了半包問題贫奠。
image

上圖唬血,簡單地可以描述為"boss接活望蜡,讓work干":manReactor用來接收請求(會與客戶端進(jìn)行握手驗證),而subReactor用來處理請求(不與客戶端直接連接)刁品。

SpringBoot使用Netty實現(xiàn)遠(yuǎn)程調(diào)用

maven依賴


<!--lombok-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.2</version>
  <optional>true</optional>
</dependency>

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

服務(wù)端部分

NettyServer.java:服務(wù)啟動監(jiān)聽器


@Slf4j
public class NettyServer {
    public void start() {
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8082);
        //new 一個主線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //new 一個工作線程組
        EventLoopGroup workGroup = new NioEventLoopGroup(200);
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ServerChannelInitializer())
                .localAddress(socketAddress)
                //設(shè)置隊列大小
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 兩小時內(nèi)沒有數(shù)據(jù)的通信時,TCP會自動發(fā)送一個活動探測數(shù)據(jù)報文
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //綁定端口,開始接收進(jìn)來的連接
        try {
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            log.info("服務(wù)器啟動開始監(jiān)聽端口: {}", socketAddress.getPort());
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("服務(wù)器開啟失敗", e);
        } finally {
            //關(guān)閉主線程組
            bossGroup.shutdownGracefully();
            //關(guān)閉工作線程組
            workGroup.shutdownGracefully();
        }
    }
}

ServerChannelInitializer.java:netty服務(wù)初始化器


/**
* netty服務(wù)初始化器
**/
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //添加編解碼
        socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast(new NettyServerHandler());
    }
}

NettyServerHandler.java:netty服務(wù)端處理器


/**
* netty服務(wù)端處理器
**/
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 客戶端連接會觸發(fā)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("Channel active......");
    }

    /**
     * 客戶端發(fā)消息會觸發(fā)
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("服務(wù)器收到消息: {}", msg.toString());
        ctx.write("你也好哦");
        ctx.flush();
    }

    /**
     * 發(fā)生異常觸發(fā)
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

RpcServerApp.java:SpringBoot啟動類


/**
* 啟動類
*
*/
@Slf4j
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RpcServerApp extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(RpcServerApp.class);
    }

    /**
     * 項目的啟動方法
     *
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(RpcServerApp.class, args);
        //開啟Netty服務(wù)
        NettyServer nettyServer =new  NettyServer ();
        nettyServer.start();
        log.info("======服務(wù)已經(jīng)啟動========");
    }
}

客戶端部分

NettyClientUtil.java:NettyClient工具類


/**
* Netty客戶端
**/
@Slf4j
public class NettyClientUtil {

    public static ResponseResult helloNetty(String msg) {
        NettyClientHandler nettyClientHandler = new NettyClientHandler();
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap()
                .group(group)
                //該參數(shù)的作用就是禁止使用Nagle算法泣特,使用于小數(shù)據(jù)即時傳輸
                .option(ChannelOption.TCP_NODELAY, true)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast("decoder", new StringDecoder());
                        socketChannel.pipeline().addLast("encoder", new StringEncoder());
                        socketChannel.pipeline().addLast(nettyClientHandler);
                    }
                });
        try {
            ChannelFuture future = bootstrap.connect("127.0.0.1", 8082).sync();
            log.info("客戶端發(fā)送成功....");
            //發(fā)送消息
            future.channel().writeAndFlush(msg);
            // 等待連接被關(guān)閉
            future.channel().closeFuture().sync();
            return nettyClientHandler.getResponseResult();
        } catch (Exception e) {
            log.error("客戶端Netty失敗", e);
            throw new BusinessException(CouponTypeEnum.OPERATE_ERROR);
        } finally {
            //以一種優(yōu)雅的方式進(jìn)行線程退出
            group.shutdownGracefully();
        }
    }
}

NettyClientHandler.java:客戶端處理器


/**
* 客戶端處理器
**/
@Slf4j
@Setter
@Getter
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    private ResponseResult responseResult;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("客戶端Active .....");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("客戶端收到消息: {}", msg.toString());
        this.responseResult = ResponseResult.success(msg.toString(), CouponTypeEnum.OPERATE_SUCCESS.getCouponTypeDesc());
        ctx.close();
    }

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

驗證

測試接口


@RestController
@Slf4j
public class UserController {

    @PostMapping("/helloNetty")
    @MethodLogPrint
    public ResponseResult helloNetty(@RequestParam String msg) {
        return NettyClientUtil.helloNetty(msg);
    }
}

訪問測試接口

image

服務(wù)端打印信息

image

客戶端打印信息

image

作者:溪源的奇思妙想
原文鏈接:https://blog.csdn.net/weixin_40990818/article/details/109248198

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浩姥,一起剝皮案震驚了整個濱河市挑随,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勒叠,老刑警劉巖兜挨,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異眯分,居然都是意外死亡拌汇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門弊决,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噪舀,“玉大人,你說我怎么就攤上這事飘诗∮氤” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵昆稿,是天一觀的道長纺座。 經(jīng)常有香客問我,道長溉潭,這世上最難降的妖魔是什么净响? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮喳瓣,結(jié)果婚禮上馋贤,老公的妹妹穿的比我還像新娘。我一直安慰自己畏陕,他們只是感情好配乓,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹭秋,像睡著了一般扰付。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仁讨,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天羽莺,我揣著相機(jī)與錄音,去河邊找鬼洞豁。 笑死盐固,一個胖子當(dāng)著我的面吹牛荒给,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刁卜,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼志电,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛔趴?” 一聲冷哼從身側(cè)響起挑辆,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孝情,沒想到半個月后鱼蝉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡箫荡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年魁亦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羔挡。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡洁奈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绞灼,到底是詐尸還是另有隱情利术,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布镀赌,位于F島的核電站氯哮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏商佛。R本人自食惡果不足惜喉钢,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望良姆。 院中可真熱鬧肠虽,春花似錦、人聲如沸玛追。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痊剖。三九已至韩玩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陆馁,已是汗流浹背找颓。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留叮贩,地道東北人击狮。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓佛析,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彪蓬。 傳聞我的和親對象是個殘疾皇子寸莫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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