摘要:線程切換效率低下單機(jī)核數(shù)固定,線程爆炸之后操作系統(tǒng)頻繁進(jìn)行線程切換,應(yīng)用性能急劇下降。線程切換效率低下由于模型中線程數(shù)量大大降低,線程切換效率因此也大幅度提高。將兩個(gè)線程優(yōu)雅地關(guān)閉。創(chuàng)建管道的子處理器,用于處理。
Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目介紹:https://segmentfault.com/a/11...
Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目github鏈接:https://github.com/ShimmerPig...
本章練習(xí)完整代碼鏈接:https://github.com/ShimmerPig...
Netty學(xué)習(xí) IO編程與NIO編程 傳統(tǒng)IO編程性能分析IO編程模型在客戶端較少的情況下運(yùn)行良好,但是對(duì)于客戶端比較多的業(yè)務(wù)來說,單機(jī)服務(wù)端可能需要支撐成千上萬的連接,IO模型可能就不太合適了。這是因?yàn)樵趥鹘y(tǒng)的IO模型中,每個(gè)連接創(chuàng)建成功之后都需要一個(gè)線程來維護(hù),每個(gè)線程包含一個(gè)while死循環(huán),那么1w個(gè)連接對(duì)應(yīng)1w個(gè)線程,繼而1w個(gè)while死循環(huán),這就帶來如下幾個(gè)問題:
1.線程資源受限:線程是操作系統(tǒng)中非常寶貴的資源,同一時(shí)刻有大量的線程處于阻塞狀態(tài)是非常嚴(yán)重的資源浪費(fèi),操作系統(tǒng)耗不起。
2.線程切換效率低下:單機(jī)cpu核數(shù)固定,線程爆炸之后操作系統(tǒng)頻繁進(jìn)行線程切換,應(yīng)用性能急劇下降。
3.除了以上兩個(gè)問題,IO編程中,我們看到數(shù)據(jù)讀寫是以字節(jié)流為單位,效率不高。
為了解決這三個(gè)問題,JDK在1.4之后提出了NIO。下面簡單描述一下NIO是如何解決以上三個(gè)問題的。
線程資源受限NIO編程模型中,新來一個(gè)連接不再創(chuàng)建一個(gè)新的線程,而是可以把這條連接直接綁定到某個(gè)固定的線程,然后這條連接所有的讀寫都由這個(gè)線程來負(fù)責(zé)。
這個(gè)過程的實(shí)現(xiàn)歸功于NIO模型中selector的作用,一條連接來了之后,現(xiàn)在不創(chuàng)建一個(gè)while死循環(huán)去監(jiān)聽是否有數(shù)據(jù)可讀了,而是直接把這條連接注冊(cè)到selector上,然后,通過檢查這個(gè)selector,就可以批量監(jiān)測(cè)出有數(shù)據(jù)可讀的連接,進(jìn)而讀取數(shù)據(jù)。
由于NIO模型中線程數(shù)量大大降低,線程切換效率因此也大幅度提高。
IO讀寫以字節(jié)為單位NIO解決這個(gè)問題的方式是數(shù)據(jù)讀寫不再以字節(jié)為單位,而是以字節(jié)塊為單位。IO模型中,每次都是從操作系統(tǒng)底層一個(gè)字節(jié)一個(gè)字節(jié)地讀取數(shù)據(jù),而NIO維護(hù)一個(gè)緩沖區(qū),每次可以從這個(gè)緩沖區(qū)里面讀取一塊的數(shù)據(jù)。
完整代碼鏈接:https://github.com/ShimmerPig...
首先定義一對(duì)線程組——主線程bossGroup與從線程workerGroup。
bossGroup——用于接受客戶端的連接,但是不做任何處理,跟老板一樣,不做事。
workerGroup——bossGroup會(huì)將任務(wù)丟給他,讓workerGroup去處理。
//主線程 EventLoopGroup bossGroup = new NioEventLoopGroup(); //從線程 EventLoopGroup workerGroup = new NioEventLoopGroup();
定義服務(wù)端的啟動(dòng)類serverBootstrap,需要設(shè)置主從線程,NIO的雙向通道,與子處理器(用于處理workerGroup),這里的子處理器后面我們會(huì)手動(dòng)創(chuàng)建。
// netty服務(wù)器的創(chuàng)建, ServerBootstrap 是一個(gè)啟動(dòng)類 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) // 設(shè)置主從線程組 .channel(NioServerSocketChannel.class) // 設(shè)置nio的雙向通道 .childHandler(new HelloServerInitializer()); // 子處理器,用于處理workerGroup
啟動(dòng)服務(wù)端,綁定8088端口,同時(shí)設(shè)置啟動(dòng)的方式為同步的,這樣我們的Netty就會(huì)一直等待,直到該端口啟動(dòng)完畢。
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
監(jiān)聽關(guān)閉的通道channel,設(shè)置為同步方式。
channelFuture.channel().closeFuture().sync();
將兩個(gè)線程優(yōu)雅地關(guān)閉。
bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();
創(chuàng)建管道channel的子處理器HelloServerInitializer,用于處理workerGroup。
HelloServerInitializer里面只重寫了initChannel方法,是一個(gè)初始化器,channel注冊(cè)后,會(huì)執(zhí)行里面相應(yīng)的初始化方法。
在initChannel方法中通過SocketChannel獲得對(duì)應(yīng)的管道,通過該管道添加相關(guān)助手類handler。
HttpServerCodec是由netty自己提供的助手類,可以理解為攔截器,當(dāng)請(qǐng)求到服務(wù)端,我們需要做解碼,響應(yīng)到客戶端做編碼。
添加自定義的助手類customHandler,返回"hello netty~"
ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("HttpServerCodec", new HttpServerCodec()); pipeline.addLast("customHandler", new CustomHandler());
創(chuàng)建自定義的助手類CustomHandler繼承SimpleChannelInboundHandler,返回hello netty~
重寫channelRead0方法,首先通過傳入的上下文對(duì)象ChannelHandlerContext獲取channel,若消息類型為http請(qǐng)求,則構(gòu)建一個(gè)內(nèi)容為"hello netty~"的http響應(yīng),通過上下文對(duì)象的writeAndFlush方法將響應(yīng)刷到客戶端。
if (msg instanceof HttpRequest) { // 顯示客戶端的遠(yuǎn)程地址 System.out.println(channel.remoteAddress()); // 定義發(fā)送的數(shù)據(jù)消息 ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8); // 構(gòu)建一個(gè)http response FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); // 為響應(yīng)增加數(shù)據(jù)類型和長度 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); // 把響應(yīng)刷到客戶端 ctx.writeAndFlush(response); }
訪問8088端口,返回"hello netty~"
完整代碼鏈接:https://github.com/ShimmerPig...
服務(wù)器定義主從線程與服務(wù)端的啟動(dòng)類
public class WSServer { public static void main(String[] args) throws Exception { EventLoopGroup mainGroup = new NioEventLoopGroup(); EventLoopGroup subGroup = new NioEventLoopGroup(); try { ServerBootstrap server = new ServerBootstrap(); server.group(mainGroup, subGroup) .channel(NioServerSocketChannel.class) .childHandler(new WSServerInitialzer()); ChannelFuture future = server.bind(8088).sync(); future.channel().closeFuture().sync(); } finally { mainGroup.shutdownGracefully(); subGroup.shutdownGracefully(); } } }
創(chuàng)建channel的子處理器WSServerInitialzer
加入相關(guān)的助手類handler
public class WSServerInitialzer extends ChannelInitializer{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // websocket 基于http協(xié)議,所以要有http編解碼器 pipeline.addLast(new HttpServerCodec()); // 對(duì)寫大數(shù)據(jù)流的支持 pipeline.addLast(new ChunkedWriteHandler()); // 對(duì)httpMessage進(jìn)行聚合,聚合成FullHttpRequest或FullHttpResponse // 幾乎在netty中的編程,都會(huì)使用到此hanler pipeline.addLast(new HttpObjectAggregator(1024*64)); // ====================== 以上是用于支持http協(xié)議 ====================== // ====================== 以下是支持httpWebsocket ====================== /** * websocket 服務(wù)器處理的協(xié)議,用于指定給客戶端連接訪問的路由 : /ws * 本handler會(huì)幫你處理一些繁重的復(fù)雜的事 * 會(huì)幫你處理握手動(dòng)作: handshaking(close, ping, pong) ping + pong = 心跳 * 對(duì)于websocket來講,都是以frames進(jìn)行傳輸?shù)模煌臄?shù)據(jù)類型對(duì)應(yīng)的frames也不同 */ pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); // 自定義的handler pipeline.addLast(new ChatHandler()); } }
創(chuàng)建自定義的助手類ChatHandler,用于處理消息。
TextWebSocketFrame:在netty中,是用于為websocket專門處理文本的對(duì)象,frame是消息的載體。
創(chuàng)建管道組ChannelGroup,用于管理所有客戶端的管道channel。
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
重寫channelRead0方法,通過傳入的TextWebSocketFrame獲取客戶端傳入的內(nèi)容。通過循環(huán)的方法對(duì)ChannelGroup中所有的channel進(jìn)行回復(fù)。
@Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { // 獲取客戶端傳輸過來的消息 String content = msg.text(); System.out.println("接受到的數(shù)據(jù):" + content); // for (Channel channel: clients) { // channel.writeAndFlush( // new TextWebSocketFrame( // "[服務(wù)器在]" + LocalDateTime.now() // + "接受到消息, 消息為:" + content)); // } // 下面這個(gè)方法,和上面的for循環(huán),一致 clients.writeAndFlush( new TextWebSocketFrame( "[服務(wù)器在]" + LocalDateTime.now() + "接受到消息, 消息為:" + content)); }
重寫handlerAdded方法,當(dāng)客戶端連接服務(wù)端之后(打開連接),獲取客戶端的channle,并且放到ChannelGroup中去進(jìn)行管理。
@Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { clients.add(ctx.channel()); }
重寫handlerRemoved方法,當(dāng)觸發(fā)handlerRemoved,ChannelGroup會(huì)自動(dòng)移除對(duì)應(yīng)客戶端的channel。
@Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // 當(dāng)觸發(fā)handlerRemoved,ChannelGroup會(huì)自動(dòng)移除對(duì)應(yīng)客戶端的channel // clients.remove(ctx.channel()); System.out.println("客戶端斷開,channle對(duì)應(yīng)的長id為:" + ctx.channel().id().asLongText()); System.out.println("客戶端斷開,channle對(duì)應(yīng)的短id為:" + ctx.channel().id().asShortText()); }客戶端
發(fā)送消息:接受消息:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73252.html
Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目介紹。Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目github鏈接。本章完整代碼鏈接。 本章內(nèi)容 (1) 查詢好友列表的接口 (2)通過或忽略好友請(qǐng)求的接口 (3)添加好友功能展示 查詢好友列表的接口 /** * @Description: 查詢我的好友列表 ...
摘要:實(shí)現(xiàn)聊天,項(xiàng)目介紹。首先根據(jù)搜索的用戶的名稱查找是否存在這個(gè)用戶。如果搜索前置條件為成功,則向前端返回搜索用戶的信息。發(fā)送添加好友的請(qǐng)求判斷不能為空查詢用戶接受到的朋友申請(qǐng)最終實(shí)現(xiàn)效果 Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目介紹。Netty+SpringBoot+FastDFS+Html5實(shí)現(xiàn)聊天App,項(xiàng)目github鏈接。本章完整代碼鏈接。...
閱讀 3400·2021-11-24 10:30
閱讀 3269·2021-11-22 15:29
閱讀 3706·2021-10-28 09:32
閱讀 1255·2021-09-07 10:22
閱讀 3336·2019-08-30 15:55
閱讀 3619·2019-08-30 15:54
閱讀 3494·2019-08-30 15:54
閱讀 2833·2019-08-30 15:44