この記事では、Netty とは何ですか? Netty関連の知識の詳細な分析は、必要な友人が参考にできることを願っています。
Netty とは
HTTP から始める
Netty を使用すると、独自の HTTP サーバー、FTP サーバー、UDP サーバー、RPC サーバー、 WebSocketサーバー、Redisプロキシサーバー、MySQLプロキシサーバーなど
従来の HTTP サーバーの原理を確認しましょう
1. ServerSocket を作成し、ポートをリッスンしてバインドします
#2。 ##3. サーバーは Accept を使用してクライアントからソケット接続オブジェクトを取得します##4. 新しいスレッドを開始して接続を処理します
4. ソケットを読み取り、バイト ストリームを取得します4.2. プロトコルをデコードして Http リクエスト オブジェクトを取得します4.3. Http リクエストを処理して結果を取得し、それを HttpResponse オブジェクトにカプセル化します#4.4. プロトコルをエンコードします。結果をワードにシリアル化します。 スロットル書き込みソケットを実行し、バイト ストリームをクライアントに送信します。
5. ステップ 3
を繰り返します。HTTP サーバーが HTTP サーバーと呼ばれる理由は、次のとおりです。エンコードおよびデコード プロトコルが HTTP プロトコルの場合、プロトコルが Redis プロトコルの場合は Redis サーバーになり、プロトコルが WebSocket の場合は WebSocket サーバーになります。 Netty を使用すると、エンコードおよびデコード プロトコルをカスタマイズし、独自のプロトコル固有のサーバーを実装できます。
NIO
上記は従来の HTTP サーバーですが、同時実行性の高い環境ではスレッド数が多くなり、システム負荷が高くなるため、NIO が作成されます。
これは Java 独自の概念ではありません。NIO に代表される用語は IO 多重化と呼ばれます。オペレーティング システムが提供するシステム コールで、当初は select という名前でしたが、その後、Linux では epoll、Mac では kqueue と呼ばれるようになりました。 Apple コンピュータを外部サービスを提供するサーバーとして使用する人は誰もいないため、一般にこれを epoll と呼びます。 Netty は、Java NIO テクノロジーに基づいたフレームワークです。なぜカプセル化が必要なのでしょうか? ネイティブ Java NIO はあまり使いにくく、悪名高いバグがあるためです。Netty がカプセル化すると、操作が簡単な使用モードとインターフェイスが提供され、ユーザーにとってより便利になります。
クライアントがリッスン (Listen) している場合、Accept はブロックされます。新しい接続が来た場合にのみ、Accept が戻り、メインスレッドは続行できます。
ソケットの読み取りおよび書き込み時には、Read が実行されます。リクエスト メッセージが来た場合にのみ、読み取りが返され、サブスレッドは処理を続行できます。
ソケットの読み取りと書き込みの場合、書き込みは、クライアントがメッセージを受信した場合にのみブロックされます。サブスレッドは読み取りを続行できます。従来の BIO モードでは、すべてのスレッドが最初から最後までブロックされ、システム リソースを占有するだけで何も行いません。
それでは、NIO はどのようにしてノンブロッキングを実現するのでしょうか?イベントメカニズムを使用します。 1 つのスレッドを使用して、受け入れ、読み取りおよび書き込み操作、および要求処理のすべてのロジックを実行できます。何もすることがなければ、無限にループすることはなく、次のイベントが来るまでスレッドをスリープさせ、その後動作を継続します。疑似コードで表すと:
while true { events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠 for event in events { if event.isAcceptable { doAccept() // 新链接来了 } elif event.isReadable { request = doRead() // 读消息 if request.isComplete() { doProcess() } } elif event.isWriteable { doWrite() // 写消息 } } }
Reactor スレッド モデル
Reactor シングル スレッド モデル1 つの NIO スレッドと 1 つの受け入れスレッド:
Reactor マルチスレッド モデル
Reactor マスター/スレーブ モデル
マスター/スレーブ Reactor マルチスレッド: クライアント接続を受け入れるために複数のアクセプター NIO スレッド プールが使用されます
#Netty は上記 3 つのモデルに基づいて柔軟に構成できます。
概要
Netty は NIO 上に構築されており、Netty は NIO 上でより高いレベルの抽象化を提供します。
Netty では、接続の受け入れは別のスレッド プールで処理でき、読み取りおよび書き込み操作は別のスレッド プールで処理されます。Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。
如果不用netty,使用原生JDK的话,有如下问题:
1、API复杂
2、对多线程很熟悉:因为NIO涉及到Reactor模式
3、高可用的话:需要出路断连重连、半包读写、失败缓存等问题
4、JDK NIO的bug
而Netty来说,他的api简单、性能高而且社区活跃(dubbo、rocketmq等都使用了它)
现象
先看如下代码,这个代码是使用netty在client端重复写100次数据给server端,ByteBuf是netty的一个字节容器,里面存放是的需要发送的数据
public class FirstClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) { for (int i = 0; i < 1000; i++) { ByteBuf buffer = getByteBuf(ctx); ctx.channel().writeAndFlush(buffer); } } private ByteBuf getByteBuf(ChannelHandlerContext ctx) { byte[] bytes = "需要更多资料加群:586446657".getBytes(Charset.forName("utf-8")); ByteBuf buffer = ctx.alloc().buffer(); buffer.writeBytes(bytes); return buffer; } }
从client端读取到的数据为:
从服务端的控制台输出可以看出,存在三种类型的输出
一种是正常的字符串输出。
一种是多个字符串“粘”在了一起,我们定义这种 ByteBuf 为粘包。
一种是一个字符串被“拆”开,形成一个破碎的包,我们定义这种 ByteBuf 为半包。
应用层面使用了Netty,但是对于操作系统来说,只认TCP协议,尽管我们的应用层是按照 ByteBuf 为 单位来发送数据,server按照Bytebuf读取,但是到了底层操作系统仍然是按照字节流发送数据,因此,数据到了服务端,也是按照字节流的方式读入,然后到了 Netty 应用层面,重新拼装成 ByteBuf,而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 可能是不对等的。因此,我们需要在客户端根据自定义协议来组装我们应用层的数据包,然后在服务端根据我们的应用层的协议来组装数据包,这个过程通常在服务端称为拆包,而在客户端称为粘包。
拆包和粘包是相对的,一端粘了包,另外一端就需要将粘过的包拆开,发送端将三个数据包粘成两个 TCP 数据包发送到接收端,接收端就需要根据应用协议将两个数据包重新组装成三个数据包。
在没有 Netty 的情况下,用户如果自己需要拆包,基本原理就是不断从 TCP 缓冲区中读取数据,每次读取完都需要判断是否是一个完整的数据包 如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从 TCP 缓冲区中读取,直到得到一个完整的数据包。 如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。
而在Netty中,已经造好了许多类型的拆包器,我们直接用就好:
选好拆包器后,在代码中client段和server端将拆包器加入到chanelPipeline之中就好了:
如上实例中:
客户端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));
服务端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));
是在发送数据的时候,传统的实现方式是:
1. `File.read(bytes)`
2. `Socket.send(bytes)`
这种方式需要四次数据拷贝和四次上下文切换:
1. 数据从磁盘读取到内核的read buffer
2. 数据从内核缓冲区拷贝到用户缓冲区
3. 数据从用户缓冲区拷贝到内核的socket buffer
4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区
明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持)
1. transferTo を呼び出し、DMA エンジンによってデータがファイルからカーネル読み取りバッファにコピーされます。
2. その後、DMA がカーネル読み取りバッファからネットワーク カード インターフェイス バッファにデータをコピーします
2 倍 この操作には CPU の参加が必要ないため、コピーはゼロになります。
1. bytebuffer
Netty は主にメッセージの送受信に bytebuffer と bytebuffer を使用します。外部メモリ(DirectMemory)を使用し、Socketを直接読み書きします。
理由: 従来のヒープ メモリがソケットの読み書きに使用されている場合、JVM はヒープ メモリ バッファをダイレクト メモリにコピーしてからソケットに書き込み、バッファの追加メモリ コピーを作成します。 DirectMemory は、DMA
2 を通じてネットワーク カード インターフェイスに直接送信できます。複合バッファ
従来の ByteBuffer、2 つの ByteBuffer のデータを結合する必要がある場合は、最初にサイズを作成する必要があります。 = size1 size2 の新しい配列を作成し、2 つの配列のデータを新しい配列にコピーします。ただし、Netty が提供する結合 ByteBuf を使用すると、そのような操作を回避できます。CompositeByteBuf は実際には複数のバッファを結合するのではなく、それらの参照を保存するため、データのコピーが回避され、コピーがゼロになります。
3. FileChannel.transferTo
Netty の使用では、ゼロ コピーの実装をオペレーティング システムに依存する FileChannel の transferTo メソッドが使用されます。
Netty 内部実行プロセス
#サーバー: ##1. ServerBootStrap インスタンスを作成します。2. Reactor スレッド プールを設定してバインドします。EventLoop は、このスレッドに登録されているセレクター上のすべてのチャネルを処理します。
3. サーバー上でチャネルを設定してバインドします。#4.5. ネットワーク イベントを処理するための ChannelPipeline とハンドラーを作成します。関数のカスタマイズ: デコード SSl セキュリティ認証の編集など
#6. 待機ポートをバインドして開始します#7。準備ができたチャネルがポーリングされると、Reactor スレッド: NioEventLoop がパイプラインでメソッドを実行します。最後に、channelHandler
8 をスケジュールして実行します。そう言えば、Java コミュニケーションおよび学習コミュニティ 586446657 をお勧めします。ここでは、コミュニケーションやディスカッションだけでなく、インタビューの経験や情報を共有することもできます。 Spring、MyBatis、Netty ソース コード分析などの無料資料をダウンロードできます。高同時実行性、高性能、分散型マイクロサービス アーキテクチャ、および JVM パフォーマンスの最適化の原則は、アーキテクトにとって必要な知識体系になっています。すでに仕事をしていて技術的なボトルネックに遭遇しているプログラマーにとって、ここには必要なコンテンツがあると思います。
クライアント
##概要
上記は私ですNetty に関する知識をまとめました。異なる意見がある場合は、お気軽に議論してください。以上がネッティとは何ですか? Netty関連の知識を徹底的に分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。