錆の詳现スケヌラブルなチャットをれロから䜜成する、パヌト1

パヌト1WebSocketを実装したす。 はじめに



この䞀連の蚘事では、リアルタむムで機胜するスケヌラブルなチャットを䜜成するプロセスに぀いお説明したす。

このレビュヌの目的は、Rustプログラミング蚀語が実際に急速に普及しおいる基瀎の基瀎を段階的に研究するこずであり、システムむンタヌフェむスを網矅しおいたす。



最初の郚分では、環境の初期蚭定ず最も単玔なWebSocketサヌバヌの実装に぀いお怜蚎したす。 蚘事の技術的な詳现を理解するには、Rust蚀語の経隓は必芁ありたせんが、システムAPIPOSIXずC / C ++の基本的な知識は䞍必芁ではありたせん。 読み始める前に、少し時間をずっおそしおコヌヒヌも取りたしょう-この蚘事では、すべおを可胜な限り詳现に説明しおいるため、非垞に長くなりたす。





1 Rust-遞択の理由



Rustプログラミング蚀語に興味を持぀ようになったのは、長幎にわたるシステムプログラミングぞの情熱であり、面癜くもありながら非垞に耇雑です。



そしお、おそらく、ここで最も難しい問題は、メモリを䜿甚した安党な䜜業ず呌ぶこずができたす。 バッファオヌバヌフロヌ 、 メモリリヌク 、二重メモリ割り圓お解陀、 参照のダングリング 、すでに解攟されたメモリぞのポむンタの逆参照など 、倚くのバグを匕き起こすのはメモリの䞍適切な動䜜です。 たた、このような゚ラヌは深刻なセキュリティ問題を䌎うこずがありたす。たずえば、OpenSSLの悪名高いバグHeartbleedの原因は、メモリのずさんな凊理にすぎたせん。 そしお、これは氷山の䞀角にすぎたせん。私たちが毎日䜿甚しおいる゜フトりェアにそのようなギャップがいく぀隠されおいるのかは誰にもわかりたせん。



C ++では、このような問題を解決するためのいく぀かの方法が考案されたした。たずえば、スマヌトポむンタヌの䜿甚[1]やスタック䞊の割り圓お[2]などです。 残念ながら、そのようなアプロヌチを䜿甚しおも、「足を撃぀」ず呌ばれるものがただ存圚する可胜性がありたす-バッファヌの境界を越えるか、垞に䜿甚可胜なメモリを操䜜するための䜎レベル関数を䜿甚したす。



぀たり、蚀語レベルでは、このようなプラクティスを適甚するための前提条件はありたせん。代わりに、「優れた開発者」は垞に自分自身を䜿甚し、間違いを犯さないず考えられおいたす。 ただし、倧量のコヌドを手動で完党にチェックするこずはできないため、コヌドにこのような重倧な問題が存圚するこずは、開発者のレベルずはたったく関係ないず考えおいたす。これはコンピュヌタヌのタスクです。 ある皋床たでは、静的解析ツヌルがここで圹立ちたすが、すべおではなく、垞に䜿甚するわけではありたせん。



このため、メモリの操䜜で問題を取り陀く別の基本的な方法がありたす。ガベヌゞコレクションは、コンピュヌタサむ゚ンスの別の耇雑な知識の領域です。 ほずんどすべおの最新の蚀語ず仮想マシンには䜕らかの圢の自動ガベヌゞコレクションがあり、ほずんどの堎合これは非垞に優れた゜リュヌションであるずいう事実にもかかわらず、欠点がありたす。たず、自動ガベヌゞコレクタは理解ず実装が困難です] 。 第二に、ガベヌゞコレクションの䜿甚は、未䜿甚のメモリを解攟するための䞀時停止を意味したす[4] 。これは通垞、高負荷のアプリケヌションで遅延を枛らすための埮調敎の必芁性を䌎いたす。



Rustにはこの問題に察する別のアプロヌチがありたす。黄金の平均ずは、远加のメモリやプロセッサ時間を必芁ずせず、各ステップの自己远跡が䞍芁なメモリずリ゜ヌスの自動解攟です。 これは所有暩ず借甚の抂念の適甚によっお達成されたす。



蚀語は、各倀が所有者を 1人だけ持぀こずができるずいうステヌトメントに基づいおいたす。぀たり、メモリの特定の領域を指す可倉倉数は1぀しか存圚できたせん。



let foo = vec![1, 2, 3]; //     (),   1, 2,  3, //       `foo`. let bar = foo; //     `bar`. //          `foo`, //      "" - ..,    .
      
      







このアプロヌチには興味深い結果がありたす倀は1぀の倉数のみに関連付けられおいるため、倉数がスコヌプを出るず、この倀に関連付けられたリ゜ヌスメモリ、ファむル蚘述子、゜ケットなどが自動的に解攟されたす䞭括匧内のコヌドブロックによっお蚭定されたす 、 {



および}



。



そのような人為的な制限は䞍必芁で過床に耇雑に思えるかもしれたせんが、慎重に考えれば、これは抂しお、Rustの「キラヌ機胜」であり、実甚的な理由のためだけに珟れたした。 このアプロヌチにより、C / C ++で蚘述された䜎レベルコヌドの有効性を維持しながら、Rustを高レベル蚀語のように芋せるこずができたす。



しかし、すべおの興味深い機胜にもかかわらず、最近たでRustには深刻な欠陥がありたした-たずえば、互換性を保蚌できない非垞に䞍安定なAPIなどです。 しかし、この蚀語の䜜成者はほが10幎で倧きな進歩を遂げおおり[5] 、珟圚、安定バヌゞョン1.0のリリヌスにより、蚀語は実際のプロゞェクトで実践できるように進化しおいたす。



2ゎヌル



私は、珟実の䞖界で比范的簡単なプロゞェクトを開発しながら、新しい蚀語ず抂念を孊ぶこずを奜みたす。 したがっお、蚀語の可胜性は、必芁になったずきに正確に研究されたす。 Rustを探玢するプロゞェクトずしお、Chat Rouletteなどの匿名チャットサヌビスを遞択したした。 私の意芋では、これはチャットがサヌバヌからの短い応答時間を通垞芁求し、倚数の同時接続を意味するずいう理由から適切な遞択です。 私たちは数千を頌りにしたす-したがっお、実際の環境でRustで曞かれたプログラムのメモリ消費ずパフォヌマンスを芋るこずができたす。



最終結果は、さたざたなクラりドホスティングサヌビスにサヌバヌを展開するためのスクリプトを含むバむナリプログラムファむルになりたす。



しかし、コヌドを曞き始める前に、I / Oでいく぀かのポむントを説明するために少し䜙談をする必芁がありたす。それを適切に操䜜するこずがネットワヌクサヌビスの開発における重芁なポむントだからです。



3 I / Oオプション



タスクを完了するには、サヌビスがネットワヌク゜ケットを介しおデヌタを送受信する必芁がありたす。



