انتقال‌ها و پروتکل‌ها

پیش‌گفتار

انتقال‌ها و پروتکل‌ها (Transports and Protocols) توسط APIهای سطح پایین حلقه رویداد مانند loop.create_connection() استفاده می‌شوند. آن‌ها از سبک برنامه‌نویسی مبتنی بر کال‌بک استفاده می‌کنند و پیاده‌سازی‌های با کارایی بالای پروتکل‌های شبکه یا IPC (مانند HTTP) را ممکن می‌سازند.

در اصل، انتقال‌هاو پروتکل‌ها تنها باید در کتابخانه‌ها و چارچوب‌ها استفاده شوند و هرگز در برنامه‌های asyncio سطح‌بالا استفاده نشوند.

این صفحه‌ی مستندات، هر دو Transports و Protocols را پوشش می‌دهد.

مقدمه

در بالاترین سطح، انتقال به چگونگی ارسال بایت‌ها مربوط است، در حالی که پروتکل تعیین می‌کند که کدام بایت‌ها ارسال شوند (و تا حدی چه زمانی).

راه دیگری برای بیان همان مطلب: یک انتقالانتزاعی از یک سوکت (یا پایانه ورودی/خروجی مشابه) است، در حالی که یک پروتکل، از دیدگاه انتقال، انتزاعی از یک برنامه است.

دیدگاه دیگر این است که رابط‌های انتقال و پروتکل با هم یک رابط انتزاعی برای استفاده از I/O شبکه و I/O بین‌فرایندی تعریف می‌کنند.

همیشه یک رابطه‌ی ۱:۱ بین اشیای انتقالو پروتکل وجود دارد: پروتکل متدهای انتقال را برای ارسال داده فراخوانی می‌کند، در حالی که انتقال متدهای پروتکل را فراخوانی می‌کند تا داده‌های دریافت‌شده را به آن منتقل کند.

بیشتر متدهای اتصال‌محور حلقه رویداد (مانند loop.create_connection()) معمولاً آرگومان protocol_factory را می‌پذیرند که برای ایجاد یک شیء Protocol برای یک اتصال پذیرفته‌شده که با یک شیء Transport نمایش داده می‌شود، استفاده می‌شود. چنین متدهایی معمولاً یک تاپل (transport, protocol) را برمی‌گردانند.

فهرست

این صفحه‌ی مستندات شامل بخش‌های زیر است:

انتقال‌ها

کد منبع: Lib/asyncio/transports.py


ترابرهاکلاس‌هایی هستند که توسط asyncio فراهم شده‌اند تا انواع مختلفی از کانال‌های ارتباطی را انتزاع کنند.

اشیای Transport همیشه توسط یک حلقه رویداد asyncio نمونه‌سازی می‌شوند.

asyncio انتقال‌هارا برای TCP، UDP، SSL و پایپ‌های subprocess پیاده‌سازی می‌کند. متدهای در دسترس برای یک انتقال، به نوع آن انتقال بستگی دارند.

کلاس‌های انتقال نخ‌ایمن نیستند.

سلسله‌مراتب ترابری‌ها

class asyncio.BaseTransport

کلاس پایه برای همه‌ی انتقال‌ها . شامل متدهایی است که همه‌ی انتقال‌ها در asyncio آن‌ها را به اشتراک می‌گذارند.

class asyncio.WriteTransport(BaseTransport)

یک ترابری پایه برای اتصال‌های فقط‌نوشتنی.

نمونه‌های کلاس WriteTransport از متد حلقه رویداد loop.connect_write_pipe() برگردانده می‌شوند و همچنین توسط متدهای مرتبط با زیرفرایند (subprocess) مانند loop.subprocess_exec() استفاده می‌شوند.

class asyncio.ReadTransport(BaseTransport)

یک انتقالپایه برای اتصال‌های فقط‌خواندنی.

نمونه‌های کلاس ReadTransport از متد حلقه رویداد loop.connect_read_pipe() بازگردانده می‌شوند و متدهای مرتبط با زیرفرایند مانند loop.subprocess_exec() نیز از آن‌ها استفاده می‌کنند.

class asyncio.Transport(WriteTransport, ReadTransport)

رابطی که یک انتقال دوطرفه، مانند اتصال TCP، را نشان می‌دهد.

کاربر یک انتقالرا مستقیماً نمونه‌سازی نمی‌کند؛ بلکه یک تابع کمکی را فراخوانی می‌کند و یک کارخانه‌ی پروتکل و سایر اطلاعات لازم برای ایجاد انتقالو پروتکل را به آن می‌دهد.

