{"id":3939,"date":"2026-07-29T07:30:00","date_gmt":"2026-07-28T22:30:00","guid":{"rendered":"https:\/\/www.litcoder.com\/?p=3939"},"modified":"2026-07-26T13:31:45","modified_gmt":"2026-07-26T04:31:45","slug":"grpc-streaming%ec%9c%bc%eb%a1%9c-%ec%83%81%ed%83%9c-%ec%a0%84%ec%86%a1-%eb%b0%9b%ea%b8%b0-%ec%98%88%ec%a0%9c","status":"publish","type":"post","link":"https:\/\/litcoder.com\/?p=3939","title":{"rendered":"gRPC Streaming\uc73c\ub85c \uc0c1\ud0dc \uc804\uc1a1 \ubc1b\uae30 \uc608\uc81c"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">gRPC\uc5d0\uc11c Observer pattern\uc744 \uad6c\ud604\ud558\ub294 \uc608\uc81c\ub97c \uace0\ubbfc\ud588\ub358 \uc801\uc774 \uc788\uc5c8\ub294\ub370(<a href=\"https:\/\/litcoder.com\/?p=3509\" data-type=\"link\" data-id=\"https:\/\/litcoder.com\/?p=3509\">gRPC\ub97c \uc774\uc6a9\ud55c Observer Pattern<\/a>) \uc54c\uace0\ubcf4\ub2c8 gRPC\uc5d0\uc11c\ub294 ReadReactor \/ WriteReactor \ubcf4\ub2e4 \uac04\ub2e8\ud558\uac8c object\uc758 \uc0c1\ud0dc \ubcc0\uacbd\uc744 \uac10\uc9c0\ud560 \uc218 \uc788\ub294 \uba54\ucee4\ub2c8\uc998\uc744 \uc81c\uacf5\ud558\uace0 \uc788\uc5c8\ub2e4. gRPC\ub97c \uc774\uc6a9\ud55c Observer pattern \uad6c\ud604 \uc608\uc81c\ub97c \ucc3e\uae30 \uc5b4\ub824\uc6e0\ub358 \uc774\uc720\uac00 \uc5b4\uca4c\uba74 \uadf8\ub7f0 \ubcf5\uc7a1\ud55c \ud328\ud134 \uc5c6\uc774\ub3c4 \uad6c\ud604\uc774 \uac00\ub2a5\ud558\uae30 \ub54c\ubb38\uc774\uc5c8\ub294\uc9c0\ub3c4 \ubaa8\ub974\uaca0\ub2e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\uc774 \ud3ec\uc2a4\ud305\uc5d0\uc11c\ub294 Stream\uc744 \ud1b5\ud574 \ubcc0\uacbd\ub418\ub294 \uac12\ub4e4\uc744 subscribe\ud558\ub294 \uac04\ub2e8\ud55c \uc608\uc81c\ub97c Rust\ub85c \uad6c\ud604\ud574 \ubcf8\ub2e4. gRPC\uc758 Rust \uad6c\ud604\uccb4\uc778 <code>tonic<\/code>\uacfc proto compiler\uc778 <code>prost<\/code>, \ube44\ub3d9\uae30 \ucc98\ub9ac\ub97c \uc704\ud574 <code>tokio<\/code>\ub97c \uc0ac\uc6a9\ud588\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Proto file &#8211; stockprice.proto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\ub79c\ub364\ud558\uac8c \uc0dd\uc131\ub41c \uc8fc\uac00 \uc815\ubcf4\ub97c gRPC\ub85c \uad6c\ub3c5\ud558\ub294 \uac04\ub2e8\ud55c \uc11c\ube44\uc2a4 \uc608\uc81c\ub97c \uac00\uc815\ud574 \ubcf4\uc790. Proto file\uc740 <code>UpdateStockPrice()<\/code>\ub97c \uc815\uc758\ud558\uace0 \ud638\ucd9c\ub418\uba74 <code>StockPriceResponse<\/code>\uc758 stream\uc744 \ubc1b\uc544\uc624\ub3c4\ub85d \ub2e4\uc74c\uacfc \uac19\uc774 \uad6c\uc131\ud55c\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">syntax = \"proto3\";\npackage stockservice;\n\nimport \"google\/protobuf\/empty.proto\";\n\nmessage StockPriceResponse {\n    string symbol = 1;\n    double price = 2;\n    uint64 sequence = 3;\n}\n\nservice StockService {\n    rpc UpdateStockPrice(google.protobuf.Empty) returns (stream StockPriceResponse);\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>StockServiceImpl<\/code> \uad6c\uc870\uccb4\ub97c \uc120\uc5b8\ud558\uace0 \uc11c\ube44\uc2a4\ub97c \uc81c\uacf5\ud558\uae30 \uc704\ud55c <code>StockService<\/code>\ub97c \ub2e4\uc74c\uacfc \uac19\uc774 \uad6c\ud604\ud55c\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Server \uad6c\ud604\uccb4\nstruct StockServiceImpl;\n\n\/\/ tokio_stream\uc744 \uc774\uc6a9\ud574\uc11c gRPC stream\uc744 \uad6c\ud604.\ntype StockStream = Pin&lt;Box&lt;dyn Stream&lt;Item = Result&lt;StockPriceResponse, Status>> + Send>>;\n\n#[tonic::async_trait]\nimpl StockService for StockServiceImpl {\n    type UpdateStockPriceStream = StockStream;\n\n    async fn update_stock_price(\n        &amp;self,\n        _request: Request&lt;()>,\n    ) -> Result&lt;Response&lt;Self::UpdateStockPriceStream>, Status> {\n\n        \/\/ 1\ucd08 \uac04\uaca9\uc73c\ub85c stock price \uc0dd\uc131\n        let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(\n            Duration::from_secs(1),\n        ));\n\n        \/\/ Stream\uc73c\ub85c \uc804\uc1a1\ud560 \ub370\uc774\ud130\ub97c \uc0dd\uc131\ud558\uace0 yield\ub85c \ubc18\ud658.\n        let output = async_stream::stream! {\n            let mut rng = SmallRng::from_os_rng();\n            let mut sequence: u64 = 0;\n\n            \/\/ \ub9e4 interval\ub9c8\ub2e4 \uc21c\ubc88\uc744 \uc99d\uac00\uc2dc\ud0a4\uace0 \ub79c\ub364 \uc8fc\uac00\ub97c \uc0dd\uc131\ud574 \ud074\ub77c\uc774\uc5b8\ud2b8\ub85c \uc804\uc1a1\n            while interval.next().await.is_some() {\n                sequence += 1;\n                let p = generate_stock_price(&amp;mut rng, sequence);\n                yield Ok(p);\n            }\n        };\n\n        Ok(Response::new(\n            Box::pin(output) as Self::UpdateStockPriceStream\n        ))\n    }\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\uc774 \ucf54\ub4dc\uc5d0\uc11c \ud575\uc2ec\uc740 <code>async_stream::stream!()<\/code> \ub9e4\ud06c\ub85c\ub85c \ub370\uc774\ud130\ub97c \uc804\uc1a1\ud560 \ub370\uc774\ud130\uc784\uc744 \uc815\uc758\ud558\uace0, <code>while \/ yeild<\/code>\ub85c data\ub97c \uc804\uc1a1\ud558\ub294 \ub2e4\uc74c\uc758 \ubd80\ubd84\uc774\ub2e4. \uc774 \ubd80\ubd84\uc5d0\uc11c \ub370\uc774\ud130\ub97c \uac31\uc2e0\ud558\uace0 stream\uc5d0 \uc9c0\uc18d\uc801\uc73c\ub85c update \uc2dc\ud0a8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">        let output = async_stream::stream! {\n           ...\n\n            \/\/ \ub9e4 interval\ub9c8\ub2e4 \uc21c\ubc88\uc744 \uc99d\uac00\uc2dc\ud0a4\uace0 \ub79c\ub364 \uc8fc\uac00\ub97c \uc0dd\uc131\ud574 \ud074\ub77c\uc774\uc5b8\ud2b8\ub85c \uc804\uc1a1\n            while interval.next().await.is_some() {\n                \/\/ Data \uc0dd\uc131 \ubc0f \uc804\uc1a1\n                ...\n                yield Ok(p);\n            }\n        };<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Client<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Server\uc5d0 \ube44\ud574 Client \ucabd\uc740 \ube44\uad50\uc801 \uad6c\ud604\uc774 \uac04\ub2e8\ud55c\ub370, <code>prost<\/code>\uac00 proto file\ub85c \ubd80\ud130 \uc0dd\uc131\ud574 \uc900 <code>StockServiceClient<\/code>\ub97c \uc774\uc6a9\ud574\uc11c stream\uc744 \uc0dd\uc131\ud558\uace0, \uc774 \ub54c \ubc18\ud658\ubc1b\uc740 <code>stream<\/code>\uc73c\ub85c \ubd80\ud130 \uacc4\uc18d \ub370\uc774\ud130\ub97c \uc77d\uc5b4 \uc8fc\uba74\ub41c\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#[tokio::main]\nasync fn main() -> Result&lt;(), Box&lt;dyn Error>> {\n    let mut client = StockServiceClient::connect(\"http:\/\/[::1]:50051\").await?;\n\n    \/\/ grpc \uc11c\ube44\uc2a4 stream \uc0dd\uc131.\n    let mut stream = client\n        .update_stock_price(Request::new(()))\n        .await?\n        .into_inner();\n\n    \/\/ stream\uc5d0\uc11c \ub370\uc774\ud130\ub97c \ubc1b\uc544\uc11c \ud654\uba74\uc5d0 \ucd9c\ub825.\n    while let Some(update) = stream.message().await? {\n        println!(\n            \"[{}] {}: {:.2}\",\n            update.sequence, update.symbol, update.price\n        );\n    }\n\n    Ok(())\n}\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ucf54\ub4dc \uc704\uce58 \ubc0f \uc218\ud589\uacb0\uacfc<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\uc804\uccb4 \uad6c\ud604 \ucf54\ub4dc\ub294 \ub2e4\uc74c \uc704\uce58\uc5d0 \uc788\ub2e4. <a href=\"https:\/\/github.com\/litcoder\/grpcobsr\/tree\/stream_rust\">https:\/\/github.com\/litcoder\/grpcobsr\/tree\/stream_rust<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"865\" height=\"636\" src=\"https:\/\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png\" alt=\"\" class=\"wp-image-4622\" srcset=\"https:\/\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png 865w, https:\/\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM-300x221.png 300w, https:\/\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM-768x565.png 768w, https:\/\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM-624x459.png 624w\" sizes=\"auto, (max-width: 865px) 100vw, 865px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>gRPC\uc5d0\uc11c Observer pattern\uc744 \uad6c\ud604\ud558\ub294 \uc608\uc81c\ub97c \uace0\ubbfc\ud588\ub358 \uc801\uc774 \uc788\uc5c8\ub294\ub370(gRPC\ub97c \uc774\uc6a9\ud55c Observer Pattern) \uc54c\uace0\ubcf4\ub2c8 gRPC\uc5d0\uc11c\ub294 ReadReactor \/ WriteReactor \ubcf4\ub2e4 \uac04\ub2e8\ud558\uac8c object\uc758 \uc0c1\ud0dc \ubcc0\uacbd\uc744 \uac10\uc9c0\ud560 \uc218 \uc788\ub294 \uba54\ucee4\ub2c8\uc998\uc744 \uc81c\uacf5\ud558\uace0 \uc788\uc5c8\ub2e4. gRPC\ub97c \uc774\uc6a9\ud55c Observer pattern \uad6c\ud604 \uc608\uc81c\ub97c \ucc3e\uae30 \uc5b4\ub824\uc6e0\ub358 \uc774\uc720\uac00 \uc5b4\uca4c\uba74 \uadf8\ub7f0 \ubcf5\uc7a1\ud55c \ud328\ud134 \uc5c6\uc774\ub3c4 \uad6c\ud604\uc774 \uac00\ub2a5\ud558\uae30 \ub54c\ubb38\uc774\uc5c8\ub294\uc9c0\ub3c4 \ubaa8\ub974\uaca0\ub2e4. \uc774 \ud3ec\uc2a4\ud305\uc5d0\uc11c\ub294 Stream\uc744 \ud1b5\ud574 \ubcc0\uacbd\ub418\ub294 \uac12\ub4e4\uc744 subscribe\ud558\ub294 \uac04\ub2e8\ud55c \uc608\uc81c\ub97c Rust\ub85c [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[5],"tags":[330,423,456],"class_list":["post-3939","post","type-post","status-publish","format-standard","hentry","category-programming","tag-grpc","tag-rust","tag-stream"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3939","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3939"}],"version-history":[{"count":16,"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3939\/revisions"}],"predecessor-version":[{"id":4640,"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3939\/revisions\/4640"}],"wp:attachment":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3939"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3939"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3939"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}