摘要:提供異步的事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。總結(jié)我們完成了服務(wù)端的簡單搭建,模擬了聊天會話場景。
之前一直在搞前端的東西,都快忘了自己是個(gè)java開發(fā)。其實(shí)還有好多java方面的東西沒搞過,突然了解到netty,覺得有必要學(xué)一學(xué)。介紹
Netty是由JBOSS提供的一個(gè)java開源框架。Netty提供異步的、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。
也就是說,Netty 是一個(gè)基于NIO的客戶、服務(wù)器端編程框架,使用Netty 可以確保你快速和簡單的開發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶、服務(wù)端應(yīng)用。Netty相當(dāng)于簡化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過程,例如:基于TCP和UDP的socket服務(wù)開發(fā)。
一些IO概念NIO (non-blocking IO) 非阻塞
BIO (blocking IO) 阻塞
以上兩種又可分為同步和異步,即同步阻塞,同步非阻塞,異步阻塞,異步非阻塞。
阻塞:數(shù)據(jù)沒來,啥都不做,直到數(shù)據(jù)來了,才進(jìn)行下一步的處理。
非阻塞:數(shù)據(jù)沒來,進(jìn)程就不停的去檢測數(shù)據(jù),直到數(shù)據(jù)來。
至于這塊的詳細(xì)概念,大家可以自行百度學(xué)習(xí)。總之,netty處理io很高效,不需要你擔(dān)心。
netty結(jié)構(gòu)可以看出它支持的網(wǎng)絡(luò)傳輸協(xié)議,以及容器支持,安全支持,io.
工作流程:
所有客戶端的連接交給住主線程去管理,響應(yīng)客戶端的消息交給從線程去處理,整個(gè)線程池由netty負(fù)責(zé)。
創(chuàng)建maven工程引入最新的依賴
4.0.0 com.mike netty 0.0.1-SNAPSHOT UTF-8 1.8 io.netty netty-all 4.1.32.Final
創(chuàng)建消息處理器
package netty; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.Http2Headers; import io.netty.util.concurrent.GlobalEventExecutor; /** * */ public class ChatHandler extends SimpleChannelInboundHandler{ public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /** * 每當(dāng)從服務(wù)端收到新的客戶端連接時(shí),客戶端的 Channel 存入ChannelGroup列表中,并通知列表中的其他客戶端 Channel */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入 "); } channels.add(ctx.channel()); } /** * 每當(dāng)從服務(wù)端收到客戶端斷開時(shí),客戶端的 Channel 移除 ChannelGroup 列表中,并通知列表中的其他客戶端 Channel */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開 "); } channels.remove(ctx.channel()); } /** * 會話建立時(shí) */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"在線"); } /** * 會話結(jié)束時(shí) */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"掉線"); } /** * 出現(xiàn)異常 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"異常"); // 當(dāng)出現(xiàn)異常就關(guān)閉連接 cause.printStackTrace(); ctx.close(); } /** * 讀取客戶端發(fā)送的消息,并將信息轉(zhuǎn)發(fā)給其他客戶端的 Channel。 */ @Override protected void channelRead0(ChannelHandlerContext ctx, Object request) throws Exception { FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1,HttpResponseStatus.OK , Unpooled.wrappedBuffer("Hello netty" .getBytes())); response.headers().set("Content-Type", "text/plain"); response.headers().set("Content-Length", response.content().readableBytes()); response.headers().set("connection", HttpHeaderValues.KEEP_ALIVE); ctx.writeAndFlush(response); } }
這里面其實(shí)只需要重寫channelRead0 方法就可以了,其他是它的生命周期的方法,可以用來做日至記錄。我們在讀取消息后,往channel里寫入了一個(gè)http的response。
初始化我們的消息處理器
package netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; /** * 用來增加多個(gè)的處理類到 ChannelPipeline 上,包括編碼、解碼、SimpleChatServerHandler 等。 */ public class ChatServerInitializer extends ChannelInitializer{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("HttpResponseEncoder",new HttpResponseEncoder()); pipeline.addLast("HttpRequestDecoder",new HttpRequestDecoder()); pipeline.addLast("chathandler", new ChatHandler()); System.out.println("ChatClient:"+ch.remoteAddress() +"連接上"); } }
這個(gè)pipeline可以理解為netty的攔截器,每個(gè)消息進(jìn)來,經(jīng)過各個(gè)攔截器的處理。我們需要響應(yīng)http消息,所以加入了響應(yīng)編碼以及請求解碼,最后加上了我們的自定義處理器。這里面有很多處理器,netty以及幫你定義好的。
服務(wù)啟動類
package netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * The class ChatServer */ public class ChatServer { private int port; public ChatServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitializer()) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); System.out.println("ChatServer 啟動了"); // 綁定端口,開始接收進(jìn)來的連接 ChannelFuture f = b.bind(port).sync(); // (7) // 等待服務(wù)器 socket 關(guān)閉 。 // 在這個(gè)例子中,這不會發(fā)生,但你可以優(yōu)雅地關(guān)閉你的服務(wù)器。 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); System.out.println("ChatServer 關(guān)閉了"); } } public static void main(String[] args) throws Exception { new ChatServer(8090).run(); } }
這個(gè)啟動類就是按照上面那個(gè)結(jié)構(gòu)圖來的,添加兩個(gè)線程組,設(shè)置channel,添加消息處理器,配置一些選項(xiàng)option。
測試
啟動程序,瀏覽器訪問 http://localhost:8090
可以在瀏覽器看到我們返回的消息,但是控制臺卻顯示連接了多個(gè)客戶端,其實(shí)是因?yàn)闉g覽器發(fā)送了無關(guān)的請求道服務(wù)端,由于我們沒有做路由,所以所有請求都是200。
可以看到,發(fā)送了兩次請求。現(xiàn)在我們換postman測試。
這次只有一個(gè)客戶端連接,當(dāng)我們關(guān)閉postman:
客戶端顯示掉線,整個(gè)會話過程結(jié)束。
我們完成了服務(wù)端的簡單搭建,模擬了聊天會話場景。后面再接著完善。
別忘了關(guān)注我 mike啥都想搞
還有其他后端技術(shù)分享在我的公眾號。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73106.html
摘要:開始聊天發(fā)送聊天信息時(shí)消息,這樣后端就知道是誰要發(fā)給誰,根據(jù)用戶名去找到具體的線程去單獨(dú)推送消息,實(shí)現(xiàn)單聊。前端待完善左側(cè)聊天列表沒有實(shí)現(xiàn),每搜索一個(gè)在線用戶,應(yīng)該動態(tài)顯示在左側(cè),點(diǎn)擊該用戶,動態(tài)顯示右側(cè)聊天窗口進(jìn)行消息發(fā)送。 上節(jié)課講了群聊,這次來說說單聊,單聊要比群聊復(fù)雜點(diǎn),但是代碼也不是很多,主要是前端顯示比較麻煩點(diǎn)。 效果:showImg(https://segmentfaul...
摘要:上節(jié)課完成了的后端搭建,搞定了簡單的請求響應(yīng),今天來結(jié)合前端來完成群聊功能。其實(shí)后端群聊很簡單,就是把一個(gè)用戶的輸入消息,返回給所有在線客戶端,前端去負(fù)責(zé)篩選顯示。 上節(jié)課完成了netty的后端搭建,搞定了簡單的http請求響應(yīng),今天來結(jié)合前端websocket來完成群聊功能。話不多說先上圖:showImg(https://segmentfault.com/img/bVbnCa8?w=...
摘要:前言熬了一晚上硬是磨出來了,更新到了上,善存一些小,不過這個(gè)版本的整體功能算是實(shí)現(xiàn)了。預(yù)留其余的就是可能善存的一些了圖片過大,需要在前端做圖片上傳壓縮前端代碼的一點(diǎn)問題,不影響項(xiàng)目正常運(yùn)行遠(yuǎn)程主機(jī)強(qiáng)迫關(guān)閉了一個(gè)現(xiàn)有的連接。 前言 熬了一晚上硬是磨出來了,更新到了GitHub上,善存一些小BUG,不過這個(gè)版本的整體功能算是實(shí)現(xiàn)了。 項(xiàng)目:UncleCatMySelf/InChat 地址:...
摘要:前言大家可以看看上一篇用構(gòu)建一個(gè)簡單的聊天室在上一篇文章中我們已經(jīng)實(shí)現(xiàn)了自我對話好友交流群聊離線消息等的功能。系統(tǒng)通知恭喜您連續(xù)登錄超過天,獎勵(lì)積分。 本文首發(fā)公眾號與個(gè)人博客:Java貓說 & 貓叔的博客 | MySelf,轉(zhuǎn)載請申明出處。 前言 大家可以看看上一篇:用Java構(gòu)建一個(gè)簡單的WebSocket聊天室 在上一篇文章中我們已經(jīng)實(shí)現(xiàn)了:自我對話、好友交流、群聊、離線消息等...
閱讀 3355·2021-11-25 09:43
閱讀 3149·2021-10-11 10:58
閱讀 2751·2021-09-27 13:59
閱讀 3084·2021-09-24 09:55
閱讀 2175·2019-08-30 15:52
閱讀 1837·2019-08-30 14:03
閱讀 2264·2019-08-30 11:11
閱讀 2029·2019-08-28 18:12