نمونه‌های کلاس Transport توسط متدهای حلقه رویداد مانند loop.create_connection()، loop.create_unix_connection()، loop.create_server()، loop.sendfile() و غیره برگردانده یا استفاده می‌شوند.

class asyncio.DatagramTransport(BaseTransport)

یک انتقالبرای اتصال‌های دیتاگرام (UDP).

نمونه‌های کلاس DatagramTransport از متد حلقه رویداد loop.create_datagram_endpoint() برگردانده می‌شوند.

class asyncio.SubprocessTransport(BaseTransport)

انتزاعی برای نمایش اتصال میان یک فرایند والد و فرایند فرزند سیستم‌عامل آن.

نمونه‌های کلاس SubprocessTransport از متدهای حلقه رویداد loop.subprocess_shell() و loop.subprocess_exec() برگردانده می‌شوند.

انتقال پایه

BaseTransport.close()

انتقالرا ببندید.

اگر انتقالبرای داده‌های خروجی دارای بافرباشد، داده‌های بافرشده به‌صورت ناهمگام تخلیه خواهند شد. دیگر داده‌ای دریافت نخواهد شد. پس از تخلیه‌ی همه‌ی داده‌های بافرشده، متد protocol.connection_lost() پروتکل با None به‌عنوان آرگومان آن فراخوانی خواهد شد. پس از بسته شدن، نباید از انتقال استفاده شود.

BaseTransport.is_closing()

اگر انتقالدر حال بسته شدن یا بسته باشد، True را برمی‌گرداند.

BaseTransport.get_extra_info(name, default=None)

اطلاعات درباره‌ی انتقالیا منابع زیربنایی مورد استفاده‌ی آن را برمی‌گرداند.

name رشته‌ای است که نشان‌دهنده‌ی بخشی از اطلاعات ویژه‌ی انتقال (transport-specific) است که باید دریافت شود.

default مقداری است که باید در صورت در دسترس نبودن اطلاعات، یا اگر انتقال از پرس‌وجوی آن با پیاده‌سازی حلقه رویداد شخص ثالثِ داده‌شده یا در پلتفرم فعلی پشتیبانی نکند، برگردانده شود.

برای مثال، کد زیر تلاش می‌کند شیء سوکت زیربناییِ انتقالرا دریافت کند:

sock = transport.get_extra_info('socket')
if sock is not None:
    print(sock.getsockopt(...))

دسته‌های اطلاعاتی که می‌توان آن‌ها را در برخی از انتقال‌ها پرس‌وجو کرد:

  • سوکت:

  • سوکت SSL:

    • 'compression': الگوریتم فشرده‌سازی استفاده‌شده به‌صورت یک رشته، یا None اگر اتصال فشرده‌نشده باشد؛ نتیجه‌ی ssl.SSLSocket.compression()

    • 'cipher': یک تاپل سه‌مقداری شامل نام رمز استفاده‌شده، نسخه‌ی پروتکل SSL که استفاده‌ی آن را تعریف می‌کند، و تعداد بیت‌های محرمانه‌ی استفاده‌شده؛ نتیجه‌ی ssl.SSLSocket.cipher()

    • 'peercert': گواهی همتا؛ نتیجه‌ی ssl.SSLSocket.getpeercert()

    • 'sslcontext': نمونه‌ی ssl.SSLContext

    • 'ssl_object': نمونه‌ای از ssl.SSLObject یا ssl.SSLSocket

  • پایپ :

    • 'pipe': شیء پایپ

  • subprocess:

BaseTransport.set_protocol(protocol)

تنظیم یک پروتکل جدید.

تعویض پروتکل تنها باید زمانی انجام شود که در مستندات آمده باشد که هر دو پروتکل از این تعویض پشتیبانی می‌کنند.

BaseTransport.get_protocol()

پروتکل جاری را برمی‌گرداند.

انتقال‌های فقط‌خواندنی

ReadTransport.is_reading()

اگر انتقالدر حال دریافت داده‌های جدید باشد، True را برمی‌گرداند.

اضافه شده در نسخه‌ی 3.7.

ReadTransport.pause_reading()

سمت دریافتی انتقالرا متوقف کنید. تا زمانی که resume_reading() فراخوانی نشود، هیچ داده‌ای به متد protocol.data_received() پروتکل منتقل نخواهد شد.

تغییر یافته در نسخه‌ی 3.7: این متد هم‌توان است؛ یعنی می‌توان آن را هنگامی فراخوانی کرد که انتقال از قبل متوقف یا بسته شده باشد.

ReadTransport.resume_reading()

