Netty 是一個利用 Java 的高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API 的客戶端/服務器框架。
Netty 是一個普遍使用的 Java 網絡編程框架(Netty 在 2011 年得到了Duke's Choice Award,見https://www.java.net/dukeschoice/2011)。它活躍和成長於用戶社區,像大型公司 Facebook 和 Instagram 以及流行 開源項目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強大的對於網絡抽象的核心代碼。html
以上是摘自《Essential Netty In Action》這本書,本文的內容也是本人讀了這本書以後的一些整理心得,若有不當之處歡迎大蝦們指正java
Netty和Tomcat最大的區別就在於通訊協議,Tomcat是基於Http協議的,他的實質是一個基於http協議的web容器,可是Netty不同,他能經過編程自定義各類協議,由於netty可以經過codec本身來編碼/解碼字節流,完成相似redis訪問的功能,這就是netty和tomcat最大的不一樣。linux
有人說netty的性能就必定比tomcat性能高,其實否則,tomcat從6.x開始就支持了nio模式,而且後續還有APR模式——一種經過jni調用apache網絡庫的模式,相比於舊的bio模式,併發性能獲得了很大提升,特別是APR模式,而netty是否比tomcat性能更高,則要取決於netty程序做者的技術實力了。git
如第一部分所述,netty是一款收到大公司青睞的框架,在我看來,netty可以受到青睞的緣由有三:web
Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通訊框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能獲得了很大提升,兩張圖讓你瞭解BIO和NIO的區別:
redis
Selector
。
除了BIO和NIO以外,還有一些其餘的IO模型,下面這張圖就表示了五種IO模型的處理流程:數據庫
以上摘自Linux IO模式及 select、poll、epoll詳解apache
Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。咱們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,通常咱們的數據若是須要從IO讀取到堆內存,中間須要通過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,若是數據量大,就會形成沒必要要的資源浪費。
Netty針對這種狀況,使用了NIO中的另外一大特性——零拷貝,當他須要接收數據的時候,他會在堆內存以外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面經過ByteBuf能夠直接對這些數據進行直接操做,從而加快了傳輸速度。
下兩圖就介紹了兩種拷貝方式的區別,摘自Linux 中的零拷貝技術,第 1 部分
編程
上文介紹的ByteBuf是Netty的一個重要概念,他是netty數據處理的容器,也是Netty封裝好的一個重要體現,將在下一部分作詳細介紹。segmentfault
要說Netty爲何封裝好,這種用文字是說不清的,直接上代碼:
public class PlainOioServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port); //1 try { for (;;) { final Socket clientSocket = socket.accept(); //2 System.out.println("Accepted connection from " + clientSocket); new Thread(new Runnable() { //3 @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4 out.flush(); clientSocket.close(); //5 } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); //6 } } catch (IOException e) { e.printStackTrace(); } } } 做者:追那個小女孩 連接:https://www.jianshu.com/p/b9f3f6a16911 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
public class PlainNioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); //1 Selector selector = Selector.open(); //2 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //3 final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;) { try { selector.select(); //4 } catch (IOException ex) { ex.printStackTrace(); // handle exception break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); //5 Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { //6 ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); //7 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { //8 SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { //9 break; } } client.close(); //10 } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // 在關閉時忽略 } } } } } } 做者:追那個小女孩 連接:https://www.jianshu.com/p/b9f3f6a16911 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
public class NettyOioServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); EventLoopGroup group = new OioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); //1 b.group(group) //2 .channel(OioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() {//3 @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { //4 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5 } }); } }); ChannelFuture f = b.bind().sync(); //6 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); //7 } } } 做者:追那個小女孩 連接:https://www.jianshu.com/p/b9f3f6a16911 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
從代碼量上來看,Netty就已經秒殺傳統Socket編程了,可是這一部分博大精深,僅僅貼幾個代碼豈能說明問題,在這裏給你們介紹一下Netty的一些重要概念,讓你們更理解Netty。
Channel
數據傳輸流,與channel相關的概念有如下四個,上一張圖讓你瞭解netty裏面的Channel。
他有三種使用模式:
除此以外,他還提供一大堆api方便你使用,在這裏我就不一一列出了,具體參見ByteBuf字節緩存。
Netty入門教程2——動手搭建HttpServer
Netty入門教程3——Decoder和Encoder
Netty入門教程4——如何實現長鏈接
以上就是我對《Netty實戰》這本書的一些心得和書外的一些相關知識整理,若是有不一樣的看法,歡迎討論!