䞀芋、タスクは簡単ですが、実際には、さたざたな耇雑さやさたざたな有効性を解決するための倚くの方法がありたす。 それらの䞻な違いはロックぞのアプロヌチにありたす。ここでの暙準的なプラクティスは、新しいデヌタが゜ケットに到着するのを埅っおいる間にプロセッサを停止するこずです。



他のナヌザヌをブロックする1人のナヌザヌ向けのサヌビスを構築するこずはできないため、それらを互いに䜕らかの圢で分離する必芁がありたす。 兞型的な解決策は、ナヌザヌごずに個別のスレッドを䜜成するこずです。 したがっお、プロセス党䜓がブロックされるのではなく、そのスレッドの1぀だけがブロックされたす。 このアプロヌチの短所は、比范的単玔であるにもかかわらず、メモリ消費が増加するこずです。各スレッドは、䜜成されるずスタック甚にメモリの䞀郚を予玄したす[6] 。 さらに、実行コンテキストを切り替える必芁があるため、問題は耇雑です。最新のサヌバヌプロセッサには通垞8から16コアがあり、ハヌドりェアが蚱可するよりも倚くのスレッドを䜜成するず、OSスケゞュヌラヌは十分な速床でタスクスむッチングに察凊しなくなりたす。



したがっお、マルチスレッドプログラムを倚数の接続にスケヌリングするこずは非垞に困難な堎合があり、この堎合、数千の同時接続ナヌザヌを蚈画しおいるため、たったく合理的ではありたせん。 最終的には、Habraeffectに備える必芁がありたす



4むベントルヌプ



入出力の効率的な䜜業のために、むベント凊理サむクルに基づいた倚重化システムAPIを䜿甚したす 。 Linuxカヌネル[7]にはこのためのepollメカニズムがあり、FreeBSDおよびOS Xにはkqueue [8]がありたす。



これらのAPIはどちらもかなり䌌た方法で配眮されおおり、䞀般的な考え方は単玔です。新しいデヌタがネットワヌク経由で゜ケットに届くのを埅぀のではなく、 ゜ケットに到着したバむトを通知するように䟝頌したす。



むベントの圢匏のアラヌトは䞀般的なサむクルに入りたすが、この堎合はブロッカヌずしお機胜したす。 ぀たり、新しいデヌタの数千の゜ケットを絶えずチェックするのではなく、゜ケットがそれに぀いお通知するのを埅぀だけです-そしお、接続されたナヌザヌは非垞に頻繁にスタンバむモヌドになり、䜕も送信せず、受信しおいたす。 これは、WebSocketを䜿甚するアプリケヌションに特に圓おはたりたす。 さらに、非同期I / Oを䜿甚するず、オヌバヌヘッドはほずんどありたせん。メモリに保存する必芁があるのは、゜ケットファむル蚘述子ずクラむアントの状態だけですチャットの堎合、これは接続ごずに数癟バむトです。



このアプロヌチの興味深い機胜は、ネットワヌク接続だけでなく、たずえばディスクからファむルを読み取るために非同期I / Oを䜿甚できるこずです。むベントルヌプは、あらゆるタむプのファむル蚘述子を受け入れたす* NIXの䞖界の゜ケットはたさにそれです。



Node.jsのむベントルヌプずRubyのEventMachine gemはたったく同じように機胜したす。

同じこずが、非同期I / O [9]のみを䜿甚するnginx Webサヌバヌの堎合にも圓おはたりたす。





5はじめに



远加のテキストは、Rustが既にむンストヌルされおいるこずを意味したす。 ただない堎合は、公匏Webサむトのドキュメントに埓っおください 。



Rustの暙準配信には、Maven、Composer、npm、たたはrakeに類䌌した機胜を実行するcargo



ず呌ばれるプログラムがありたす-アプリケヌションの䟝存関係を管理し、プロゞェクトをビルドし、テストを実行し、最も重芁なこずは、新しいプロゞェクトの䜜成プロセスを簡玠化したす。



これはたさに今必芁なものです。そこでタヌミナルを開いおこのコマンドを入力しおみたしょう。



 cargo new chat --bin
      
      







--bin



匕数は、ラむブラリではなくスタヌトアップアプリケヌションを䜜成するようCargoに指瀺したす。



その結果、2぀のファむルが䜜成されたす。



 Cargo.toml src/main.rs
      
      







Cargo.toml



は、説明ずプロゞェクトの䟝存関係ぞのリンクが含たれおいたすJavaScriptのpackage.json



ず同様。

src/main.rs



はメむン゜ヌスファむルであり、プログラムぞの゚ントリポむントです。



最初に他に䜕も必芁ないため、1぀のコマンドでプログラムをコンパむルしお実行するこずができたすcargo run



。 同じコマンドは、もしあれば、コヌドに゚ラヌを衚瀺したす。



あなたが幞せなEmacsナヌザヌなら、圌はCargoず「すぐに䜿える」互換性があるこずを喜んで知るでしょう-MELPAリポゞトリからrust-mode



パッケヌゞをむンストヌルし、 cargo build



を起動するようにコンパむルコマンドを蚭定cargo build



です。





6 Rustでのむベント凊理



理論から実践に移りたす。 新しいメッセヌゞが衚瀺されるのを埅぀最も単玔なむベントルヌプを実行しおみたしょう。 これを行うために、さたざたなシステムAPIを手動で接続する必芁はありたせん- 「 Metal IO 」たたはmioず呌ばれる非同期I / Oを操䜜するために既存のラむブラリを䜿甚するだけです。



芚えおいるように、Cargoプログラムは䟝存関係を扱いたす。 crates.ioリポゞトリからラむブラリをダりンロヌドしたすが、さらにGitリポゞトリから盎接それらを取埗するこずができたす-この機胜は、ただパッケヌゞリポゞトリにロヌドされおいないラむブラリの最新バヌゞョンを䜿甚する必芁がある堎合に圹立ちたす。



この蚘事の執筆時点では、 mio



のリポゞトリには叀いバヌゞョン0.3しかありたせん。開発䞭のバヌゞョン0.4では、倚くの䟿利な倉曎が行われおおり、叀いバヌゞョンずも互換性がありたせん。 したがっお、GitHubを介しお盎接接続し、そのような行をCargo.toml



远加しCargo.toml



。



 [dependencies.mio] git = "https://github.com/carllerche/mio"
      
      







プロゞェクトの説明で䟝存関係を定矩した埌、むンポヌトをmain.rs



远加しmain.rs



。



 extern crate mio; use mio::*;
      
      







mio



䜿甚mio



非垞に簡単です。 たず、 EventLoop::new()



関数を呌び出しおむベントルヌプを䜜成したしょう。 ただし、空のルヌプは圹に立たないので、すぐにチャットのむベント凊理を远加しお、 Handler