سمت دریافت را از سر می‌گیرد. اگر داده‌ای برای خواندن در دسترس باشد، متد protocol.data_received() پروتکل بار دیگر فراخوانی خواهد شد.

تغییر یافته در نسخه‌ی 3.7: این متد هم‌توان (idempotent) است، یعنی می‌توان آن را زمانی که انتقال از قبل در حال خواندن است فراخوانی کرد.

انتقال‌های فقط نوشتنی

WriteTransport.abort()

انتقالرا بلافاصله، بدون انتظار برای تکمیل عملیات‌های در انتظار، ببندید. داده‌های بافرشده از بین خواهند رفت. دیگر داده‌ای دریافت نخواهد شد. متد protocol.connection_lost() پروتکل در نهایت با None به‌عنوان آرگومان آن فراخوانی خواهد شد.

WriteTransport.can_write_eof()

اگر انتقالاز write_eof() پشتیبانی کند، True و در غیر این صورت False برمی‌گرداند.

WriteTransport.get_write_buffer_size()

اندازه‌ی فعلی بافر خروجی استفاده‌شده توسط انتقال‌دهندهرا برمی‌گرداند.

WriteTransport.get_write_buffer_limits()

واترمارک‌های high و low را برای کنترل جریان نوشتن دریافت می‌کند. یک تاپل (low, high) برمی‌گرداند که در آن low و high اعداد مثبتی از بایت‌ها هستند.

برای تنظیم محدودیت‌ها از set_write_buffer_limits() استفاده کنید.

اضافه شده در نسخه‌ی 3.4.2.

WriteTransport.set_write_buffer_limits(high=None, low=None)

آستانه‌های high و low (watermarks) برای کنترل جریان نوشتن را تنظیم کنید.

این دو مقدار (که بر حسب تعداد بایت‌ها اندازه‌گیری می‌شوند) کنترل می‌کنند که متدهای پروتکل، یعنی protocol.pause_writing() و protocol.resume_writing()، چه زمانی فراخوانی می‌شوند. در صورت مشخص شدن، آستانه پایین (low watermark) باید کوچک‌تر یا مساوی آستانه بالا (high watermark) باشد. هیچ‌کدام از high و low نمی‌توانند منفی باشند.

pause_writing() زمانی فراخوانی می‌شود که اندازه‌ی بافر بزرگ‌تر یا مساوی مقدار high شود. اگر نوشتن متوقف شده باشد، resume_writing() زمانی فراخوانی می‌شود که اندازه‌ی بافر کوچک‌تر یا مساوی مقدار low شود.

مقادیر پیش‌فرض وابسته به پیاده‌سازی هستند. اگر فقط آستانه‌ی بالا (high watermark) داده شود، مقدار پیش‌فرض آستانه‌ی پایین (low watermark) مقداری وابسته به پیاده‌سازی و کوچک‌تر یا مساوی آستانه‌ی بالا خواهد بود. تنظیم high روی صفر، low را نیز روی صفر قرار می‌دهد و باعث می‌شود pause_writing() هر زمان که بافر غیرخالی شود، فراخوانی شود. تنظیم low روی صفر باعث می‌شود resume_writing() فقط زمانی فراخوانی شود که بافر خالی شود. استفاده از صفر برای هر یک از این دو محدودیت معمولاً بهینه نیست، زیرا فرصت‌های انجام I/O و محاسبات به‌صورت همزمان را کاهش می‌دهد.

برای دریافت محدودیت‌ها از get_write_buffer_limits() استفاده کنید.

WriteTransport.write(data)

تعدادی بایت data را به انتقالبنویسید.

این متد باعث مسدودشدن نمی‌شود؛ داده‌ها را بافر می‌کند و ترتیبی می‌دهد که به‌صورت ناهمگام ارسال شوند.

WriteTransport.writelines(list_of_data)

یک فهرست (یا هر پیمایش‌پذیری) از بایت‌های داده را به انتقال‌دهنده بنویسید. این کار از نظر عملکردی معادل فراخوانی write() روی هر عنصری است که پیمایش‌پذیر تولید می‌کند، اما ممکن است به‌شکل کارآمدتری پیاده‌سازی شده باشد.

WriteTransport.write_eof()

پس از تخلیه‌ی تمام داده‌های بافرشده، سمت نوشتاری انتقالرا ببندید. ممکن است هنوز داده‌هایی دریافت شوند.

این متد ممکن است در صورتی که انتقال (transport، برای مثال SSL) از اتصالات نیمه‌بسته پشتیبانی نکند، NotImplementedError را پرتاب کند.

