شروع با gRPC-Python - استریم

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 را ایجاد کردیم. شامل:

  1. کامپایلر پروتک معمولی که کد پایتون را از تعاریف message تولید می کند.
  2. پلاگین 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 تعریف کردیم ایجاد می‌کند:

  1. route_guide_pb2.py حاوی کدی است که به صورت پویا کلاس های تولید شده از تعاریف message را ایجاد می کند.
  2. route_guide_pb2.pyi یک "فایل خرد" یا "نوع فایل راهنمایی" است که از تعاریف message ایجاد شده است. فقط شامل امضاهایی است که هیچ اجرائی ندارند. فایل‌های Stub را می‌توان توسط IDEها برای تکمیل خودکار و تشخیص خطا بهتر استفاده کرد.
  3. route_guide_pb2_grpc.py از تعاریف service تولید می شود و شامل کلاس ها و توابع خاص gRPC است.

کد مخصوص gRPC شامل:

  1. RouteGuideStub ، که می تواند توسط یک کلاینت gRPC برای فراخوانی RouteGuide RPC استفاده شود.
  2. RouteGuideServicer ، که رابط را برای پیاده سازی سرویس RouteGuide تعریف می کند.
  3. تابع 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. بعدی چه خواهد شد