Netty从零开始(一)
現在我們使用通用應用程序或包進行通信。例如,我們經常使用HTTP客戶端庫從Web服務器檢索信息,并通過Web服務調用遠程過程調用。然而,通用協議或其實現有時不能很好地擴展。這就像我們不使用通用HTTP服務器來交換大量文件,電子郵件和近實時消息,如財務信息和多人游戲數據。需要的是高度優化的協議實現,專門用于特殊目的。例如,您可能希望實現針對基于AJAX的聊天應用程序,媒體流或大型文件傳輸進行了優化的HTTP服務器。你甚至可以設計和實施一個全新的協議,這個協議是根據你的需要而定制的。另一個不可避免的情況是當您必須處理舊版專有協議以確保與舊系統的互操作性。在這種情況下重要的是我們能夠快速實現該協議,而不會犧牲最終應用程序的穩定性和性能。
方案
Netty項目是為了快速開發可維護的高性能高可擴展性協議服務器和客戶端而努力提供異步事件驅動的網絡應用程序框架和工具。換句話說,Netty是一個NIO客戶端服務器框架,可以快速輕松地開發諸如協議服務器和客戶端之類的網絡應用程序。它大大簡化了網絡編程流程,如TCP和UDP套接字服務器開發。
?
“快速和容易”并不意味著由此產生的應用程序將遭受可維護性或性能問題的困擾。Netty經過精心設計,實現了許多協議,如FTP,SMTP,HTTP以及各種基于二進制和基于文本的傳統協議。因此,Netty成功地找到了一種方法來實現輕松的開發,性能,穩定性和靈活性,而無需妥協。
?
有些用戶可能已經找到了聲稱具有相同優勢的其他網絡應用程序框架,您可能想問問Netty與他們的區別。答案是它建立的哲學。Netty旨在為您提供API和執行方面最舒適的體驗,從第一天開始。這不是有形的東西,但你會意識到,這個哲學將使你的生活更容易,當你閱讀本指南和玩Netty的時候。
?
好了,以上就是關于netty的一個官網的初步介紹吧。下面進入搭建最簡單的服務器的環節,我這里會按照官網的思路走,不過不會完全一點不差。好了,我們開始:
建立項目
首先我們需要建立項目,如下圖所示:
項目名稱是NettyDemo,官網建議使用JDK1.6以上,我這里使用的JDK1.8,然后加入使用maven導入Netty依賴:
<dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.6.Final</version></dependency> </dependencies>那么現在我們可以正式開始我們的項目編寫了。
?
編寫一個Discard服務器(按我理解就是啥也不干的服務器,別著急反駁,往下看)
世界上最簡單的協議不是“hello world”,而是。。。。什么也不做的協議Discard,丟棄的意思,服務端丟棄,那就是啥也不做的協議唄(嘗試把協議理解為用戶自定義功能)。
想要實現一個Discard協議,那么我們唯一需要做的就是忽略所有接收到的數據。讓我們從處理器實現開始,它處理由netty生成的I/O事件。
首先我們創建一個java包:netty_beginner,然后在里面創建一個類DiscardServerHandler
類的內容如下:
package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;/*** Created by moon on 2017/4/5.*/ public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // (2) // super.channelRead(ctx, msg);((ByteBuf) msg).release(); // (3) // ByteBuf in = (ByteBuf) msg; // try { // while (in.isReadable()) { // System.out.print((char) in.readByte()); // System.out.flush(); // } // } finally { // ReferenceCountUtil.release(msg); // }}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // (5) // super.exceptionCaught(ctx, cause);cause.printStackTrace();ctx.close();} }?
到目前位置一切順利。我們已經實現了DISCARD服務器的前半部分。現在剩下的是寫入使用DiscardServerHandler啟動服務器的main()方法。我們創建另外一個類:DiscardServer如下:
package netty_beginner;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; 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;/*** Created by moon on 2017/4/5.*/ public class DiscardServer {private int port;public DiscardServer(int port) {this.port = port;}public void run() throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap(); // (2)b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3).childHandler(new ChannelInitializer<SocketChannel>() { // (4)@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new DiscardServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128) // (5).childOption(ChannelOption.SO_KEEPALIVE, true); // (6)// Bind and start to accept incoming connections.ChannelFuture f = b.bind(port).sync(); // (7)// Wait until the server socket is closed.// In this example, this does not happen, but you can do that to gracefully// shut down your server.f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}public static void main(String[] args) throws InterruptedException {int port;if (args.length > 0) {port = Integer.parseInt(args[0]);} else {port = 8080;}new DiscardServer(port).run();} }?
恭喜,到了現在這個階段我們已經完成了。下面可以進行嘗試,那么在嘗試之前,我要說一句,這個例子非常好,就是一點比較費解,即使我開始測試,往本機8080端口發送內容,我們根本看不出來是否成功,因為我們把內容忽略了 - -!。所以改一下,我們的DiscardServerHandler改成如下,打印收到的字符:
?
package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil;/*** Created by moon on 2017/4/5.*/ public class DiscardServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // super.channelRead(ctx, msg); // ((ByteBuf) msg).release();ByteBuf in = (ByteBuf) msg;try {while (in.isReadable()) {System.out.print((char) in.readByte());System.out.flush();}} finally {ReferenceCountUtil.release(msg);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // super.exceptionCaught(ctx, cause);cause.printStackTrace();ctx.close();} }?
?
然后我們啟動運行我們的main方法:
由于我用的win,不是linux,所以下面的操作可能有人不好使,因為有的win默認沒有啟動telnet,這個自己網上百度,很容易。我們打開cmd,輸入telnet,進入一個新的窗口:
?
?
?
然后我們可以查看幫助,輸入?/help,查看win下的使用方式:
注意o,也就是open是我們所需的,我們使用命令:open localhost 8080如下圖:
這說明已經連上了,別跟我似的一直以為在連接ing。。。。。 ?
那么我們現在就可以聯系了,由于我們的邏輯是一個字符一個字符輸出,所以我們輸入hello,在idea控制臺會挨個字符輸出:
那么到這里,說明我們的服務端小demo成功。
?
寫一個Echo服務器
到目前為止,我們一直都在假設服務端是沒有響應的。然而,服務器通常應該響應請求。讓我們學習如何通過實現ECHO協議向客戶端寫入響應消息,其中任何接收到的數據都將被發回。
與前面部分實現的Discard服務器的唯一區別在于它將接收到的數據發回,而不是將接收的數據輸出到控制臺.因此,再次修改channelRead()方法就行了:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ctx.write(msg); // (1)ctx.flush(); // (2) }?
ChannelHandlerContext對象提供了各種可以觸發各種I / O事件和操作的操作。在這里,我們調用write(Object)來逐字寫入接收到的消息。請注意,我們沒有像DISCARD示例中那樣發布接收的消息。這是因為,當Netty發布給電子郵件時,Netty會為您報告。
如果再次運行telnet命令,您將看到服務器發送回發送給它的任何內容。(自行查看)
?
編寫一個時間服務器
本節中實現的協議是TIME協議。它與前面的示例不同之處在于,它發送一個包含32位整數的消息,而不接收任何請求,并在發送消息后關閉連接。在此示例中,您將學習如何構建和發送消息,并在完成時關閉連接。
因為我們不是將忽略任何接收到的數據,而是在建立連接后立即發送消息,這次我們不能使用channelRead()方法。相反,我們應該覆蓋channelActive()方法。所以我們創建一個新的類TimeServerHandler,以下是實現:
package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;/*** Created by moon on 2017/4/5.*/ public class TimeServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(final ChannelHandlerContext ctx) throws Exception {final ByteBuf time = ctx.alloc().buffer(4);time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));final ChannelFuture f = ctx.writeAndFlush(time);f.addListener(new ChannelFutureListener() {public void operationComplete(ChannelFuture future) throws Exception {assert f == future;ctx.close();}});}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {super.exceptionCaught(ctx, cause);} }?
要測試我們的時間服務器是否按預期工作,可以使用UNIX rdate命令:
$ rdate -o <port> -p <host>?
由于我的是win我就不測試了。
好了,這次就先到這里,明天繼續接下來的內容。
1、為什么編程中建議使用netty而不是用jdk nio?
如果對nio了解比較透徹的話,就不會糾結這個問題了,畢竟市面上流行的中間件,如mycat ,spark都是用的nio,當然使用netty的更多,如dubbo; 我們需要知道nio的原理,同時也不必亂...
總結
以上是生活随笔為你收集整理的Netty从零开始(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zcmu1862(模拟)
- 下一篇: sql中exits和in的区别