انتقال‌های دیتاگرام (Datagram Transports)

DatagramTransport.sendto(data, addr=None)

بایت‌های data را به همتای راه دور مشخص‌شده توسط addr (یک آدرس مقصد وابسته به انتقال) ارسال کنید. اگر addr برابر None باشد، داده‌ها به آدرس مقصد مشخص‌شده در هنگام ایجاد انتقال ارسال می‌شوند.

این متد باعث مسدودشدن نمی‌شود؛ داده‌ها را بافر می‌کند و ترتیبی می‌دهد که به‌صورت ناهمگام ارسال شوند.

تغییر یافته در نسخه‌ی 3.13: این متد را می‌توان با یک شیء bytes خالی فراخوانی کرد تا یک دیتاگرام (datagram) با طول صفر ارسال شود. محاسبه‌ی اندازه‌ی بافرکه برای کنترل جریان استفاده می‌شود نیز به‌روزرسانی می‌شود تا سرآیند دیتاگرام را در نظر بگیرد.

DatagramTransport.abort()

انتقالرا بلافاصله ببندید، بدون انتظار برای تکمیل عملیات‌های در انتظار. داده‌های بافرشده از دست خواهند رفت. دیگر داده‌ای دریافت نخواهد شد. متد protocol.connection_lost() پروتکل در نهایت با None به‌عنوان آرگومان آن فراخوانی خواهد شد.

انتقال‌های زیرفرایند

SubprocessTransport.get_pid()

شناسه فرایند زیرفرایند را به‌صورت یک عدد صحیح برمی‌گرداند.

SubprocessTransport.get_pipe_transport(fd)

انتقال برای پایپ ارتباطی متناظر با توصیف‌گر پرونده عدد صحیح fd را برمی‌گرداند:

  • 0: انتقال جریانی قابل نوشتنبرای ورودی استاندارد (stdin)، یا None اگر زیرفرایند با stdin=PIPE ایجاد نشده باشد

  • 1: ترابری جریانی قابل‌خواندن برای خروجی استاندارد (stdout)، یا None اگر زیرفرآیند با stdout=PIPE ایجاد نشده باشد

  • 2: انتقال جریانی قابل خواندن برای خطای استاندارد (stderr)، یا None اگر زیرفرآیند با stderr=PIPE ایجاد نشده باشد

  • fd دیگر: None

SubprocessTransport.get_returncode()

کد بازگشت زیرفرایند را به‌صورت یک عدد صحیح، یا در صورتی که هنوز به پایان نرسیده باشد به‌صورت None برمی‌گرداند، که مشابه ویژگی subprocess.Popen.returncode است.

SubprocessTransport.kill()

زیرفرایند را خاتمه دهید.

در سیستم‌های POSIX، این تابع SIGKILL را به زیرفرایند ارسال می‌کند. در ویندوز، این متد نام مستعاری برای terminate() است.

همچنین subprocess.Popen.kill() را ببینید.

SubprocessTransport.send_signal(signal)

شماره‌ی signal را به زیرفرایند ارسال کنید، همان‌طور که در subprocess.Popen.send_signal() انجام می‌شود.

SubprocessTransport.terminate()

زیرفرایند را متوقف کنید.

در سیستم‌های POSIX، این متد SIGTERM را به زیرفرایند ارسال می‌کند. در ویندوز، تابع API ویندوز TerminateProcess() برای متوقف کردن زیرفرایند فراخوانی می‌شود.

همچنین subprocess.Popen.terminate() را ببینید.

SubprocessTransport.close()

زیرفرایند را با فراخوانی متد kill() خاتمه دهید.

اگر زیرفرایند هنوز بازگشت نکرده است، و انتقال‌های پایپ‌های stdin، stdout و stderr را ببندید.

پروتکل‌ها

کد منبع: Lib/asyncio/protocols.py


asyncio مجموعه‌ای از کلاس‌های پایه انتزاعی را فراهم می‌کند که باید برای پیاده‌سازی پروتکل‌های شبکه از آن‌ها استفاده شود. این کلاس‌ها برای استفاده همراه با transports در نظر گرفته شده‌اند.

زیرکلاس‌های کلاس‌های پایه‌ی انتزاعی پروتکل ممکن است برخی یا همه‌ی متدها را پیاده‌سازی کنند. همه‌ی این متدها کال‌بک هستند: آن‌ها توسط انتقال‌دهنده‌ها در رویدادهای خاصی فراخوانی می‌شوند، برای مثال هنگامی که داده‌هایی دریافت می‌شود. یک متد از پروتکل پایه باید توسط انتقال‌دهنده‌ی متناظر فراخوانی شود.

