1. مقدمه
در این کد لبه، شما از gRPC-Python برای ایجاد یک کلاینت و سرور استفاده خواهید کرد که پایه و اساس برنامه نقشه برداری مسیر نوشته شده در پایتون را تشکیل می دهد.
در پایان آموزش، شما یک کلاینت خواهید داشت که با استفاده از gRPC به یک سرور راه دور متصل می شود تا اطلاعاتی در مورد ویژگی های مسیر کلاینت به دست آورد، خلاصه ای از مسیر مشتری ایجاد کند و اطلاعات مسیر مانند به روز رسانی ترافیک را با سرور و سایر کلاینت ها مبادله کند.
این سرویس در یک فایل Protocol Buffers تعریف شده است که از آن برای تولید کد boilerplate برای کلاینت و سرور استفاده می شود تا بتوانند با یکدیگر ارتباط برقرار کنند و در زمان و تلاش شما در اجرای آن عملکرد صرفه جویی شود.
این کد تولید شده نه تنها از پیچیدگی های ارتباط بین سرور و کلاینت، بلکه سریال سازی و سریال سازی داده ها نیز مراقبت می کند.
چیزی که یاد خواهید گرفت
- نحوه استفاده از بافرهای پروتکل برای تعریف API سرویس.
- نحوه ساخت یک کلاینت و سرور مبتنی بر gRPC از تعریف بافرهای پروتکل با استفاده از تولید کد خودکار.
- درک ارتباط جریان مشتری-سرور با gRPC.
این کد لبه برای توسعه دهندگان Python که تازه به gRPC می پردازند یا به دنبال یک تجدید کننده gRPC هستند یا هر فرد دیگری که علاقه مند به ساختن سیستم های توزیع شده است، طراحی شده است. هیچ تجربه قبلی gRPC مورد نیاز نیست.
2. قبل از شروع
آنچه شما نیاز دارید
- پایتون 3.9 یا بالاتر. ما پایتون 3.13 را توصیه می کنیم. برای دستورالعملهای نصب پلتفرم خاص، تنظیمات و استفاده پایتون را ببینید. روش دیگر، نصب پایتون غیر سیستمی با استفاده از ابزارهایی مانند uv یا pyenv .
- pip برای نصب بسته های پایتون.
- venv برای ایجاد محیط های مجازی پایتون.
بسته های ensurepip
و venv
بخشی از کتابخانه استاندارد پایتون هستند و معمولاً به طور پیش فرض در دسترس هستند.
با این حال، برخی از توزیعهای مبتنی بر دبیان (از جمله اوبونتو) هنگام توزیع مجدد پایتون، آنها را حذف میکنند. برای نصب بسته ها، اجرا کنید:
sudo apt install python3-pip python3-venv
کد را دریافت کنید
برای سادهسازی یادگیری شما، این Codelab یک داربست کد منبع از پیش ساخته شده برای کمک به شما برای شروع ارائه میکند. مراحل زیر شما را در تکمیل برنامه، از جمله تولید کد gRPC با استفاده از افزونه کامپایلر grpc_tools.protoc
Protocol Buffer راهنمایی می کند.
grpc-codelabs
کد منبع داربست برای این کد لبه در دایرکتوری codelabs/grpc-python-streaming/start_here موجود است. اگر ترجیح می دهید خودتان کد را پیاده سازی نکنید، کد منبع تکمیل شده در فهرست completed
موجود است.
ابتدا پوشه کاری codelab و cd را در آن ایجاد کنید:
mkdir grpc-python-streaming && cd grpc-python-streaming
کد لبه را دانلود و استخراج کنید:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-python-streaming/start_here
یا می توانید فایل .zip را که فقط شامل دایرکتوری Codelab است دانلود کرده و به صورت دستی آن را از حالت فشرده خارج کنید.
3. پیام ها و خدمات را تعریف کنید
اولین قدم شما این است که سرویس gRPC برنامه، روش RPC آن، و انواع پیام درخواست و پاسخ آن را با استفاده از Protocol Buffers تعریف کنید. خدمات شما ارائه خواهد کرد:
- متدهای RPC به نام
ListFeatures
،RecordRoute
وRouteChat
که سرور پیاده سازی می کند و کلاینت فراخوانی می کند. - انواع پیام
Point
،Feature
،Rectangle
،RouteNote
وRouteSummary
که ساختارهای داده ای هستند که هنگام فراخوانی متدهای RPC بین مشتری و سرور رد و بدل می شوند.
این روشهای RPC و انواع پیام آنها همگی در فایل protos/route_guide.proto
کد منبع ارائه شده تعریف میشوند.
بافرهای پروتکل معمولاً به عنوان پروتوباف شناخته می شوند. برای اطلاعات بیشتر در مورد اصطلاحات gRPC، به مفاهیم اصلی، معماری و چرخه حیات gRPC مراجعه کنید.
تعریف انواع پیام
در فایل protos/route_guide.proto
کد منبع، ابتدا نوع پیام Point
را تعریف کنید. یک Point
نشان دهنده یک جفت مختصات طول و عرض جغرافیایی بر روی نقشه است. برای این کد، از اعداد صحیح برای مختصات استفاده کنید:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
اعداد 1
و 2
شماره های شناسه منحصر به فرد برای هر یک از فیلدهای ساختار message
هستند.
سپس نوع پیام Feature
را تعریف کنید. یک Feature
از یک فیلد string
برای نام یا آدرس پستی چیزی در مکانی مشخص شده توسط یک Point
استفاده می کند:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
برای اینکه چندین نقطه در یک منطقه را بتوان برای یک مشتری پخش کرد، به یک پیام Rectangle
نیاز دارید که نشان دهنده یک مستطیل طول و عرض جغرافیایی است که به صورت دو نقطه مورب مخالف lo
و hi
نمایش داده می شود:
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
همچنین، یک پیام RouteNote
که نشان دهنده پیامی است که در یک نقطه مشخص ارسال شده است:
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
در نهایت، به یک پیام RouteSummary
نیاز دارید. این پیام در پاسخ به یک RecordRoute
RPC دریافت می شود که در قسمت بعدی توضیح داده شده است. این شامل تعداد نقاط دریافتی جداگانه، تعداد ویژگی های شناسایی شده، و کل مسافت طی شده به عنوان مجموع تجمعی فاصله بین هر نقطه است.
message RouteSummary {
// The number of points received.
int32 point_count = 1;
// The number of known features passed while traversing the route.
int32 feature_count = 2;
// The distance covered in metres.
int32 distance = 3;
// The duration of the traversal in seconds.
int32 elapsed_time = 4;
}
روش های خدمات را تعریف کنید
برای تعریف یک سرویس، یک سرویس با نام را در فایل .proto
خود مشخص می کنید. فایل route_guide.proto
دارای یک ساختار service
به نام RouteGuide
است که یک یا چند روش ارائه شده توسط سرویس برنامه را تعریف می کند.
هنگامی که روش های RPC
را در تعریف سرویس خود تعریف می کنید، نوع درخواست و پاسخ آنها را مشخص می کنید. در این بخش از Codelab، اجازه دهید تعریف کنیم:
لیست ویژگی ها
اشیاء Feature
موجود در Rectangle
داده شده را به دست می آورد. نتایج بهجای بازگردانی یکباره پخش میشوند، زیرا مستطیل ممکن است منطقه بزرگی را پوشش دهد و دارای تعداد زیادی ویژگی باشد.
برای این برنامه، شما از یک RPC استریم سمت سرور استفاده خواهید کرد: کلاینت درخواستی را به سرور ارسال می کند و جریانی برای خواندن دنباله ای از پیام ها دریافت می کند. مشتری از جریان برگشتی می خواند تا زمانی که پیام دیگری وجود نداشته باشد. همانطور که در مثال ما می بینید، با قرار دادن کلمه کلیدی جریان قبل از نوع پاسخ، یک روش پخش سمت سرور را مشخص می کنید.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
جریانی از نقاط را در مسیری که در حال پیمایش است می پذیرد، پس از تکمیل پیمایش، یک RouteSummary
برمی گرداند.
یک RPC استریم سمت کلاینت در این مورد مناسب است: مشتری دنباله ای از پیام ها را می نویسد و آنها را دوباره با استفاده از جریان ارائه شده به سرور ارسال می کند. هنگامی که مشتری نوشتن پیام ها را به پایان رساند، منتظر می ماند تا سرور همه آنها را بخواند و پاسخ خود را برگرداند. شما با قرار دادن کلمه کلیدی جریان قبل از نوع درخواست، یک روش پخش سمت مشتری را مشخص می کنید.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
روت چت
جریانی از RouteNotes
را میپذیرد که هنگام عبور از یک مسیر، در حالی که RouteNotes
دیگر (مثلاً از سایر کاربران) دریافت میکند.
این دقیقاً همان موردی است که برای پخش جریانی دو طرفه استفاده می شود. یک جریان RPC دو طرفه که در آن هر دو طرف دنباله ای از پیام ها را با استفاده از جریان خواندن و نوشتن ارسال می کنند. این دو جریان به طور مستقل عمل میکنند، بنابراین کلاینتها و سرورها میتوانند به هر ترتیبی که دوست دارند بخوانند و بنویسند: به عنوان مثال، سرور میتواند قبل از نوشتن پاسخهایش منتظر دریافت همه پیامهای مشتری باشد، یا میتواند متناوب یک پیام را بخواند و سپس یک پیام بنویسد، یا ترکیب دیگری از خواندن و نوشتن. ترتیب پیام ها در هر جریان حفظ می شود. شما این نوع روش را با قرار دادن کلمه کلیدی جریان قبل از درخواست و پاسخ مشخص می کنید.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. کد کلاینت و سرور را تولید کنید
سپس، کد gRPC را برای مشتری و سرور از فایل .proto
با استفاده از کامپایلر بافر پروتکل تولید کنید.
برای تولید کد gRPC پایتون، ابزارهای grpcio را ایجاد کردیم. شامل:
- کامپایلر پروتک معمولی که کد پایتون را از تعاریف
message
تولید می کند. - پلاگین gRPC protobuf که کد پایتون (خرد مشتری و سرور) را از تعاریف
service
تولید می کند.
ما بسته پایتون grpcio-tools
با استفاده از pip نصب می کنیم. بیایید یک محیط مجازی پایتون جدید (venv) ایجاد کنیم تا وابستگی های پروژه شما را از بسته های سیستم جدا کنیم:
python3 -m venv --upgrade-deps .venv
برای فعال کردن محیط مجازی در پوسته bash/zsh:
source .venv/bin/activate
برای ویندوز و پوسته های غیر استاندارد، جدول را در https://docs.python.org/3/library/venv.html#how-venvs-work ببینید.
بعد، grpcio-tools را نصب کنید (این نیز بسته grpcio را نصب می کند):
pip install grpcio-tools
برای تولید کد دیگ بخار پایتون از دستور زیر استفاده کنید:
python -m grpc_tools.protoc --proto_path=./protos \
--python_out=. --pyi_out=. --grpc_python_out=. \
./protos/route_guide.proto
این فایلهای زیر را برای رابطهایی که در route_guide.proto
تعریف کردیم ایجاد میکند:
-
route_guide_pb2.py
حاوی کدی است که به صورت پویا کلاس های تولید شده از تعاریفmessage
را ایجاد می کند. -
route_guide_pb2.pyi
یک "فایل خرد" یا "نوع فایل راهنمایی" است که از تعاریفmessage
ایجاد شده است. فقط شامل امضاهایی است که هیچ اجرائی ندارند. فایلهای Stub را میتوان توسط IDEها برای تکمیل خودکار و تشخیص خطا بهتر استفاده کرد. -
route_guide_pb2_grpc.py
از تعاریفservice
تولید می شود و شامل کلاس ها و توابع خاص gRPC است.
کد مخصوص gRPC شامل:
-
RouteGuideStub
، که می تواند توسط یک کلاینت gRPC برای فراخوانی RouteGuide RPC استفاده شود. -
RouteGuideServicer
، که رابط را برای پیاده سازی سرویسRouteGuide
تعریف می کند. - تابع
add_RouteGuideServicer_to_server
که برای ثبت یکRouteGuideServicer
در سرور gRPC استفاده می شود.
5. سرور را ایجاد کنید
ابتدا اجازه دهید نحوه ایجاد سرور RouteGuide
را بررسی کنیم. ایجاد و اجرای سرور RouteGuide
به دو مورد تقسیم می شود:
- پیاده سازی رابط سرویس دهنده تولید شده از تعریف سرویس ما با توابعی که "کار" واقعی سرویس را انجام می دهند.
- اجرای یک سرور gRPC برای گوش دادن به درخواست های مشتریان و انتقال پاسخ ها.
بیایید به route_guide_server.py
نگاه کنیم.
RouteGuide را پیاده سازی کنید
route_guide_server.py
دارای یک کلاس RouteGuideServicer
است که کلاس ایجاد شده route_guide_pb2_grpc.RouteGuideServicer
طبقه بندی می کند:
# RouteGuideServicer provides an implementation of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):
RouteGuideServicer
تمام متدهای سرویس RouteGuide
پیاده سازی می کند.
RPC استریم سمت سرور
ListFeatures
یک RPC پاسخگو است که چندین Feature
را برای مشتری ارسال می کند:
def ListFeatures(self, request, context):
"""List all features contained within the given Rectangle."""
left = min(request.lo.longitude, request.hi.longitude)
right = max(request.lo.longitude, request.hi.longitude)
top = max(request.lo.latitude, request.hi.latitude)
bottom = min(request.lo.latitude, request.hi.latitude)
for feature in self.db:
lat, lng = feature.location.latitude, feature.location.longitude
if left <= lng <= right and bottom <= lat <= top:
yield feature
در اینجا پیام درخواست یک route_guide_pb2.Rectangle
است که مشتری می خواهد Feature
s را در آن پیدا کند. روش به جای برگرداندن یک پاسخ واحد، صفر یا بیشتر پاسخ می دهد.
جریان RPC سمت مشتری
روش درخواست جریان RecordRoute
از یک تکرار کننده مقادیر درخواست استفاده می کند و یک مقدار پاسخ واحد را برمی گرداند.
def RecordRoute(self, request_iterator, context):
"""Calculate statistics about the trip composed of Points."""
point_count = 0
feature_count = 0
distance = 0.0
prev_point = None
start_time = time.time()
for point in request_iterator:
point_count += 1
if get_feature(self.db, point):
feature_count += 1
if prev_point:
distance += get_distance(prev_point, point)
prev_point = point
elapsed_time = time.time() - start_time
return route_guide_pb2.RouteSummary(
point_count=point_count,
feature_count=feature_count,
distance=int(distance),
elapsed_time=int(elapsed_time),
)
جریان دو طرفه RPC
در نهایت، اجازه دهید به جریان دو طرفه RPC RouteChat()
خود نگاه کنیم:
def RouteChat(self, request_iterator, context):
"""
Receive a stream of message/location pairs, and responds with
a stream of all previous messages for the given location.
"""
prev_notes = []
for new_note in request_iterator:
for prev_note in prev_notes:
if prev_note.location == new_note.location:
yield prev_note
prev_notes.append(new_note)
معناشناسی این روش ترکیبی از روش درخواست – جریان و روش پاسخ – جریان است. به یک تکرار کننده مقادیر درخواست ارسال می شود و خود تکرار کننده مقادیر پاسخ است.
سرور را راه اندازی کنید
هنگامی که تمام روشهای RouteGuide
را پیادهسازی کردید، گام بعدی راهاندازی یک سرور gRPC است تا مشتریان بتوانند واقعاً از خدمات شما استفاده کنند:
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(),
server,
)
listen_addr = "localhost:50051"
server.add_insecure_port(listen_addr)
print(f"Starting server on {listen_addr}")
server.start()
server.wait_for_termination()
متد start()
سرور غیر مسدود کننده است. یک رشته جدید برای رسیدگی به درخواست ها ایجاد می شود. رشته فراخوانی server.start()
اغلب در این مدت کار دیگری برای انجام ندارد. در این مورد، میتوانید server.wait_for_termination()
را فراخوانی کنید تا تا زمانی که سرور خاتمه یابد، رشته فراخوانی را به طور تمیز مسدود کند.
6. مشتری ایجاد کنید
بیایید به route_guide_client.py
نگاه کنیم.
یک خرد ایجاد کنید
برای فراخوانی روش های سرویس، ابتدا باید یک خرد ایجاد کنیم.
ما کلاس RouteGuideStub
از ماژول route_guide_pb2_grpc
را که از .proto.
در متد run()
:
with grpc.insecure_channel("localhost:50051") as channel:
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
توجه داشته باشید که در اینجا channel
بهعنوان مدیر زمینه استفاده میشود و زمانی که مفسر بلوک with
را ترک کند، بهطور خودکار بسته میشود.
روش های خدمات تماس
برای روشهای RPC که یک پاسخ واحد را برمیگردانند (روشهای «پاسخ-یوناری»)، gRPC پایتون از معنایی جریان کنترل همزمان (مسدودکننده) و ناهمزمان (غیر مسدودکننده) پشتیبانی میکند. برای روشهای RPC جریانسازی پاسخ، فراخوانیها بلافاصله یک تکرارکننده از مقادیر پاسخ را برمیگردانند. بلوک متد next()
آن تکرار کننده را فراخوانی می کند تا زمانی که پاسخی که باید از تکرار کننده ارائه شود در دسترس باشد.
RPC استریم سمت سرور
فراخوانی ListFeatures
در جریان پاسخ شبیه به کار با انواع توالی است:
def guide_list_features(stub):
_lo = route_guide_pb2.Point(latitude=400000000, longitude=-750000000)
_hi = route_guide_pb2.Point(latitude=420000000, longitude=-730000000)
rectangle = route_guide_pb2.Rectangle(
lo=_lo,
hi=_hi,
)
print("Looking for features between 40, -75 and 42, -73")
features = stub.ListFeatures(rectangle)
for feature in features:
print(
f"Feature called '{feature.name}'"
f" at {format_point(feature.location)}"
)
جریان RPC سمت مشتری
فراخوانی RecordRoute
در جریان درخواست شبیه به ارسال یک تکرار کننده به یک روش محلی است. مانند RPC ساده بالا که یک پاسخ را نیز برمیگرداند، میتوان آن را به صورت همزمان فراخوانی کرد:
def guide_record_route(stub):
feature_list = route_guide_resources.read_route_guide_database()
route_iterator = generate_route(feature_list)
route_summary = stub.RecordRoute(route_iterator)
print(f"Finished trip with {route_summary.point_count} points")
print(f"Passed {route_summary.feature_count} features")
print(f"Traveled {route_summary.distance} meters")
print(f"It took {route_summary.elapsed_time} seconds")
جریان دو طرفه RPC
فراخوانی RouteChat
با جریان دوطرفه (همانطور که در سمت سرویس وجود دارد) ترکیبی از معنای جریان درخواست و پاسخ جریان دارد.
پیام های درخواست را ایجاد کنید و آنها را یکی یکی با استفاده از yield
ارسال کنید.
def generate_notes():
home = route_guide_pb2.Point(latitude=1, longitude=1)
work = route_guide_pb2.Point(latitude=2, longitude=2)
notes = [
make_route_note("Departing from home", home),
make_route_note("Arrived at work", work),
make_route_note("Having lunch at work", work),
make_route_note("Departing from work", work),
make_route_note("Arrived home", home),
]
for note in notes:
print(
f"Sending RouteNote for {format_point(note.location)}:"
f" {note.message}"
)
yield note
# Sleep to simulate moving from one point to another.
# Only for demonstrating the order of the messages.
time.sleep(0.1)
دریافت و پردازش پاسخ های سرور:
def guide_route_chat(stub):
responses = stub.RouteChat(generate_notes())
for response in responses:
print(
"< Found previous note at"
f" {format_point(response.location)}: {response.message}"
)
با روش های کمکی تماس بگیرید
در اجرا، متدهایی را که به تازگی ایجاد کردهایم اجرا کرده و به آنها stub
ارسال کنید.
print("-------------- ListFeatures --------------")
guide_list_features(stub)
print("-------------- RecordRoute --------------")
guide_record_route(stub)
print("-------------- RouteChat --------------")
guide_route_chat(stub)
7. آن را امتحان کنید
سرور را اجرا کنید:
python route_guide_server.py
از یک ترمینال دیگر، دوباره محیط مجازی را فعال کنید ( source .venv/bin/activate)
، سپس کلاینت را اجرا کنید:
python route_guide_client.py
بیایید نگاهی به خروجی بیاندازیم.
لیست ویژگی ها
ابتدا لیستی از ویژگی ها را پیدا خواهید کرد. هر ویژگی از سرور پخش می شود ( RPC جریان سمت سرور ) زیرا آنها را در مستطیل درخواستی کشف می کند:
-------------- ListFeatures -------------- Looking for features between 40, -75 and 42, -73 Feature called 'Patriots Path, Mendham, NJ 07945, USA' at (lat=407838351, lng=-746143763) Feature called '101 New Jersey 10, Whippany, NJ 07981, USA' at (lat=408122808, lng=-743999179) Feature called 'U.S. 6, Shohola, PA 18458, USA' at (lat=413628156, lng=-749015468) Feature called '5 Conners Road, Kingston, NY 12401, USA' at (lat=419999544, lng=-740371136) ...
RecordRoute
دوم، RecordRoute
فهرستی از نقاط بازدید شده بهطور تصادفی را که از مشتری به سرور پخش میشوند (RPC جریان سمت مشتری) نشان میدهد:
-------------- RecordRoute -------------- Visiting point (lat=410395868, lng=-744972325) Visiting point (lat=404310607, lng=-740282632) Visiting point (lat=403966326, lng=-748519297) Visiting point (lat=407586880, lng=-741670168) Visiting point (lat=406589790, lng=-743560121) Visiting point (lat=410322033, lng=-747871659) Visiting point (lat=415464475, lng=-747175374) Visiting point (lat=407586880, lng=-741670168) Visiting point (lat=402647019, lng=-747071791) Visiting point (lat=414638017, lng=-745957854)
پس از اینکه کلاینت پخش تمام نقاط بازدید شده را تمام کرد، یک پاسخ غیر جریانی ( یک RPC یکنواخت ) از سرور دریافت می کند. این پاسخ شامل خلاصه ای از محاسبات انجام شده در مسیر کامل مشتری خواهد بود.
Finished trip with 10 points Passed 10 features Traveled 654743 meters It took 0 seconds
روت چت
در آخر، خروجی RouteChat
جریان دو جهته را نشان می دهد. هنگامی که مشتری در حال "بازدید" از نقاط home
یا work
است، با ارسال RouteNote به سرور، یادداشتی را برای آن نقطه ثبت می کند. هنگامی که یک نقطه قبلاً بازدید شده است، سرور تمام یادداشت های قبلی را برای این نقطه بازگردانی می کند.
-------------- RouteChat -------------- Sending RouteNote for (lat=1, lng=1): Departing from home Sending RouteNote for (lat=2, lng=2): Arrived at work Sending RouteNote for (lat=2, lng=2): Having lunch at work < Found previous note at (lat=2, lng=2): Arrived at work Sending RouteNote for (lat=2, lng=2): Departing from work < Found previous note at (lat=2, lng=2): Arrived at work < Found previous note at (lat=2, lng=2): Having lunch at work Sending RouteNote for (lat=1, lng=1): Arrived home < Found previous note at (lat=1, lng=1): Departing from home
8. بعدی چه خواهد شد
- نحوه عملکرد gRPC را در مقدمه مفاهیم gRPC و Core بیاموزید
- از طریق آموزش اصول کار کنید
- مرجع Python API را کاوش کنید