{"id":3509,"date":"2024-10-14T08:24:00","date_gmt":"2024-10-13T23:24:00","guid":{"rendered":"http:\/\/43.203.250.216\/?p=3509"},"modified":"2026-07-08T20:05:13","modified_gmt":"2026-07-08T11:05:13","slug":"grpc%eb%a5%bc-%ec%9d%b4%ec%9a%a9%ed%95%9c-observer-pattern","status":"publish","type":"post","link":"https:\/\/litcoder.com\/?p=3509","title":{"rendered":"gRPC\ub97c \uc774\uc6a9\ud55c Observer Pattern"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Remote\uc5d0\uc11c \uc81c\uacf5\ud558\ub294 \uae30\ub2a5\uc744 \ub9c8\uce58 local system\uc758 function call \ucc98\ub7fc \uc81c\uacf5 \ud55c\ub2e4\ub294 gRPC\uc758 \uac1c\ub150 \uc790\uccb4\ub294 1990\ub144\ub300\uc5d0\ub3c4 \uc788\uc5c8\ub358 \uac83\uc774\uae30\uc5d0 \uc0c8\ub85c\uc6b8 \uac83\uc740 \uc5c6\uc9c0\ub9cc, \uadf8 \uc704\uc5d0\uc11c &#8220;Subject\uc758 \ubcc0\uacbd\uc774 \uc788\uc744 \ub54c subscriber\uc5d0\uac8c notify \ud574\uc8fc\ub294 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Observer_pattern\" data-type=\"link\" data-id=\"https:\/\/en.wikipedia.org\/wiki\/Observer_pattern\">Observer Pattern<\/a>\uc744 \uc5b4\ub5bb\uac8c \uad6c\ud604 \ud560 \uc218 \uc788\uc744\uae4c?&#8221;\ud558\ub294 \uc758\ubb38\uc774 \ub4e4\uc5c8\ub2e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\uc774 \ud3ec\uc2a4\ud305\uc5d0\uc11c\ub294 gRPC\ub97c \uc774\uc6a9\ud55c observer pattern\uc758 \uc608\uc81c\ub85c server\uc5d0\uc11c \uc784\uc758\uc758 \uc8fc\uc2dd\uacfc \uadf8 \ubcc0\ub3d9 \uac00\uaca9\uc744 client\ub85c notify \ud574\uc8fc\uace0 \uc774\uac83\uc744 \ud654\uba74\uc5d0 \ucd9c\ub825\ud558\ub294 \uc608\uc81c\ub97c \uc791\uc131\ud574 \ubcf8\ub2e4. \uc804\uccb4 \ucf54\ub4dc\ub294 <a href=\"https:\/\/github.com\/litcoder\/grpcobsr\/tree\/obsr_cpp\">https:\/\/github.com\/litcoder\/grpcobsr\/tree\/obsr_cpp<\/a>\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\uae30\ubcf8\uc801\uc778 gRPC\ub294 client\uc5d0\uc11c \ud544\uc694\ud55c \uc815\ubcf4\ub97c server\uc5d0\uac8c \uc694\uccad\ud574\uc11c \uadf8 \uacb0\uacfc\ub97c \ub3cc\ub824\ubc1b\ub294\ub2e4. \ubc18\uba74, Observer Pattern\uc740 \uadf8 \ubc18\ub300\ub85c server \uce21\uc5d0\uc11c \uc815\ubcf4\uc758 \ubcc0\uacbd \uc0ac\ud56d\uc774 \uc788\uc744 \ub54c \uc774\uac83\uc744 client \uce21\uc5d0 \uc54c\ub824 \uc8fc\uc5b4\uc57c \ud55c\ub2e4.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/thumb\/a\/a8\/Observer_w_update.svg\/500px-Observer_w_update.svg.png\" alt=\"\"\/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">\uc774\ub97c \uac00\ub2a5\ud558\uac8c \ud558\ub294 \uac83\uc740 <a href=\"https:\/\/grpc.github.io\/grpc\/cpp\/classgrpc_1_1_server_write_reactor.html\">ServerWriteRector<\/a>\uc640 <a href=\"https:\/\/grpc.github.io\/grpc\/cpp\/classgrpc_1_1_client_read_reactor.html\">ClientReadReactor<\/a> template\uc778\ub370, \uc774\ub4e4\uc740 client\uc758 \uc694\uccad\uc5d0 \ub300\ud574 server\uac00 \uc5ec\ub7ec \uac1c\uc758 \uc751\ub2f5\uc744 \ube44\ub3d9\uae30\uc801\uc73c\ub85c \uc804\uc1a1\ud558\ub294 \uc774\ub978\ubc14 gRPC\uc758 server-side streaming\uc744 \uad6c\ud604\ud558\ub294\ub370 \uac00\uc7a5 \ud575\uc2ec\uc774 \ub418\ub294 \uc694\uc18c\ub4e4\uc774\ub2e4. \uc6b0\ub9ac \uc608\uc81c\uc758 \uacbd\uc6b0 client\ub294 server\ub85c &#8220;\uc8fc\uc2dd \uac00\uaca9\uc744 \uc54c\ub824 \uc8fc\uc138\uc694&#8221;\ub77c\ub294 request\ub97c \uc804\uc1a1\ud558\uba74 ClientReadReactor\uc758 OnReadDone() \ud568\uc218\ub85c \ubcc0\uacbd\ub41c \uc8fc\uc2dd\uacfc \uac00\uaca9\uc774 \ud558\ub098\uc529 \ub4e4\uc5b4\uc624\ub294 \uc2dd\uc774\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Proto file<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\uc8fc\uc2dd \uc815\ubcf4\ub97c \uc81c\uacf5\ud558\ub294 proto file\uc740 \ub2e4\uc74c\uacfc \uac19\uc774 \uc815\uc758 \ud55c\ub2e4. \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 UpdateStockPrice()\ub97c \uc694\uccad\ud558\uba74 \uc11c\ubc84\uac00 StockPriceResponse\ub97c \uc5ec\ub7ec \uac1c stream\uc73c\ub85c \uc804\uc1a1\ud574 \uc8fc\ub294\ub370 \uadf8 \uc548\uc5d0\ub294 \uac01\uac01\uc758 \uc8fc\uc2dd\uc758 \uc885\ubaa9(symbol)\uacfc \uac00\uaca9(price) \uc815\ubcf4\uac00 \ub2f4\uaca8\uc838 \uc788\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\";\nimport \"google\/protobuf\/empty.proto\";\n\nmessage StockPriceResponse {\n    string symbol = 1;\n    double price = 2;\n}\n\nservice StockService {\n    rpc UpdateStockPrice(google.protobuf.Empty) returns (stream StockPriceResponse);\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">StockPriceWriteReactor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/litcoder\/grpcobsr\/blob\/main\/src\/writereactor.cpp\" data-type=\"link\" data-id=\"https:\/\/github.com\/litcoder\/grpcobsr\/blob\/main\/src\/writereactor.cpp\">StockPriceWriteReactor<\/a>\ub294 \uc11c\ubc84\uc5d0\uc11c \ub3d9\uc791\ud558\ub294 reactor\uc774\ub2e4. <\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class StockPriceWriteReactor\n    : public ::grpc::ServerWriteReactor&lt;::StockPriceResponse>\n{\npublic:\n  StockPriceWriteReactor(int evCnt);\n\n  void OnWriteDone(bool ok) override;\n  void OnDone() override;\n  void OnCancel() override;\n\nprivate:\n  void NextWrite();\n\n  int _mReqEventCount;\n  int _mCurEventCount;\n  StockPriceResponse _mResp;\n  StockRepository _mStockRepo;\n};<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>NextWrite():<\/strong> \ud074\ub77c\uc774\uc5b8\ud2b8\ub85c \uc804\uc1a1\ud560 \ub370\uc774\ud130\ub97c \uc0dd\uc131\ud558\uace0 stream\uc5d0 \uc4f4\ub2e4.<\/li>\n\n\n\n<li><strong>OnWriteDone():<\/strong> NextWrite()\uc5d0\uc11c \ud638\ucd9c\ud558\ub294 StartWrite()\uc5d0 \uc758\ud574 \ud558\ub098\uc758 \uc815\ubcf4\uac00 \uc4f0\uc5ec\uc84c\uc744 \ub54c \ud638\ucd9c\ub418\ub294 callback\uc774\ub2e4. \uc774\uc804\uc758 \uc804\uc1a1\uc774 \uc131\uacf5\uc801\uc73c\ub85c \ub418\uc5c8\ub294\uc9c0 \uac80\uc0ac\ud558\uace0 \ub2e4\uc74c \uc815\ubcf4\ub97c \uc804\uc1a1\ud55c\ub2e4.<\/li>\n\n\n\n<li><strong>OnDone():<\/strong> Stream \uc804\uc1a1 \uc644\ub8cc\ub97c \uc758\ubbf8\ud558\ub294 Finish()\ub97c \ud638\ucd9c\ud558\uba74 \ubd88\ub9ac\ub294 callback\uc774\ub2e4. \ud574\ub2f9 \uc778\uc2a4\ud134\uc2a4\uc758 \uc0ac\uc6a9\uc774 \uc885\ub8cc\ub418\uc5c8\ub2e4\ub294 \uc758\ubbf8\uc774\ubbc0\ub85c \uba54\ubaa8\ub9ac\ub97c \ud574\uc81c\ud55c\ub2e4.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Server \uad6c\ud604<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class StockServiceImpl final : public StockService::CallbackService\n{\npublic:\n  ...\n  ServerWriteReactor&lt;::StockPriceResponse> *UpdateStockPrice(\n      CallbackServerContext *context,\n      const google::protobuf::Empty *empty) override\n  {\n    return new StockPriceWriteReactor(_mEventCount);\n  }\n  ...\n};\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">gRPC \ube44\ub3d9\uae30 \ud638\ucd9c\uc744 \uc704\ud55c \uc11c\ubc84\ub294 StockService::Server\uac00 \uc544\ub2cc <strong>StockService::CallbackService<\/strong>\ub97c \uc0c1\uc18d\ubc1b\uc544 \uad6c\ud604\ud55c\ub2e4. StockService::Server\uac00 write stream\uc5d0 \uc804\uc1a1\ud560 \ub0b4\uc6a9\uc744 \uc4f0\uace0 grpc::Status\ub97c \ubc18\ud658 \ud558\ub3c4\ub85d \ud558\ub294 \uac83\uacfc \ub2ec\ub9ac CallbackService\ub294 \uc55e\uc11c \uc815\uc758\ud55c ServerWriteReactor&lt;StockPriceResponse&gt;* type\uc744 \ubc18\ud658 \ud558\ub3c4\ub85d \uc815\uc758 \ub418\uc5b4\uc788\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">StockPriceReadReactor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/litcoder\/grpcobsr\/blob\/main\/src\/readreactor.cpp\">StockPriceReadReactor<\/a>\ub294 \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0\uc11c \ub3d9\uc791\ud558\ub294 reactor\uc774\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class StockPriceReadReactor\n    : public ::grpc::ClientReadReactor&lt;::StockPriceResponse>\n{\npublic:\n  StockPriceReadReactor(\n      std::shared_ptr&lt;StockService::Stub> stub, std::shared_ptr&lt;Publisher> pub);\n  void OnReadDone(bool ok) override;\n  void OnDone(const ::grpc::Status &amp;s) override;\n  ::grpc::Status Await();\n\nprivate:\n  std::shared_ptr&lt;Publisher> _mPub;\n  ::grpc::ClientContext _mContext;\n  ::StockPriceResponse _mResp;\n\n  std::mutex _mMtx;\n  std::condition_variable _mCondVar;\n  ::grpc::Status _mStatus;\n  bool _mAllDone = false;\n};<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>OnReadDone(): <\/strong>StartRead() \ud638\ucd9c\uc744 \ud1b5\ud574 \uc11c\ubc84\ub85c \ubd80\ud130 \ud558\ub098\uc758 record\uc778 \uc8fc\uc2dd \uc815\ubcf4 \uc5c5\ub370\uc774\ud2b8\ub97c \ubc1b\uc744 \ub54c \ub9c8\ub2e4 \ud638\ucd9c\ub418\ub294 callback\uc774\ub2e4. \uc774\uac83\uc744 \uc774\uc6a9\ud574 Observer pattern\uc5d0\uc11c event publisher\uac00 \uc790\uc2e0\uc5d0\uac8c \ub4f1\ub85d\ub41c subscriber\ub4e4\uc5d0\uac8c event\ub97c notify\ud558\ub294 \ucf54\ub4dc\ub97c \uad6c\ud604\ud560 \uc218 \uc788\ub2e4.<\/li>\n\n\n\n<li><strong>OnDone(): <\/strong>\uc11c\ubc84\ub85c \ubd80\ud130 stream \uc885\ub8cc\ub97c \ubc1b\uc73c\uba74 \ud638\ucd9c\ub418\ub294 callback\uc774\ub2e4. Await()\uacfc \uacf5\uc720\ub418\ub294 condition_variable\uc744 \uc774\uc6a9\ud574 process\uac00 \uc885\ub8cc \ub420 \uc218 \uc788\ub3c4\ub85d \ud55c\ub2e4.<\/li>\n\n\n\n<li><strong>Await(): <\/strong>\ube44\ub3d9\uae30 \ud638\ucd9c\uc740 multi threading\uc744 \uc804\uccb4 \ud558\ubbc0\ub85c, \uc774 \ud568\uc218\ub294 \uc11c\ubc84\ub85c \ubd80\ud130 \ubc1b\ub294 stream\uc774 \uc885\ub8cc\ub420 \ub54c\uae4c\uc9c0 main thread\uac00 \uc885\ub8cc\ub418\uc9c0 \uc54a\uace0 \uc720\uc9c0 \ub418\ub3c4\ub85d \ud574\uc900\ub2e4.<\/li>\n\n\n\n<li><strong>mutext\uc640 condition_variable: <\/strong>\uc704\uc5d0\uc11c \uc124\uba85\ud55c OnDone()\uacfc Await()\uc774 thread control\uc744 \ud560 \uc218 \uc788\ub3c4\ub85d \ud574\uc8fc\ub294 \ub3d9\uae30\ud654 \ubcc0\uc218 \ub4e4\uc774\ub2e4.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Client \uad6c\ud604<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class StockClient\n{\npublic:\n  ...\n  void updateStockPrice()\n  {\n    StockPriceReadReactor reader(_mStub, _mPub);\n    Status status = reader.Await();\n    if (status.ok())\n    {\n      spdlog::info(\"PriceListing succeed.\");\n    }\n    else\n    {\n      spdlog::error(\"Failed to get prices.\");\n      spdlog::error(\"{}({})\", status.error_message(), status.error_code());\n    }\n  }\n  ...\n};<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\ud074\ub77c\uc774\uc5b8\ud2b8 \ucf54\ub4dc\ub294 StockPriceReadReactor\uc758 instance\ub97c \ub9cc\ub4e4\uace0 Await()\uc744 \ud638\ucd9c\ud574\uc11c \uc11c\ubc84\ub85c \ubd80\ud130 \uc804\uc1a1\uc774 \uc644\ub8cc\ub418\uae30\ub97c \uae30\ub2e4\ub9b0\ub2e4. \uadf8\ub7fc \uc11c\ubc84\ucabd\uc73c\ub85c &#8220;\uc8fc\uc2dd \uac00\uaca9 \uc8fc\uc138\uc694&#8221;\ub77c\ub294 request\ub294 \ub204\uac00 \ub0a0\ub9ac\ub0d0\uace0? StockPriceReadReactor\uc758 \uc0dd\uc131\uc790\uc5d0 \ub2e4\uc74c\uacfc \uac19\uc774 stub\uc5d0 UpdateStockPrice()\ub97c \ud638\ucd9c\ud558\ub294 \ubd80\ubd84\uc774 \uc815\uc758\ub418\uc5b4 \uc788\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">StockPriceReadReactor::StockPriceReadReactor(\n    std::shared_ptr&lt;StockService::Stub> stub, std::shared_ptr&lt;Publisher> pub)\n    : _mPub(pub)\n{\n\n  ::google::protobuf::Empty empty;\n  stub->async()->UpdateStockPrice(&amp;_mContext, &amp;empty, this);\n  StartRead(&amp;_mResp);\n  StartCall();\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ub3d9\uc791 \ud655\uc778<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Code repo: <a href=\"https:\/\/github.com\/litcoder\/grpcobsr\/tree\/obsr_cpp\">https:\/\/github.com\/litcoder\/grpcobsr\/tree\/obsr_cpp<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"898\" height=\"603\" src=\"https:\/\/litcoder.com\/wp-content\/uploads\/2024\/10\/grpc_observer.png\" alt=\"\" class=\"wp-image-3511\" srcset=\"https:\/\/litcoder.com\/wp-content\/uploads\/2024\/10\/grpc_observer.png 898w, https:\/\/litcoder.com\/wp-content\/uploads\/2024\/10\/grpc_observer-300x201.png 300w, https:\/\/litcoder.com\/wp-content\/uploads\/2024\/10\/grpc_observer-768x516.png 768w, https:\/\/litcoder.com\/wp-content\/uploads\/2024\/10\/grpc_observer-624x419.png 624w\" sizes=\"auto, (max-width: 898px) 100vw, 898px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">References<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/sultanov.dev\/blog\/grpc-long-lived-streaming-using-observer-pattern\/\">gRPC Long-lived Streaming using Observer Pattern<\/a>: Java\ub85c gRPC\uc758 Observer pattern\uc744 \uad6c\ud604\ud55c \ub0b4\uc6a9\uc744 \uc124\uba85\ud55c \uae00\uc774\ub2e4. \uc0ac\uc2e4 \ubcf8 \ud3ec\uc2a4\ud305\uc758 \uc2dc\uc791\ub3c4 \uc6d0\ub798\ub294 \uc774 \uad6c\ud604\uc744 C++\ub85c \ubcc0\uacbd\ud574 \ubcf4\uace0\uc790 \ud558\ub294 \uc758\ub3c4\uc600\uc73c\ub098 \uc548\ud0c0\uae5d\uac8c\ub3c4 C++\uc5d0\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\ub294 \uc758\uc874\uc131 \ub54c\ubb38\uc5d0 \ub9ce\uc740 \ubd80\ubd84\uc744 \uc0c8\ub86d\uac8c \uc791\uc131\ud574\uc57c \ud588\ub2e4.<\/li>\n\n\n\n<li><a href=\"https:\/\/grpc.io\/docs\/languages\/cpp\/callback\/\">Asynchronous Callback API Tutorial<\/a>: gPRC\uc5d0 \ub300\ud55c \ube44\ub3d9\uae30 \ud638\ucd9c\uc5d0 \ub300\ud574 \uc608\uc81c\ub97c \ud3ec\ud568\ud574\uc11c \ub9e4\uc6b0 \uc790\uc138\ud788 \uc124\uba85\ud55c \uae00\uc774\ub2e4. Observer pattern\uc744 \uc9c1\uc811 \uc5b8\uae09\ud558\uace0 \uc788\uc9c0\ub294 \uc54a\uc73c\ub098 \ud65c\uc6a9\ub3c4\uac00 \ub192\uc740 Unary, Server-side streaming, Client-side streaming \uadf8\ub9ac\uace0 Bidirectional streaming\uc744 \uc124\uba85\ud55c\ub2e4.<\/li>\n\n\n\n<li><a href=\"https:\/\/grpc.github.io\/grpc\/cpp\/\">gRPC API reference<\/a>: API\ub4e4\uc5d0 \ub300\ud55c \uc124\uba85\uc744 \ucc3e\uc544 \ubcfc \uc218 \uc788\ub2e4. \uce5c\uc808\ud558\uac8c \uc124\uba85\ub41c \ubb38\uc11c\ub294 \uc544\ub2c8\uc9c0\ub9cc \uadf8\ub798\ub3c4 \uc5c6\ub294 \uac83 \ubcf4\ub2e4\ub294 \ubb50&#8230;<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Remote\uc5d0\uc11c \uc81c\uacf5\ud558\ub294 \uae30\ub2a5\uc744 \ub9c8\uce58 local system\uc758 function call \ucc98\ub7fc \uc81c\uacf5 \ud55c\ub2e4\ub294 gRPC\uc758 \uac1c\ub150 \uc790\uccb4\ub294 1990\ub144\ub300\uc5d0\ub3c4 \uc788\uc5c8\ub358 \uac83\uc774\uae30\uc5d0 \uc0c8\ub85c\uc6b8 \uac83\uc740 \uc5c6\uc9c0\ub9cc, \uadf8 \uc704\uc5d0\uc11c &#8220;Subject\uc758 \ubcc0\uacbd\uc774 \uc788\uc744 \ub54c subscriber\uc5d0\uac8c notify \ud574\uc8fc\ub294 Observer Pattern\uc744 \uc5b4\ub5bb\uac8c \uad6c\ud604 \ud560 \uc218 \uc788\uc744\uae4c?&#8221;\ud558\ub294 \uc758\ubb38\uc774 \ub4e4\uc5c8\ub2e4. \uc774 \ud3ec\uc2a4\ud305\uc5d0\uc11c\ub294 gRPC\ub97c \uc774\uc6a9\ud55c observer pattern\uc758 \uc608\uc81c\ub85c server\uc5d0\uc11c \uc784\uc758\uc758 \uc8fc\uc2dd\uacfc \uadf8 \ubcc0\ub3d9 \uac00\uaca9\uc744 client\ub85c notify \ud574\uc8fc\uace0 \uc774\uac83\uc744 [&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":[337,247,330,339,338],"class_list":["post-3509","post","type-post","status-publish","format-standard","hentry","category-programming","tag-async","tag-c","tag-grpc","tag-observer-pattern","tag-server-side-streaming"],"jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":3939,"url":"https:\/\/litcoder.com\/?p=3939","url_meta":{"origin":3509,"position":0},"title":"gRPC Streaming\uc73c\ub85c \uc0c1\ud0dc \uc804\uc1a1 \ubc1b\uae30 \uc608\uc81c","author":"litcoder","date":"2026\ub144 7\uc6d4 29\uc77c","format":false,"excerpt":"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\u2026","rel":"","context":"&quot;Programming&quot;\uc5d0\uc11c","block_context":{"text":"Programming","link":"https:\/\/litcoder.com\/?cat=5"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-10-at-6.34.33-PM.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":3457,"url":"https:\/\/litcoder.com\/?p=3457","url_meta":{"origin":3509,"position":1},"title":"CMake FetchContent\uc758 target\uc774 \uc911\ubcf5\ub418\ub294 \ubb38\uc81c","author":"litcoder","date":"2024\ub144 9\uc6d4 8\uc77c","format":false,"excerpt":"CMake\uc758 FetchContent\ub294 \ube4c\ub4dc \uacfc\uc815\uae4c\uc9c0 \ucc98\ub9ac\ud574 \uc8fc\uae30\uc5d0 git-submodule \ubcf4\ub2e4 \ud3b8\ub9ac\ud55c \ubc29\uc2dd\uc778 \uac83 \uac19\ub2e4. \ub2e4\ub9cc, version 3.22.1 \uae30\uc900\uc73c\ub85c \uc5ec\ub7ec \ud504\ub85c\uc81d\ud2b8 \ub4e4\uc774 \ub3d9\uc77c\ud55c target\uc774\ub984\uc744 \uc911\ubcf5\ud574\uc11c \uc120\uc5b8\ud558\ub294 \uacbd\uc6b0\uc5d0\ub294 \uc774\ub97c \uc798 \ucc98\ub9ac \ud558\uc9c0 \ubabb\ud558\uace0 \uc704\uc640 \uac19\uc740 \uc5d0\ub7ec\ub97c \ubc49\uace0 \uc885\ub8cc\ub418\ub294 \ubb38\uc81c\uac00 \uc788\ub2e4. -- Found systemd via pkg-config. CMake Error at build\/_deps\/grpc-src\/CMakeLists.txt:667 (add_custom_target): add_custom_target cannot create target\u2026","rel":"","context":"&quot;Tools &amp; Tips&quot;\uc5d0\uc11c","block_context":{"text":"Tools &amp; Tips","link":"https:\/\/litcoder.com\/?cat=6"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3558,"url":"https:\/\/litcoder.com\/?p=3558","url_meta":{"origin":3509,"position":2},"title":"VNC SSH Tunneling","author":"litcoder","date":"2025\ub144 8\uc6d4 18\uc77c","format":false,"excerpt":"Local VNC server\ub294 \uc811\uc18d\uc774 \uac00\ub2a5\ud558\uc9c0\ub9cc \uc678\ubd80\uc5d0\uc11c \uc811\uc18d\uc774 \ubd88\uac00\ub2a5 \ud558\ub2e4\uba74 SSH tunneling\uc744 \uc124\uc815\ud574\uc11c \uc5f0\uacb0\uc744 \uc2dc\ub3c4\ud560 \uc218 \uc788\ub2e4. VNC Client\uc5d0\uc11c SSH Tunneling\uc744 \uc9c0\uc6d0\ud558\ub294 \uacbd\uc6b0 SSH tunneling\uc744 \uc9c0\uc6d0\ud558\ub294 VNC client\uc778 Remmina\uc640 \uac19\uc740 \uacbd\uc6b0\uc5d0\ub294 \ub2e4\uc74c\uacfc \uac19\uc774 \uc11c\ubc84\ub85c localhost:5901\uc744 \uc124\uc815\ud55c\ub2e4. \uc5ec\uae30\uc5d0 \uc11c\ubc84 IP\uac00 \uc544\ub2c8\ub77c localhost\ub97c \uc801\uc5b4\uc8fc\ub294\uac8c \uc880 \uc774\uc0c1\ud574 \ubcf4\uc774\uae34 \ud558\uc9c0\ub9cc SSH tunneling\uc744 \uc124\uc815\ud55c \uc0c1\ud0dc\uc5d0\uc11c\ub294 localhost\uc758\u2026","rel":"","context":"&quot;Linux&quot;\uc5d0\uc11c","block_context":{"text":"Linux","link":"https:\/\/litcoder.com\/?cat=4"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2025\/08\/remmina_basic_setting.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2025\/08\/remmina_basic_setting.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2025\/08\/remmina_basic_setting.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2025\/08\/remmina_basic_setting.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":4673,"url":"https:\/\/litcoder.com\/?p=4673","url_meta":{"origin":3509,"position":3},"title":"Rust: pbjson crate\uc5d0\uc11c &#8220;redundant reference&#8221; \uacbd\uace0","author":"litcoder","date":"2026\ub144 8\uc6d4 26\uc77c","format":false,"excerpt":"\ub85c\uceec\uc5d0\uc11c lint \uac80\uc0ac \uae4c\uc9c0 \ub9c8\uce58\uace0 \uc11c\ubc84\ub85c push \ud588\ub294\ub370, \ub85c\uceec\uc5d0\uc11c \ubcf4\uc774\uc9c0 \uc54a\uc558\ub358 \"redundant referece\" \uacbd\uace0\uac00 \ub728\uba74\uc11c clippy\ub97c \uc218\ud589\ud558\ub294 Rust\uc6a9 CI\uac00 \uc2e4\ud328 \ud588\ub2e4. \ud604\uc7ac \ud504\ub85c\uc81d\ud2b8\uc5d0\uc11c Rust\uc6a9 CI\ub294 \uacbd\uace0\ub9cc \ub5a0\ub3c4 \uc2e4\ud328\ub85c \uac04\uc8fc\ud558\ub294 -D warnings \ud30c\ub77c\ubbf8\ud130\ub97c \uc124\uc815\ud558\uace0 \uc788\ub294\ub370 \uc774\uac83 \ub54c\ubb38\uc5d0 CI \uc804\uccb4\uac00 \uc2e4\ud328\ud558\ub294 \ubb38\uc81c\uac00 \uc0dd\uacbc\ub2e4. \ub85c\uceec\uacfc\uc758 \ucc28\uc774\uc810\uc744 \uc0b4\ud3b4 \ubcf4\ub2c8, local\uc5d0\uc11c\ub294 Cargo 1.94.1 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\ub294\ub370\u2026","rel":"","context":"&quot;Programming&quot;\uc5d0\uc11c","block_context":{"text":"Programming","link":"https:\/\/litcoder.com\/?cat=5"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/08\/sc_rust_redundant_reference.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/08\/sc_rust_redundant_reference.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/08\/sc_rust_redundant_reference.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2026\/08\/sc_rust_redundant_reference.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":718,"url":"https:\/\/litcoder.com\/?p=718","url_meta":{"origin":3509,"position":4},"title":"PlantUML war file \uc124\uce58","author":"litcoder","date":"2013\ub144 9\uc6d4 16\uc77c","format":false,"excerpt":"\uadf8\ub3d9\uc548 UML\uc744 \uadf8\ub824\uc57c\ud560 \ud544\uc694\uac00 \uc788\uc744\ub54c StarUML\uc744 \uc368\uc654\ub294\ub370, Windows\ub9cc \uc9c0\uc6d0\ud558\ub294 software\ub77c\ub294 \ud55c\uacc4\ub3c4 \uc788\uace0 \ud574\uc11c \uc5bc\ub9c8 \uc804 \ubd80\ud130\ub294 \uc6b0\uc5f0\ud788 \uc54c\uac8c\ub41c PlantUML\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\ub2e4. \uc774 software\ub294 \ub9c8\uce58 LaTex \ucc98\ub7fc text\ub85c UML\uc744 \uadf8\ub9ac\uae30 \uc704\ud55c description\uc744 \uc791\uc131\ud558\uba74 \uc774\ub97c \uae30\ubc18\ud574\uc11c UML\uc744 \uadf8\ub824\uc900\ub2e4. \ubb38\ubc95\ub3c4 \uadf8\ub2e4\uc9c0 \uc5b4\ub835\uc9c0 \uc54a\uace0 \uc9c1\uad00\uc801\uc774\uc5b4\uc11c \uc0ac\uc6a9\ud558\uae30\ub3c4 \uc26c\uc6b4 \ud3b8\uc774\ub2e4. \uc791\uc131\ud55c text\ub97c UML\ub85c \uadf8\ub9ac\uae30 \uc704\ud55c client\u2026","rel":"","context":"&quot;Linux&quot;\uc5d0\uc11c","block_context":{"text":"Linux","link":"https:\/\/litcoder.com\/?cat=4"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2013\/08\/SC_plantuml_server_page.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2013\/08\/SC_plantuml_server_page.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2013\/08\/SC_plantuml_server_page.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/litcoder.com\/wp-content\/uploads\/2013\/08\/SC_plantuml_server_page.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":33,"url":"https:\/\/litcoder.com\/?p=33","url_meta":{"origin":3509,"position":5},"title":"Repo mirroring","author":"litcoder","date":"2012\ub144 5\uc6d4 26\uc77c","format":false,"excerpt":"\ud55c\uad6d\uacfc \ubbf8\uad6d \ucc98\ub7fc \uac70\ub9ac\uc0c1\uc73c\ub85c \uba40\ub9ac \ub5a8\uc5b4\uc838 \uc788\ub294 \uacbd\uc6b0, \ud55c\ucabd\uc5d0\uc11c source\ub97c sync\ud558\uae30 \uc704\ud574\uc11c\ub294 \uc2dc\uac04\uc774 \ub108\ubb34 \ub9ce\uc774 \uac78\ub9b0\ub2e4. Mirror\ub97c \uc124\uc815\ud558\uace0 \uc8fc\uae30\uc801\uc73c\ub85c source\ub97c sync \ud574 \uc624\ub3c4\ub85d \ud558\uba74 \uc2dc\uac04\uacfc load\ub97c \uc904\uc77c \uc218 \uc788\ub2e4. \ub2e4\uc74c\uc740 Ubuntu 10.04 LTS \uae30\uc900\uc73c\ub85c\ud55c repo mirror server \uc124\uc815\ubc29\ubc95\uc5d0 \ub300\ud574 \uc124\uba85\ud55c\ub2e4. 1. Git daemon \uc124\uc815 : xinetd\ub97c \uc124\uce58\ud558\uace0 git-daemon\uc744 \uc124\uc815\ud55c\ub2e4. $>\u2026","rel":"","context":"&quot;Tools &amp; Tips&quot;\uc5d0\uc11c","block_context":{"text":"Tools &amp; Tips","link":"https:\/\/litcoder.com\/?cat=6"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3509","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=3509"}],"version-history":[{"count":10,"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3509\/revisions"}],"predecessor-version":[{"id":4619,"href":"https:\/\/litcoder.com\/index.php?rest_route=\/wp\/v2\/posts\/3509\/revisions\/4619"}],"wp:attachment":[{"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3509"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3509"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/litcoder.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3509"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}