پروتکل‌های پایه

class asyncio.BaseProtocol

پروتکل پایه با متدهایی که تمام پروتکل‌ها به اشتراک می‌گذارند.

class asyncio.Protocol(BaseProtocol)

کلاس پایه برای پیاده‌سازی پروتکل‌های جریانی (TCP، سوکت‌های Unix و غیره).

class asyncio.BufferedProtocol(BaseProtocol)

یک کلاس پایه برای پیاده‌سازی پروتکل‌های جریانی (streaming protocols) با کنترل دستی بر بافر دریافت.

class asyncio.DatagramProtocol(BaseProtocol)

کلاس پایه برای پیاده‌سازی پروتکل‌های دیتاگرام (UDP).

class asyncio.SubprocessProtocol(BaseProtocol)

کلاس پایه برای پیاده‌سازی پروتکل‌های ارتباط با فرایندهای فرزند (پایپ‌های یک‌طرفه).

پروتکل پایه

تمام پروتکل‌های asyncio می‌توانند کال‌بک‌های پروتکل پایه را پیاده‌سازی کنند.

کال‌بک‌های اتصال

کال‌بک‌های اتصال در تمام پروتکل‌ها، دقیقاً یک بار به‌ازای هر اتصال موفق فراخوانی می‌شوند. سایر کال‌بک‌های پروتکل فقط می‌توانند بین آن دو متد فراخوانی شوند.

BaseProtocol.connection_made(transport)

هنگامی که یک اتصال برقرار می‌شود، فراخوانی می‌شود.

آرگومان transport، انتقالی است که نشان‌دهنده‌ی اتصال است. پروتکل مسئول ذخیره‌سازی ارجاع به انتقال خود است.

BaseProtocol.connection_lost(exc)

در صورت قطع یا بسته شدن اتصال، فراخوانی می‌شود.

این آرگومان یا یک شیء استثنا است یا None. مورد دوم به این معناست که یک EOF معمولی دریافت شده است، یا اتصال از این سمت اتصال قطع یا بسته شده است.

کال‌بک‌های کنترل جریان

کال‌بک‌های کنترل جریان می‌توانند توسط انتقال‌هابرای مکث یا از سرگیری نوشتن انجام‌شده توسط پروتکل فراخوانی شوند.

برای جزئیات بیشتر، مستندات متد set_write_buffer_limits() را ببینید.

BaseProtocol.pause_writing()

هنگامی که بافر انتقال از آستانه‌ی بالا (high watermark) عبور کند، فراخوانی می‌شود.

BaseProtocol.resume_writing()

هنگامی که بافر انتقالبه کمتر از آستانه پایین (low watermark) تخلیه می‌شود، فراخوانی می‌شود.

اگر اندازه‌ی بافر برابر با آستانه‌ی بالا باشد، pause_writing() فراخوانی نمی‌شود: اندازه‌ی بافر باید به‌طور اکید از آن عبور کند.

در مقابل، resume_writing() زمانی فراخوانی می‌شود که اندازه‌ی بافر برابر یا کمتر از آستانه‌ی پایین باشد. این شرایط پایان برای اطمینان از اینکه در صفر بودن هر یک از این آستانه‌ها روند کار مطابق انتظار باشد، مهم هستند.

پروتکل‌های جریانی

متدهای رویداد، مانند loop.create_server()، loop.create_unix_server()، loop.create_connection()، loop.create_unix_connection()، loop.connect_accepted_socket()، loop.connect_read_pipe() و loop.connect_write_pipe()، کارخانه‌هایی را می‌پذیرند که پروتکل‌های جریانی را برمی‌گردانند.

Protocol.data_received(data)

هنگامی که داده‌ای دریافت می‌شود، فراخوانی می‌شود. data یک شیء bytes غیرخالی است که حاوی داده‌های ورودی است.

اینکه داده‌ها بافر شوند (buffered)، بخش‌بندی شوند (chunked) یا بازهم‌سازی شوند (reassembled)، به انتقالبستگی دارد. به‌طور کلی، نباید به معناشناسی خاصی تکیه کنید و در عوض تجزیه خود را عام و انعطاف‌پذیر کنید. با این حال، داده‌ها همیشه به ترتیب صحیح دریافت می‌شوند.

این متد را می‌توان به تعداد دفعات دلخواه، تا زمانی که یک اتصال باز است، فراخوانی کرد.

با این حال، protocol.eof_received() حداکثر یک‌بار فراخوانی می‌شود. پس از فراخوانی eof_received()، دیگر data_received() فراخوانی نمی‌شود.