むンタヌフェヌスに察応する関数を持぀構造を定矩したしょう。



Rustは「䌝統的な」オブゞェクト指向プログラミングをサポヌトしおいたせんが、構造はクラスにほが類䌌しおおり、埓来のOOPず同様の方法で、蚀語で芏制されおいるむンタヌフェむスを実装できたす。



新しい構造を定矩したしょう



 struct WebSocketServer;
      
      







そしお、 Handler



ためのHandler



を実装したす。



 impl Handler for WebSocketServer { //         ,  //  Handler     :  //      . //          ,    //      mio: type Timeout = usize; type Message = (); }
      
      







次に、むベントルヌプを実行したす。

 fn main() { let mut event_loop = EventLoop::new().unwrap(); //     Handler: let mut handler = WebSocketServer; // ...        : event_loop.run(&mut handler).unwrap(); }
      
      







ここでは、最初に借甚借甚の䜿甚に遭遇したす最埌の行で&mut



に泚意しおください。 これは、倀の「所有暩」を䞀時的に転送し、デヌタを倉曎する可胜性がある別の倉数にリンクするこずを意味したす。







簡単に蚀えば、借甚の原理は次のように想像できたす擬䌌コヌド



 //     "" -  owner: let mut owner = value; //          : { let mut borrow = owner; //        . //        : borrow.mutate(); //       : owner = borrow; }
      
      







䞊蚘のコヌドはこれず同等です

 //     "" -  owner: let owner = value; { //     : let mut borrow = &mut owner; //     ,     . //       : borrow.mutate(); //         //     . }
      
      







スコヌプごずに、倉数は1぀の可倉ボロヌのみを持぀こずができ、倀の所有者でさえも、 ボロヌむングがスコヌプを離れるたで読み取りたたは倉曎できたせん。



さらに、 䞍倉のBorrowを䜿甚しお倀を借甚する簡単な方法があり、読み取り専甚の倀を䜿甚できたす。 たた、倉数の借甚である&mut



ずは異なり、読み取りに制限を蚭定せず、曞き蟌みにのみ制限を蚭定したす-スコヌプ内に䞍倉の借甚がある限り、倀を倉曎しお&mut



から借甚するこずはできたせん。



そのような説明があなたにずっお十分に明確でないように芋えおも倧䞈倫です-遅かれ早かれ盎感的な理解が来るでしょう、Rustの借甚はどこでも䜿われおいるので、蚘事を読むずより実甚的な䟋が芋぀かりたす。



それでは、プロゞェクトに戻りたしょう。 「 cargo run



」コマンドをcargo run



ず、Cargoは必芁な䟝存関係をすべおダりンロヌドし、プログラムをコンパむルしたすいく぀かの譊告はありたすが、珟時点では無芖できたす。



その結果、カヌ゜ルが点滅するタヌミナルりィンドりが衚瀺されたす。 あたりおもしろい結果ではありたせんが、少なくずもプログラムが正しく実行されおいるこずを瀺しおいたす。これたでのずころ有甚なこずは䜕もしおいたせんが、むベントルヌプを正垞に起動したした。 この状況を修正したしょう。



プログラムを䞭断するには、キヌボヌドショヌトカットCtrl + Cを䜿甚したす。





7 TCPサヌバヌ



WebSocketプロトコル経由の接続を受け入れるTCPサヌバヌを起動するには、このために蚭蚈された構造構造䜓を䜿甚したすTcpListener



mio::tcp



パッケヌゞのTcpListener



。 サヌバヌTCP゜ケットを䜜成するプロセスは非垞に簡単です。特定のアドレスIP +ポヌト番号にバむンドし、゜ケットをリッスンしお接続を受け入れたす。 私たちは圌をあたり離れたせん。



コヌドを芋おください



 use mio::tcp::*; use std::net::SocketAddr; ... let address = "0.0.0.0:10000".parse::<SocketAddr>().unwrap(); let server_socket = TcpListener::bind(&address).unwrap(); event_loop.register(&server_socket, Token(0), EventSet::readable(), PollOpt::edge()).unwrap();
      
      







行ごずに芋おみたしょう。



たず、 main.rs



モゞュヌルのスコヌプに、TCPを操䜜するためのパッケヌゞず、゜ケットアドレスを蚘述するSocketAddr



構造䜓をむンポヌトする必芁がありたす。これらの行をファむルの先頭に远加したす。



 use mio::tcp::*; use std::net::SocketAddr;
      
      







文字列"0.0.0.0:10000"



を解析しお、アドレスを説明する構造に倉換し、゜ケットをこのアドレスにバむンドしたす。



 let address = "0.0.0.0:10000".parse::<SocketAddr>().unwrap(); server_socket.bind(&address).unwrap();
      
      







コンパむラヌが必芁なタむプの構造を衚瀺する方法に泚意しおください server_socket.bind



はSockAddr



タむプの匕数を予期するため、明瀺的に指定しおコヌドを詰たらせる必芁はありたせんSockAddr



コンパむラヌはそれを独立しお決定できたす。



リスニング゜ケットを䜜成しお、リスニングを開始したす。



 let server_socket = TcpListener::bind(&address).unwrap();
      
      







たた、関数の実行結果でunwrap



を呌び出すほずんどすべおの堎所に気づいたかもしれたせん。これはRustの゚ラヌ凊理のパタヌンであり、すぐにこのトピックに戻りたす。



次に、䜜成した゜ケットをむベントルヌプに远加したしょう。



 event_loop.register(&server_socket, Token(0), EventSet::readable(), PollOpt::edge()).unwrap();
      
      







register



呌び出しregister



より耇雑です-関数は次の匕数を取りたす







次に、結果のコヌドをコンパむルし、 cargo run



コマンドを䜿甚しおプログラムをcargo run



たす。 タヌミナルでは、点滅カヌ゜ル以倖は䜕も衚瀺されたせんが、 netstat



コマンドを個別に実行するず、゜ケットがポヌト番号10000ぞの接続を埅機しおいるこずがわかりたす。



 $ netstat -ln |  grep 10000
 tcp 0 0 127.0.0.1:10000 0.0.0.0:*リッスン




8接続を受け入れる



すべおのWebSocket接続は、接続の確立いわゆるハンドシェむク 、HTTP経由で送信される特別なシヌケンスの芁求ず応答の確認から始たりたす。 ぀たり、WebSocketの実装を進める前に、サヌバヌに基本プロトコルであるHTTP / 1.1を䜿甚しお通信する方法を教える必芁がありたす。



ただし、HTTPの䞀郚のみが必芁です。WebSocketを介しお接続を確立するクラむアントは、ヘッダヌConnection: Upgrade



およびUpgrade: websocket



を䜿甚しおリク゚ストを送信し、このリク゚ストに特定の方法で応答する必芁がありたす。 それだけです。ファむル、静的コンテンツなどの配垃を備えた本栌的なWebサヌバヌを䜜成する必芁はありたせん。 -より高床で適切なツヌルがありたすたずえば、同じnginx。





WebSocket接続芁求ヘッダヌ。





しかし、HTTPの実装を開始する前に、クラむアントずの接続を確立し、クラむアントからのむベントをサブスクラむブするコヌドを蚘述する必芁がありたす。



基本的な実装を怜蚎しおください。



 use std::collections::HashMap; struct WebSocketServer { socket: TcpListener, clients: HashMap<Token, TcpStream>, token_counter: usize } const SERVER_TOKEN: Token = Token(0); impl Handler for WebSocketServer { type Timeout = usize; type Message = (); fn ready(&mut self, event_loop: &mut EventLoop<WebSocketServer>, token: Token, events: EventSet) { match token { SERVER_TOKEN => { let client_socket = match self.socket.accept() { Err(e) => { println!("  : {}", e); return; }, Ok(None) => panic!(" accept  'None'"), Ok(Some(sock)) => sock }; self.token_counter += 1; let new_token = Token(self.token_counter); self.clients.insert(new_token, client_socket); event_loop.register(&self.clients[&new_token], new_token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap(); } } } }
      
      







たくさんのコヌドがあったので、ステップごずに詳现に芋おいきたしょう。



たず、 WebSocketServer



サヌバヌ構造に状態を远加する必芁がありたす-サヌバヌ゜ケットず接続されたクラむアントの゜ケットを栌玍したす。



 use std::collections::HashMap; struct WebSocketServer { socket: TcpListener, clients: HashMap<Token, TcpStream>, token_counter: usize }
      
      







クラむアント゜ケットを栌玍するには、暙準コレクションラむブラリのHashMap



デヌタ構造を䜿甚したすHashMap



std::collections



は、 ハッシュテヌブル 蟞曞および連想配列ずも呌ばれたすの暙準実装です。 キヌずしお、すでにおなじみのトヌクンを䜿甚したす。これは、接続ごずに䞀意である必芁がありたす。



たず、簡単な方法でトヌクンを生成できたす。カりンタヌを䜿甚しお、新しい接続ごずに1぀ず぀増やしたす。 これを行うには、構造䜓にtoken_counter



倉数が必芁です。



次に、再びmio



ラむブラリから䟿利なHandler



型をmio



たす。



 impl Handler for WebSocketServer
      
      







トレむトの実装では、コヌルバック関数callback-readyを再定矩する必芁がありたす。再定矩ずは、タむプにHandler



すでにダミヌ関数ready



ず他のコヌルバック関数の空癜が含たれおいるこずを意味したす。型で定矩された実装は、もちろん有甚なこずは䜕もしないので、関心のあるむベントを凊理するために独自のバヌゞョンの関数を定矩する必芁がありたす。



 fn ready(&mut self, event_loop: &mut EventLoop<WebSocketServer>, token: Token, events: EventSet)
      
      







この関数は、゜ケットが読み取りたたは曞き蟌みサブスクリプションに応じお可胜になるたびに呌び出され、その呌び出しパラメヌタヌを通じお、必芁なすべおの情報を取埗したす。むベントルヌプの構造のむンスタンス、むベント゜ヌスに関連付けられたトヌクンこの堎合、゜ケット、およびEventSet



むベントに関する情報を含むフラグのセットを含む特別な構造読み取り甚たたは曞き蟌み甚のそれぞれの゜ケットの可甚性に関する通知の堎合に読み取り可胜。



リスニング゜ケットは読み取り可胜なむベントを生成したす新しいクラむアントが保留䞭の接続のキュヌに入る瞬間。ただし、接続を開始する前に、むベントの゜ヌスがリスニング゜ケットであるこずを確認する必芁がありたす。これは、パタヌンマッチングを䜿甚しお簡単に確認できたす。



 match token { SERVER_TOKEN => { ... } }
      
      







これはどういう意味ですかこの構文は、「埓来の」呜什型プログラミング蚀語match



の暙準スむッチ構成を連想させたすが、より倚くの機胜を提䟛したす。たずえば、Javaでは、構成䜓はswitch



特定のタむプのセットに制限され、列挙型の数倀、文字列、および列挙に察しおのみ機胜したす。ただし、Rustでは、match



耇数の倀、構造などを含む、ほがすべおのタむプの比范を行うこずができたす。マッチングに加えお、match



正芏衚珟ず同様の方法でサンプルの内容たたは郚分をキャプチャするこずもできたす。



䞊蚘の䟋では、トヌクンをサンプルにマッピングToken(0)



したす。思い出すず、トヌクンはリスニング゜ケットに接続されおいたす。そしお、コヌドを読むずきに意図をより明確にするために、このトヌクンを定数ずしお定矩したした SERVER_TOKEN







 const SERVER_TOKEN: Token = Token(0);
      
      







したがっお、匏の䟋match



この堎合には、これに盞圓したすmatch { Token(0) => ... }



。



サヌバヌ゜ケットを凊理しおいるず確信できたので、クラむアントずの接続を確立できたす。

 let client_socket = match self.socket.accept() { Err(e) => { println!("  : {}", e); return; }, Ok(None) => unreachable!(), Ok(Some(sock)) => sock };
      
      







ここでも、サンプルず比范したす。今回accept()



は、typeの「ラッパヌ」でクラむアント゜ケットを返す関数を実行した結果をチェックしたすResult<Option<TcpStream>>



。Result



これは、Rustの゚ラヌ凊理の基本ずなる特別なタむプです。゚ラヌ、タむムアりトタむムアりトなどの「未定矩」の結果のラッパヌです。



個々のケヌスでは、そのような結果をどう凊理するかを個別に決定できたすが、もちろん、すべおの゚ラヌを正しく凊理したす。ここで、すでに慣れ芪しんでいる関数unwrap()



が、暙準の動䜜を提䟛したす。゚ラヌの堎合にプログラムの実行を䞭断し、関数の結果をコンテナから「アンパック」したすResult



すべおが順調である堎合。したがっお、を䜿甚unwrap()



するこずは、即時の結果ず、゚ラヌが適切なずきにプログラムの実行が停止する状況にのみ関心があるこずを意味したす。



これはいく぀かの時点で蚱容される動䜜ですが、状況の組み合わせが倱敗した堎合、その呌び出しによっおサヌバヌがシャットダりンされ、すべおのナヌザヌが切断される可胜性があるため、accept()



䜿甚するのunwrap()



は䞍合理です。したがっお、゚ラヌをログに出力しお実行を継続したす。



 Err(e) => { println!("  : {}", e); return; },
      
      







タむプOption



はResult



、倀の有無を決定する「ラッパヌ」のようなものです。倀が存圚しないこずはNone



; ず衚瀺され、反察の堎合、倀はの圢匏をずりたすSome(value)



。おそらく掚枬されるように、この型は他の蚀語のnullたたはNone型に匹敵したす。Option



すべおのnull倀がロヌカラむズされ、Result



たずえば䜿甚前に必須の「アンパック」が必芁なため、安党です。NullReferenceException



あなた自身がしたくない堎合、「有名な」間違い。



返されたaccept()



結果をアンパックしたしょう



 Ok(None) => unreachable!(),
      
      







この堎合、結果ずしお倀None



が返される状況は䞍可胜accept()



です。クラむアント぀たり、リッスンしおいない゜ケットに適甚されたずきにこの関数を呌び出そうずした堎合にのみ返されたす。たた、サヌバヌ゜ケットを凊理しおいるず確信しおいるため、通垞の状況ではこのコヌドの実行には至らないはずです。したがっお、unreachable!()



゚ラヌでプログラムの実行を䞭断する特別な構造を䜿甚したす。



結果をサンプルず比范し続けたす。

 let client_socket = match self.socket.accept() { ... Ok(Some(sock)) => sock }
      
      







ここで最も興味深いのmatch



は、それが単なる呜什ではなく、匏぀たりmatch



、結果も返すであるため、マッチングに加えお、倀をキャプチャするこずもできるこずです。したがっお、これを䜿甚しお結果を倉数に割り圓おるこずができたす。䞊蚘のように、型から倀をアンパックResult<Option<TcpStream>>



し、倉数に割り圓おたすclient_socket



。



トヌクンカりンタヌを増やすこずを忘れずに、受信した゜ケットをハッシュテヌブルに保存したす。



 let new_token = Token(self.token_counter); self.clients.insert(new_token, client_socket); self.token_counter += 1;
      
      







最埌に、接続を確立したばかりの゜ケットからむベントをサブスクラむブする必芁がありたす。むベントルヌプに登録したしょう。これは、サヌバヌ゜ケットの登録ずたったく同じ方法で行われたすが、ここではパラメヌタヌずしお別のトヌクン、そしおもちろん別の゜ケットを提䟛したす。

 event_loop.register(&self.clients[&new_token], new_token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap();
      
      







あなたは、匕数のセットに別の違いに気づいたこずがありたす。に加えおPollOpt::edge()



、我々は新しいオプションが远加されたしたPollOpt::oneshot()



。むベントがトリガヌされるず、ルヌプから゜ケットの登録を䞀時的に削陀するように指瀺したす。これは、サヌバヌコヌドを簡玠化するのに圹立ちたす。このオプションがなければ、゜ケットの珟圚の状態を手動で監芖する必芁がありたす-今すぐ曞き蟌みが可胜か、今すぐ読み取り可胜かなど。代わりに、珟時点で必芁なオプションずサブスクリプションのセットを䜿甚しお、毎回゜ケットを単玔に登録したす。その䞊、このアプロヌチはマルチスレッドのむベントルヌプに圹立ちたすが、次回はさらに圹立ちたす。



そしお最埌に、私たちの構造がWebSocketServer



耇雑なため、むベントルヌプでサヌバヌ登録コヌドを倉曎する必芁がありたす。倉曎は非垞に簡単で、䞻に新しい構造の初期化に関するものです。



 let mut server = WebSocketServer { token_counter: 1, //     1 clients: HashMap::new(), //   -, HashMap socket: server_socket //       }; event_loop.register(&server.socket, SERVER_TOKEN, EventSet::readable(), PollOpt::edge()).unwrap(); event_loop.run(&mut server).unwrap();
      
      







9 Parsim HTTP



プロトコルに埓っおクラむアントずの接続を確立したので、着信HTTPリク゚ストを解析し、WebSocketプロトコルぞの接続を「切り替える」アップグレヌドする必芁がありたす。



これはかなり退屈なタスクなので、すべおを手動で行うのではなく、代わりにhttp-muncher



HTTP解析ラむブラリを䜿甚しお䟝存関係リストに远加したす。このラむブラリは、Node.jsのHTTPパヌサヌnginxのパヌサヌでもありたすをHTTPに適合させたす。これにより、芁求をストリヌミングモヌドで凊理でき、TCP接続に非垞に圹立ちたす。



に䟝存関係を远加したしょうCargo.toml







 [dependencies] http-muncher = "0.2.0"
      
      







ラむブラリAPIを詳现に怜蚎するこずはせず、すぐにパヌサヌの䜜成に進みたす。



 extern crate http_muncher; use http_muncher::{Parser, ParserHandler}; struct HttpParser; impl ParserHandler for HttpParser { } struct WebSocketClient { socket: TcpStream, http_parser: Parser<HttpParser> } impl WebSocketClient { fn read(&mut self) { loop { let mut buf = [0; 2048]; match self.socket.try_read(&mut buf) { Err(e) => { println!("  : {:?}", e); return }, Ok(None) => //      . break, Ok(Some(len)) => { self.http_parser.parse(&buf[0..len]); if self.http_parser.is_upgrade() { // ... break; } } } } } fn new(socket: TcpStream) -> WebSocketClient { WebSocketClient { socket: socket, http_parser: Parser::request(HttpParser) } } }
      
      







それでもready



、構造䜓の関数の実装にいく぀かの倉曎を加える必芁がありたすWebSocketServer



。



 match token { SERVER_TOKEN => { ... self.clients.insert(new_token, WebSocketClient::new(client_socket)); event_loop.register(&self.clients[&new_token].socket, new_token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap(); ... }, token => { let mut client = self.clients.get_mut(&token).unwrap(); client.read(); event_loop.reregister(&client.socket, token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap(); } }
      
      







行ごずに新しいコヌドをもう䞀床確認しおみたしょう。



たず、ラむブラリをむンポヌトし、パヌサヌの制埡構造を远加したす。



 extern crate http_muncher; use http_muncher::{Parser, ParserHandler}; struct HttpParser; impl ParserHandler for HttpParser { }
      
      







ここでは、顔の特城の実装を远加ParserHandler



だけでなく、いく぀かの䟿利な機胜、コヌルバックが含たれ、Handler



よりmio



構造の堎合にWebSocketServer



。これらのコヌルバックは、パヌサヌが有甚な情報HTTPヘッダヌ、リク゚ストコンテンツなどを取埗するずすぐに呌び出されたす。しかし、今では、クラむアントがHTTP接続をWebSocketプロトコルに切り替えるために特別なヘッダヌのセットを送信したかどうかを調べるだけで枈みたす。パヌサヌ構造にはこれに必芁な機胜が既に備わっおいるため、珟時点ではコヌルバックを再定矩せず、暙準実装を残したす。



ただし、詳现が1぀ありたす。HTTPパヌサヌには独自の状態があるため、構造䜓の新しいむンスタンスを䜜成する必芁がありたす。HttpParser



新しい顧客ごずに。各クラむアントがパヌサヌの状態を保存するこずを前提に、個々のクラむアントを蚘述する新しい構造を䜜成したしょう。



 struct WebSocketClient { socket: TcpStream, http_parser: Parser<HttpParser> }
      
      







これで、クラむアント゜ケットを同じ堎所に栌玍できるようになったため、サヌバヌ構造内で定矩HashMap<Token, TcpStream>



を眮き換えるこずができHashMap<Token, WebSocketClient>



たす。



さらに、クラむアントの凊理に関連するコヌドを同じ構造に移動するず䟿利です。すべおを1぀の関数に保持するずready



、コヌドはすぐに「ヌヌドル」に倉わりたす。それではread



、構造に別の実装を远加したしょうWebSocketClient



。



 impl WebSocketClient { fn read(&mut self) { ... } }
      
      







この関数はパラメヌタヌを受け入れる必芁はありたせん-構造自䜓の倖偎に必芁な状態が既にありたす。



これで、クラむアントからのデヌタの読み取りを開始できたす。

 loop { let mut buf = [0; 2048]; match self.socket.try_read(&mut buf) { ... } }
      
      







ここで䜕が起こっおいたすか無限のサむクル構築loop { ... }



を開始し、デヌタを曞き蟌むバッファヌに2 KBのメモリを割り圓お、そこに着信デヌタを曞き蟌もうずしたす。



呌び出しtry_read



が倱敗する可胜性があるため、タむプによるパタヌンマッチングを実行したすResult



。

 match self.socket.try_read(&mut buf) { Err(e) => { println!("  : {:?}", e); return }, ... }
      
      







次に、TCP゜ケットバッファヌに読み蟌むバむトが残っおいるかどうかを確認したす。

 match self.socket.try_read(&mut buf) { ... Ok(None) => //      . break, ... }
      
      







try_read



Ok(None)



クラむアントから受信した利甚可胜なデヌタをすべお読み取った堎合、結果を返したす。これが発生するず、無限のサむクルを䞭断し、新しいむベントを埅ち続けたす。



最埌に、呌び出しtry_read



がバッファにデヌタを曞き蟌んだ堎合の凊理​​を次に瀺したす。

 match self.socket.try_read(&mut buf) { ... Ok(Some(len)) => { self.http_parser.parse(&buf[0..len]); if self.http_parser.is_upgrade() { // ... break; } } }
      
      







ここでは、受信したデヌタをパヌサヌに送信し、WebSocketモヌドぞの接続を「切り替える」ためのリク゚ストの利甚可胜なHTTPヘッダヌをすぐに確認したすより正確には、ヘッダヌが必芁ですConnection: Upgrade



。



最埌の改善点はnew



、クラむアント構造のむンスタンスの䜜成をより䟿利にするために必芁な機胜ですWebSocketClient



。

 fn new(socket: TcpStream) -> WebSocketClient { WebSocketClient { socket: socket, http_parser: Parser::request(HttpParser) } }
      
      







これは、いわゆる関連関数であり、倚くの点で、埓来のオブゞェクト指向アプロヌチの静的メ゜ッドに䌌おいたす。具䜓的new



には、関数ずコンストラクタを比范できたす。ここでは単にむンスタンスを䜜成したすWebSocketClient



が、「コンストラクタヌ」関数がなくおも同じ方法で実行できるこずを理解する必芁がありたす-コンストラクタヌ関数を䜿甚しないず、特別な必芁なしにコヌドが頻繁に繰り返される可胜性があるため、むしろ利䟿性の問題です。結局、DRY原則「繰り返さない」が発明されたした。



さらにいく぀かの詳现がありたす。キヌワヌドは䜿甚しないこずに泚意しおくださいreturn



明瀺的に-Rustを䜿甚するず、関数の最埌の匏を結果ずしお自動的に返すこずができたす。



そしお、この行には明確化が必芁です

 http_parser: Parser::request(HttpParser)
      
      







ここではParser



、連想関数を䜿甚しお構造の新しいむンスタンスを䜜成したすParser::request



。匕数ずしお、以前に定矩されたstructureの䜜成されたむンスタンスを枡したすHttpParser



。



クラむアントを敎理したので、サヌバヌコヌドに戻り、ハンドラヌでready



このような倉曎を行いたす。

 match token { SERVER_TOKEN => { ... }, token => { let mut client = self.clients.get_mut(&token).unwrap(); client.read(); event_loop.reregister(&client.socket, token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap(); } }
      
      







に新しい条件を远加したしたmatch



。これSERVER_TOKEN



は、クラむアント゜ケット内のむベントに加えお、他のすべおのトヌクンを凊理したす。既存のトヌクンを䜿甚するず、ハッシュテヌブルからクラむアント構造の察応するむンスタンスぞの可倉リンクを借甚できたす。



 let mut client = self.clients.get_mut(&token).unwrap();
      
      







ここで、read



䞊蚘で定矩したこのクラむアントの関数を呌び出したしょう。



 client.read();
      
      







最埌に、むベントルヌプでクラむアントを再登録する必芁がありたすためoneshot()





 event_loop.reregister(&client.socket, token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap();
      
      







ご芧のずおり、クラむアント゜ケット登録手順ずの違いはわずかです。実際、呌び出された関数の名前をからに倉曎register



しreregister



、すべお同じパラメヌタヌを枡したす。



これですべおです。クラむアントがWebSocketプロトコルを䜿甚しお接続を確立したいずきがわかり、そのような芁求にどのように応答するかを考えるこずができたす。



10接続確認



基本的に、このような単玔なヘッダヌセットを送り返すこずができたす。



 HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket
      
      







1぀の重芁な詳现がない堎合WebSocketプロトコルは、適切に構成されたヘッダヌを送信するこずも矩務付けおいたすSec-WebSocket-Accept



。RFCによるず、特定のルヌルに埓っおこれを行う必芁がありたす。クラむアントSec-WebSocket-Key



から送信されたヘッダヌを取埗しお蚘憶し、特定の静的文字列"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"



を远加しおから、結果をSHA-1アルゎリズムでハッシュし、最埌にすべおをbase64で゚ンコヌドする必芁がありたす。



Rust暙準ラむブラリにはSHA-1およびbase64を操䜜するための関数はありたせんが、必芁なラむブラリはすべおcrates.ioリポゞトリにあるため、これらをラむブラリに远加したしょうCargo.toml



。



 [dependencies] ... rustc-serialize = "0.3.15" sha1 = "0.1.1"
      
      







このラむブラリにrustc-serialize



は、base64でバむナリデヌタを゚ンコヌドするための関数ずsha1



、SHA-1でハッシュするための関数が含たれおいたす。



応答キヌを生成する関数は非垞に簡単です。



 extern crate sha1; extern crate rustc_serialize; use rustc_serialize::base64::{ToBase64, STANDARD}; fn gen_key(key: &String) -> String { let mut m = sha1::Sha1::new(); let mut buf = [0u8; 20]; m.update(key.as_bytes()); m.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11".as_bytes()); m.output(&mut buf); return buf.to_base64(STANDARD); }
      
      







関数ぞの匕数ずしおキヌを持぀文字列ぞのリンクを取埗しgen_key



、SHA-1ハッシュの新しいむンスタンスを䜜成し、クラむアントから送信されたキヌをそれに远加し、RFCで定矩された定数文字列を远加し、base64で゚ンコヌドされた文字列ずしお結果を返したす。



ただし、この関数を意図した目的に䜿甚するには、たずクラむアントからヘッダヌを取埗する必芁がありたすSec-WebSocket-Key



。前のセクションからHTTPパヌサヌに戻りたしょう。芚えおいるように、このタむプをParserHandler



䜿甚するず、新しいヘッダヌを受信したずきに呌び出されるコヌルバックを再定矩できたす。今こそこの機䌚を利甚する時です-察応する構造の実装を改善したしょう



 use std::cell::RefCell; use std::rc::Rc; struct HttpParser { current_key: Option<String>, headers: Rc<RefCell<HashMap<String, String>>> } impl ParserHandler for HttpParser { fn on_header_field(&mut self, s: &[u8]) -> bool { self.current_key = Some(std::str::from_utf8(s).unwrap().to_string()); true } fn on_header_value(&mut self, s: &[u8]) -> bool { self.headers.borrow_mut() .insert(self.current_key.clone().unwrap(), std::str::from_utf8(s).unwrap().to_string()); true } fn on_headers_complete(&mut self) -> bool { false } }
      
      







このコヌド自䜓は非垞に単玔ですが、ここでは新しい重芁な抂念である共同所有に盎面しおいたす。



ご存知のように、Rustでは倀の所有者は1人だけですが、ある時点で所有暩を共有する必芁がある堎合がありたす。たずえば、この堎合、ハッシュテヌブルで特定のヘッダヌを芋぀ける必芁がありたすが、同時にこれらのヘッダヌを蚘述する必芁がありたすパヌサヌで。したがっお、倉数headers



- WebSocketClient



ずの2人の所有者を取埗したすParserHandler



。



この矛盟を解決するために、Rustには特別な型Rc



がありたす。これは、参照カりントガベヌゞコレクションの䞀皮ず考えるこずができるを備えたラッパヌです。本質的に、コンテナの所有暩を譲枡したすRc



これは、ブラックランゲヌゞマゞックの助けを借りお倚くの所有者に安党に分割できたすRc



。関数を䜿甚しお倀を耇補するだけclone()



で、コンテナがメモリを管理したす。



確かに、ここには埮劙な違いがありたすRc



-immutableを含む倀であり、コンパむラヌの制限により、なんらかの圱響を䞎えるこずはできたせん。実際、これはデヌタのボラティリティに関するRustルヌルの結果にすぎたせん。倉数の借甚は奜きなだけ行うこずができたすが、所有者が1人しかない堎合にのみ倉曎できたす。



そしお、ここでも矛盟がありたす-リストに新しいヘッダヌを远加する必芁があるのは、この倉数を1か所でのみ倉曎するこずが確実であるにもかかわらず、正匏にRust芏則に違反しないようにするためです。このスコアに぀いおはコンパむラヌのみが同意したせんRc



。内容を倉曎しようずするず、コンパむル゚ラヌが発生したす。



しかし、圓然のこずながら、蚀語でこの問題を解決するには、そこにある-それは、コンテナの別のタむプを䜿甚しおいたすRefCell



。圌は、内郚デヌタの䞍安定性のメカニズムによりそれを解決したす。簡単に蚀えば、RefCell



それは私たちが脇に、すべおの怜蚌ルヌルを配眮するこずができたすランタむムの代わりに静的にそれらをチェックするので- ランタむムコンパむル時。したがっお、ヘッダヌを2぀のコンテナヌに同時にラップする必芁がありたすRc<RefCell<...>>



もちろん、心の準備ができおいない堎合はかなり恐ろしく芋えたす。



ハンドラヌから次の行を芋おみたしょうHttpParser



。



 self.headers.borrow_mut() .insert(self.current_key.clone().unwrap(), ...
      
      







このような蚭蚈は党䜓ずしお倉数ボロヌむング&mut



に察応したすが、ボロヌむングの数を制限するすべおのチェックはプログラム実行䞭に動的に実行されるため、コンパむラヌではなく、私たちがこれを泚意深く監芖する必芁がありたす。そうしないず、ランタむム゚ラヌが発生する可胜性がありたす 構造䜓は



倉数の盎接の所有者にheaders



なるWebSocketClient



ため、新しいプロパティを远加しお、新しいコンストラクタヌ関数を䜜成したしょう。

 //   RefCell  Rc    use std::cell::RefCell; use std::rc::Rc; ... struct WebSocketClient { socket: TcpStream, http_parser: Parser<HttpParser>, //      WebSocketClient: headers: Rc<RefCell<HashMap<String, String>>> } impl WebSocketClient { fn new(socket: TcpStream) -> WebSocketClient { let headers = Rc::new(RefCell::new(HashMap::new())); WebSocketClient { socket: socket, //      : headers: headers.clone(), http_parser: Parser::request(HttpParser { current_key: None, // ...     : headers: headers.clone() }) } } ... }
      
      







これでWebSocketClient



、解析枈みヘッダヌにアクセスできるようになったため、それらの䞭から興味のあるヘッダヌを芋぀けるこずができたす- Sec-WebSocket-Key



。クラむアントキヌがある堎合、応答をコンパむルする手順は問題になりたせん。文字列を分割しお収集し、クラむアント゜ケットに曞き蟌むだけです。



ただし、非ブロッキング゜ケットにデヌタを送信するこずはできないため、最初にむベントルヌプに問い合わせお、蚘録甚の゜ケットの可甚性を通知する必芁がありたす。それを簡単に-あなたはフラグのセットを倉曎する必芁がEventSet



でEventSet::writable()



゜ケットの再登録の時。



この行を芚えおいたすか

 event_loop.reregister(&client.socket, token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot()).unwrap();
      
      







興味のある䞀連のむベントをクラむアント状態に保存できたす-構造を倉曎したすWebSocketClient





 struct WebSocketClient { socket: TcpStream, http_parser: Parser<HttpParser>, headers: Rc<RefCell<HashMap<String, String>>>, //   , `interest`: interest: EventSet }
      
      







それに応じお、再登録手順を適宜倉曎したす。

 event_loop.reregister(&client.socket, token, client.interest, //    `EventSet`    PollOpt::edge() | PollOpt::oneshot()).unwrap();
      
      







interest



適切な堎所で倀を倉曎するだけです。このプロセスを簡玠化するために、接続状態を䜿甚しお圢匏化したしょう。



 #[derive(PartialEq)] enum ClientState { AwaitingHandshake, HandshakeResponse, Connected }
      
      







ここでは、サヌバヌに接続されたクラむアントのすべおの可胜な状態の列挙を定矩したす。最初の状態AwaitingHandshake



は、新しいクラむアントがHTTP経由で接続するこずを期埅しおいるこずを意味したす。HandshakeResponse



HTTPを介しおクラむアントに応答するずきの状態を意味したす。そしお最埌に、Connected



クラむアントずの接続を正垞に確立し、WebSocketプロトコルを䜿甚しお通信するずきの状態。



状態倉数をクラむアント構造に远加したす。

 struct WebSocketClient { socket: TcpStream, http_parser: Parser<HttpParser>, headers: Rc<RefCell<HashMap<String, String>>>, interest: EventSet, //   : state: ClientState }
      
      







そしお、新しい倉数の初期倀をコンストラクタヌに远加したす。

 impl WebSocketClient { fn new(socket: TcpStream) -> WebSocketClient { let headers = Rc::new(RefCell::new(HashMap::new())); WebSocketClient { socket: socket, ... // Initial events that interest us interest: EventSet::readable(), // Initial state state: ClientState::AwaitingHandshake } } }
      
      







これで、関数の状態を倉曎できたすread



。これらの行を芚えおいたすか

 match self.socket.try_read(&mut buf) { ... Ok(Some(len)) => { if self.http_parser.is_upgrade() { // ... break; } } }
      
      







条件ブロックのスタブをis_upgrade()



、接続ステヌタスを倉曎するためのコヌドに倉曎したす。



 if self.http_parser.is_upgrade() { //     HandshakeResponse self.state = ClientState::HandshakeResponse; //       Writable // (..    ): self.interest.remove(EventSet::readable()); self.interest.insert(EventSet::writable()); break; }
      
      







察象のフラグのセットをに倉曎した埌Writable



、接続を確立するために応答を送信するために必芁なコヌドを远加したす。構造䜓の実装で



関数を倉曎したす。゜ケット自䜓ぞの応答の曞き蟌み手順は単玔で実際には読み取り手順ず倉わらない、1぀のタむプのむベントを他のタむプから分離するだけで枈みたす。ready



WebSocketServer





 fn ready(&mut self, event_loop: &mut EventLoop<WebSocketServer>, token: Token, events: EventSet) { //        ? if events.is_readable() { // Move all read handling code here match token { SERVER_TOKEN => { ... }, ... } ... } //        : if events.is_writable() { let mut client = self.clients.get_mut(&token).unwrap(); client.write(); event_loop.reregister(&client.socket, token, client.interest, PollOpt::edge() | PollOpt::oneshot()).unwrap(); } }
      
      







少しだけ残っおいたす-郚分的に収集しお応答行を送信する必芁がありたす

 use std::fmt; ... impl WebSocketClient { fn write(&mut self) { //  -    Rc<RefCell<...>>: let headers = self.headers.borrow(); //           : let response_key = gen_key(&headers.get("Sec-WebSocket-Key").unwrap()); //       . //         (printf  , format  Python,  ..), //   Rust    -        // ,         ""  //  .           . let response = fmt::format(format_args!("HTTP/1.1 101 Switching Protocols\r\n\ Connection: Upgrade\r\n\ Sec-WebSocket-Accept: {}\r\n\ Upgrade: websocket\r\n\r\n", response_key)); //    : self.socket.try_write(response.as_bytes()).unwrap(); //    : self.state = ClientState::Connected; //         `readable()` ( ): self.interest.remove(EventSet::writable()); self.interest.insert(EventSet::readable()); } }
      
      







サヌバヌに接続しおみたしょう。お気に入りのブラりザヌで開発コン゜ヌルを開きたずえば、F12を抌しお、次のコヌドを入力したす。



 ws = new WebSocket('ws://127.0.0.1:10000'); if (ws.readyState == WebSocket.OPEN) { console.log('Connection is successful'); }
      
      











すべおが機胜しおいるようです-サヌバヌに接続しおいたす



おわりに



Rust蚀語の可胜性ず珍しい抂念を巡る魅力的な旅は終わりたしたが、最初に觊れただけです-䞀連の蚘事が続きたすもちろん、続線は長くお退屈です:)。他の倚くの興味深い問題を考慮する必芁がありたすセキュアTLS接続、マルチスレッドむベントサむクル、負荷テストず最適化、そしおもちろん、最も重芁なこず-WebSocketプロトコルの実装を終了し、チャットアプリケヌション自䜓を蚘述する必芁がありたす。



しかし、アプリケヌションに到達する前に、ラむブラリコヌドをアプリケヌションコヌドから少しリファクタリングしお分離する必芁がありたす。ほずんどの堎合、crates.ioでラむブラリを公開するこずも怜蚎したす。



珟圚のコヌドはすべおGithubで入手できたす。、リポゞトリをフォヌクしお、リポゞトリ内の䜕かを倉曎しおみおください。



蚘事の以䞋の郚分の倖芳をフォロヌするには、Twitterでフォロヌしおください。



じゃあね



泚釈





[1]本質的に䜿甚しおいるこず、錆に泚意すべきでスマヌトポむンタを、蚀語のレベルに-借入のアむデアは、皮類ず非垞によく䌌おいるunique_ptr



ずshared_ptr



C ++から。



[2]たずえば、NASAのJet Propulsion LaboratoryのNASコヌディング暙準および自動車業界暙準のMISRA Cは、䞀般に、を介した動的メモリ割り圓おの䜿甚を犁止しおいたすmalloc()



。代わりに、スタック䞊のロヌカル倉数の割り圓おず事前に割り圓おられたメモリの䜿甚を想定しおいたす。



[3]単玔なガベヌゞコレクションアルゎリズムは非垞に䜿いやすいですが、マルチスレッドアセンブリなどのより耇雑なオプションでは、かなりの実装䜜業が必芁になる堎合がありたす。たずえば、Go蚀語では、マルチスレッドガベヌゞコレクションはバヌゞョン1.5にのみ登堎し、バヌゞョン1.5は最初のリリヌスからほが3幎埌に登堎したした。



[4]䞀般的に蚀えば、倚くの関数の実装にmalloc()



もメモリの断片化free()



による同じ問題がありたす。



[5]「Graydon Hoar [...]は2006幎にRustず呌ばれる新しいプログラミング蚀語で䜜業を開始したした」-InfoQ「Rustのむンタビュヌ」



[6]マニュアルペヌゞでpthread_create(3)



は、32ビットLinuxシステムで2 MBに぀いお説明しおいたす。



[7] epollを他のシステムAPIず比范するには、出版物「epoll、select、およびポヌリングむベントメカニズムの比范ず評䟡、りォヌタヌルヌ倧孊、2004



[8]「Kqueue䞀般的でスケヌラブルなむベント通知機胜」



9「内郚からのNGINXパフォヌマンスずスケヌリング。」






私は助けに感謝したすむラストず校正のための

ポダスト。

䞋曞きを読んで校正するためのVgaCich。




All Articles