Protocol.eof_received()

هنگامی فراخوانی می‌شود که طرف مقابل اعلام کند دیگر داده‌ای ارسال نخواهد کرد (برای مثال با فراخوانی transport.write_eof()، اگر طرف مقابل نیز از asyncio استفاده کند).

این متد ممکن است یک مقدار نادرست (از جمله None) برگرداند، که در این صورت انتقالخود بسته خواهد شد. در مقابل، اگر این متد یک مقدار درست برگرداند، پروتکل استفاده‌شده تعیین می‌کند که آیا انتقالبسته شود یا خیر. از آن‌جا که پیاده‌سازی پیش‌فرض None را برمی‌گرداند، به‌طور ضمنی اتصال را می‌بندد.

برخی انتقال‌ها، از جمله SSL، از اتصال‌های نیمه‌بسته پشتیبانی نمی‌کنند؛ در این صورت، برگرداندن true از این متد منجر به بسته شدن اتصال خواهد شد.

ماشین حالت:

start -> connection_made
    [-> data_received]*
    [-> eof_received]?
-> connection_lost -> end

پروتکل‌های جریان بافرشده

اضافه شده در نسخه‌ی 3.7.

می‌توان از پروتکل‌های بافری با هر متدی از حلقه رویداد که از Streaming Protocols پشتیبانی می‌کند، استفاده کرد.

پیاده‌سازی‌های BufferedProtocol امکان تخصیص و کنترل دستی صریح بافر دریافت را فراهم می‌کنند. حلقه‌های رویداد می‌توانند سپس از بافر فراهم‌شده توسط پروتکل برای اجتناب از کپی‌های غیرضروری داده استفاده کنند. این موضوع می‌تواند منجر به بهبود محسوس کارایی برای پروتکل‌هایی شود که حجم زیادی از داده را دریافت می‌کنند. پیاده‌سازی‌های پیشرفته‌ی پروتکل می‌توانند تعداد تخصیص‌های بافر را به‌طور قابل‌توجهی کاهش دهند.

کال‌بک‌های زیر بر روی نمونه‌های BufferedProtocol فراخوانی می‌شوند:

BufferedProtocol.get_buffer(sizehint)

برای تخصیص یک بافر دریافت جدید فراخوانی می‌شود.

sizehint حداقل اندازه‌ی پیشنهادی برای بافر برگردانده‌شده است. بازگرداندن بافرهای کوچک‌تر یا بزرگ‌تر از آنچه sizehint پیشنهاد می‌دهد، مجاز است. هنگامی که روی -1 تنظیم شود، اندازه‌ی بافر می‌تواند دلخواه باشد. بازگرداندن بافری با اندازه‌ی صفر خطا است.

get_buffer() باید شیءای را برگرداند که پروتکل بافر را پیاده‌سازی کند.

BufferedProtocol.buffer_updated(nbytes)

هنگامی که بافر با داده‌های دریافتی به‌روزرسانی شد، فراخوانی می‌شود.

nbytes تعداد کل بایت‌هایی است که در بافر نوشته شده‌اند.

BufferedProtocol.eof_received()

مستندات متد protocol.eof_received() را ببینید.

get_buffer() می‌تواند تعداد دلخواهی بار در طول یک اتصال فراخوانی شود. با این حال، protocol.eof_received() حداکثر یک بار فراخوانی می‌شود و در صورت فراخوانی، پس از آن get_buffer() و buffer_updated() فراخوانی نخواهند شد.

ماشین حالت:

start -> connection_made
    [-> get_buffer
        [-> buffer_updated]?
    ]*
    [-> eof_received]?
-> connection_lost -> end

پروتکل‌های دیتاگرام

نمونه‌های پروتکل دیتاگرام (Datagram Protocol) باید توسط کارخانه‌های پروتکل (protocol factories) که به متد loop.create_datagram_endpoint() داده می‌شوند، ساخته شوند.

DatagramProtocol.datagram_received(data, addr)

هنگام دریافت یک دیتاگرام فراخوانی می‌شود. data یک شیء bytes حاوی داده‌های دریافتی است. addr نشانی همتای ارسال‌کننده‌ی داده‌ها است؛ قالب دقیق آن به انتقالبستگی دارد.

DatagramProtocol.error_received(exc)

هنگامی که یک عملیات ارسال یا دریافت پیشین یک OSError را پرتاب کند، فراخوانی می‌شود. exc یک نمونه از OSError است.

این متد در شرایط نادر فراخوانی می‌شود، زمانی که انتقال (مثلاً UDP) تشخیص دهد که یک دیتاگرام نتوانسته است به گیرنده خود تحویل داده شود. هرچند در بسیاری از شرایط، دیتاگرام‌های غیرقابل‌تحویل به‌صورت خاموش دور انداخته می‌شوند.

توجه

در سیستم‌های BSD (macOS، FreeBSD و غیره) کنترل جریان برای پروتکل‌های دیتاگرام پشتیبانی نمی‌شود، زیرا روش قابل‌اطمینانی برای تشخیص شکست‌های ارسال ناشی از نوشتن تعداد بیش‌ازحد بسته وجود ندارد.

سوکت همیشه «آماده» به نظر می‌رسد و بسته‌های مازاد دور انداخته می‌شوند. ممکن است یک OSError با errno تنظیم‌شده روی errno.ENOBUFS پرتاب شود یا نشود؛ اگر پرتاب شود، به DatagramProtocol.error_received() گزارش می‌شود اما در غیر این صورت نادیده گرفته می‌شود.

پروتکل‌های زیرفرایند

نمونه‌های پروتکل زیرفرایند باید به‌وسیله‌ی کارخانه‌های پروتکل داده‌شده به متدهای loop.subprocess_exec() و loop.subprocess_shell() ساخته شوند.

SubprocessProtocol.pipe_data_received(fd, data)

هنگامی فراخوانی می‌شود که فرایند فرزند داده‌ای را در پایپ stdout یا stderr خود بنویسد.

fd توصیف‌گر پرونده عدد صحیحِ پایپ است.

data یک شیء bytes غیرخالی حاوی داده‌های دریافتی است.

SubprocessProtocol.pipe_connection_lost(fd, exc)

هنگامی فراخوانی می‌شود که یکی از پایپ‌های ارتباطی با فرایند فرزند بسته شود.

fd توصیف‌گر پرونده از نوع عدد صحیح است که بسته شده است.

SubprocessProtocol.process_exited()

هنگامی فراخوانی می‌شود که فرایند فرزند خارج شده باشد.

می‌توان آن را پیش از متدهای pipe_data_received() و pipe_connection_lost() فراخوانی کرد.

مثال‌ها

سرور اکو TCP

با استفاده از متد loop.create_server() یک سرور اکوی TCP ایجاد کنید، داده‌های دریافتی را بازگردانید و اتصال را ببندید:

import asyncio


class EchoServerProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        print('Send: {!r}'.format(message))
        self.transport.write(data)

        print('Close the client socket')
        self.transport.close()


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    server = await loop.create_server(
        EchoServerProtocol,
        '127.0.0.1', 8888)

    async with server:
        await server.serve_forever()


asyncio.run(main())

همچنین ملاحظه نمائید

مثال سرور اکوی TCP با استفاده از جریان‌ها از تابع سطح بالای asyncio.start_server() استفاده می‌کند.

کلاینت‌ی پژواک TCP

یک کلاینت اکوی TCP با استفاده از متد loop.create_connection()، داده ارسال می‌کند و منتظر می‌ماند تا اتصال بسته شود:

import asyncio


class EchoClientProtocol(asyncio.Protocol):
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost

    def connection_made(self, transport):
        transport.write(self.message.encode())
        print('Data sent: {!r}'.format(self.message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        self.on_con_lost.set_result(True)


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    on_con_lost = loop.create_future()
    message = 'Hello World!'

    transport, protocol = await loop.create_connection(
        lambda: EchoClientProtocol(message, on_con_lost),
        '127.0.0.1', 8888)

    # Wait until the protocol signals that the connection
    # is lost and close the transport.
    try:
        await on_con_lost
    finally:
        transport.close()


asyncio.run(main())

همچنین ملاحظه نمائید

مثال TCP echo client using streams از تابع سطح بالای asyncio.open_connection() استفاده می‌کند.

سرور پژواک UDP

یک سرور اکو UDP، با استفاده از متد loop.create_datagram_endpoint()، داده‌های دریافتی را بازمی‌گرداند:

import asyncio


class EchoServerProtocol:
    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        message = data.decode()
        print('Received %r from %s' % (message, addr))
        print('Send %r to %s' % (message, addr))
        self.transport.sendto(data, addr)


async def main():
    print("Starting UDP server")

    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    # One protocol instance will be created to serve all
    # client requests.
    transport, protocol = await loop.create_datagram_endpoint(
        EchoServerProtocol,
        local_addr=('127.0.0.1', 9999))

    try:
        await asyncio.sleep(3600)  # Serve for 1 hour.
    finally:
        transport.close()


asyncio.run(main())

کلاینت پژواک UDP

یک کلاینت پژواک UDP، با استفاده از متد loop.create_datagram_endpoint()، داده‌ها را ارسال می‌کند و هنگامی که پاسخ را دریافت می‌کند، انتقال را می‌بندد:

import asyncio


class EchoClientProtocol:
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost
        self.transport = None

    def connection_made(self, transport):
        self.transport = transport
        print('Send:', self.message)
        self.transport.sendto(self.message.encode())

    def datagram_received(self, data, addr):
        print("Received:", data.decode())

        print("Close the socket")
        self.transport.close()

    def error_received(self, exc):
        print('Error received:', exc)

    def connection_lost(self, exc):
        print("Connection closed")
        self.on_con_lost.set_result(True)


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    on_con_lost = loop.create_future()
    message = "Hello World!"

    transport, protocol = await loop.create_datagram_endpoint(
        lambda: EchoClientProtocol(message, on_con_lost),
        remote_addr=('127.0.0.1', 9999))

    try:
        await on_con_lost
    finally:
        transport.close()


asyncio.run(main())

اتصال سوکت‌های موجود

با استفاده از متد loop.create_connection() به‌همراه یک پروتکل، منتظر بمانید تا یک سوکت داده‌ای دریافت کند:

import asyncio
import socket


class MyProtocol(asyncio.Protocol):

    def __init__(self, on_con_lost):
        self.transport = None
        self.on_con_lost = on_con_lost

    def connection_made(self, transport):
        self.transport = transport

    def data_received(self, data):
        print("Received:", data.decode())

        # We are done: close the transport;
        # connection_lost() will be called automatically.
        self.transport.close()

    def connection_lost(self, exc):
        # The socket has been closed
        self.on_con_lost.set_result(True)


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()
    on_con_lost = loop.create_future()

    # Create a pair of connected sockets
    rsock, wsock = socket.socketpair()

    # Register the socket to wait for data.
    transport, protocol = await loop.create_connection(
        lambda: MyProtocol(on_con_lost), sock=rsock)

    # Simulate the reception of data from the network.
    loop.call_soon(wsock.send, 'abc'.encode())

    try:
        await protocol.on_con_lost
    finally:
        transport.close()
        wsock.close()

asyncio.run(main())

همچنین ملاحظه نمائید

مثال پایش یک توصیف‌گر پرونده برای رویدادهای خواندن از متد سطح پایین loop.add_reader() برای ثبت یک FD استفاده می‌کند.

مثال ثبت یک سوکت باز برای انتظار داده‌ها با استفاده از جریان‌ها از جریان‌های سطح بالایی استفاده می‌کند که توسط تابع open_connection() در یک هم‌روال ایجاد شده‌اند.

loop.subprocess_exec() و SubprocessProtocol

مثالی از یک پروتکل زیرفرایند که برای دریافت خروجی یک زیرفرایند و انتظار برای خروج زیرفرایند استفاده می‌شود.

زیرفرایند توسط متد loop.subprocess_exec() ایجاد می‌شود:

import asyncio
import sys

class DateProtocol(asyncio.SubprocessProtocol):
    def __init__(self, exit_future):
        self.exit_future = exit_future
        self.output = bytearray()
        self.pipe_closed = False
        self.exited = False

    def pipe_connection_lost(self, fd, exc):
        self.pipe_closed = True
        self.check_for_exit()

    def pipe_data_received(self, fd, data):
        self.output.extend(data)

    def process_exited(self):
        self.exited = True
        # process_exited() method can be called before
        # pipe_connection_lost() method: wait until both methods are
        # called.
        self.check_for_exit()

    def check_for_exit(self):
        if self.pipe_closed and self.exited:
            self.exit_future.set_result(True)

async def get_date():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    code = 'import datetime as dt; print(dt.datetime.now())'
    exit_future = asyncio.Future(loop=loop)

    # Create the subprocess controlled by DateProtocol;
    # redirect the standard output into a pipe.
    transport, protocol = await loop.subprocess_exec(
        lambda: DateProtocol(exit_future),
        sys.executable, '-c', code,
        stdin=None, stderr=None)

    # Wait for the subprocess exit using the process_exited()
    # method of the protocol.
    await exit_future

    # Close the stdout pipe.
    transport.close()

    # Read the output which was collected by the
    # pipe_data_received() method of the protocol.
    data = bytes(protocol.output)
    return data.decode('ascii').rstrip()

date = asyncio.run(get_date())
print(f"Current date: {date}")

همچنین همین مثال را ببینید که با استفاده از APIهای سطح بالا نوشته شده است.