socket --- وحدة المقابس (socket)

توفّر هذه الوحدة الوصول إلى واجهة مقابس BSD.

الاختلاف عن CPython

تحقيقًا للكفاءة والاتساق، تنفّذ كائنات المقابس في MicroPython واجهة stream (شبيهة بالملف) مباشرةً. أما في CPython، فعليك تحويل المقبس إلى كائن شبيه بالملف باستخدام الدالة makefile(). ولا تزال هذه الدالة مدعومة في MicroPython (لكنها لا تؤدي أي عمل)، لذا حيثما كان التوافق مع CPython مهمًا، احرص على استخدامها.

صيغة (صيغ) عنوان المقبس

صيغة عنوان المقبس الأصلية لوحدة socket هي نوع بيانات معتم تُرجعه الدالة getaddrinfo()، والتي يجب استخدامها لحل العنوان النصي (بما في ذلك العناوين العددية):

sockaddr = socket.getaddrinfo('www.micropython.org', 80)[0][-1]
# You must use getaddrinfo() even for numeric addresses
sockaddr = socket.getaddrinfo('127.0.0.1', 80)[0][-1]
# Now you can use that address
sock.connect(sockaddr)

إنّ استخدام getaddrinfo() هو الطريقة الأكثر كفاءة (من حيث الذاكرة وقدرة المعالجة معًا) والأكثر قابلية للنقل للتعامل مع العناوين.

توفّر وحدة socket أيضًا طريقة متوافقة مع CPython لتحديد العناوين باستخدام الصفوف (tuples)، كما هو موضح أدناه. على OpenMV Cam تكون وحدة socket مدمجة؛ ويمكن إعطاء العناوين العددية مباشرةً بصيغة الصف، لكن يجب أولًا حل أسماء النطاقات باستخدام getaddrinfo().

خلاصة القول:

  • استخدم دائمًا getaddrinfo() لحل أسماء المضيفين.

  • يمكن استخدام عناوين الصفوف الموضحة أدناه كاختصار للعناوين العددية، لأغراض الحلول السريعة والاستخدام التفاعلي.

صيغة عنوان الصف لوحدة socket:

  • IPv4: (ipv4_address, port)، حيث ipv4_address سلسلة تحتوي على عنوان IPv4 عددي بترميز النقاط، مثل "8.8.8.8"، وport رقم منفذ صحيح في النطاق 1-65535. ولا تُقبل أسماء النطاقات كـ ipv4_address؛ فحلّها أولًا باستخدام getaddrinfo().

  • IPv6: (ipv6_address, port, flowinfo, scopeid)، حيث ipv6_address سلسلة تحتوي على عنوان IPv6 عددي بترميز النقطتين، مثل "2001:db8::1"، وport رقم منفذ صحيح في النطاق 1-65535. ويجب أن يكون flowinfo صفرًا. وscopeid هو معرّف نطاق الواجهة للعناوين المحلية للوصلة. ولا تُقبل أسماء النطاقات كـ ipv6_address؛ فحلّها أولًا باستخدام getaddrinfo().

الدوال

socket.getaddrinfo(host: str, port: int, af: int = 0, type: int = 0, proto: int = 0, flags: int = 0, /) List[Tuple]

يترجم وسيط المضيف/المنفذ إلى سلسلة من الصفوف الخماسية التي تحتوي على جميع الوسائط اللازمة لإنشاء مقبس متصل بتلك الخدمة. يمكن استخدام الوسائط af وtype وproto (التي لها المعنى نفسه كما في الدالة socket) لتصفية أنواع العناوين التي تُرجَع. وإذا لم يُحدّد معامل أو كان صفرًا، فقد تُرجَع جميع تركيبات العناوين (مما يتطلب التصفية من جانب المستخدم).

تكون قائمة الصفوف الخماسية الناتجة بالبنية التالية:

(family, type, proto, canonname, sockaddr)

يوضح المثال التالي كيفية الاتصال بعنوان url معطى:

s = socket.socket()
# This assumes that if "type" is not specified, an address for
# SOCK_STREAM will be returned, which may be not true
s.connect(socket.getaddrinfo('www.micropython.org', 80)[0][-1])

الاستخدام الموصى به لمعاملات التصفية:

s = socket.socket()
# Guaranteed to return an address which can be connect'ed to for
# stream operation.
s.connect(socket.getaddrinfo('www.micropython.org', 80, 0, SOCK_STREAM)[0][-1])

الاختلاف عن CPython

يثير CPython استثناء socket.gaierror (وهو صنف فرعي من OSError) في حال حدوث خطأ في هذه الدالة. أما MicroPython فلا يملك socket.gaierror ويثير OSError مباشرةً. لاحظ أن أرقام أخطاء getaddrinfo() تشكّل فضاء أسماء منفصلًا وقد لا تطابق أرقام الأخطاء من الوحدة errno. ولتمييز أخطاء getaddrinfo()، تُمثَّل بأرقام سالبة، بينما تكون أخطاء النظام القياسية أرقامًا موجبة (تتاح أرقام الأخطاء باستخدام الخاصية e.args[0] من كائن الاستثناء). إنّ استخدام القيم السالبة تفصيلٌ مؤقت قد يتغير في المستقبل.

socket.inet_ntop(af: int, bin_addr: bytes) str

يحوّل عنوان شبكة ثنائي bin_addr من عائلة العناوين المعطاة af إلى تمثيل نصي:

>>> socket.inet_ntop(socket.AF_INET, b"\x7f\0\0\1")
'127.0.0.1'
socket.inet_pton(af: int, txt_addr: str) bytes

يحوّل عنوان شبكة نصي txt_addr من عائلة العناوين المعطاة af إلى تمثيل ثنائي:

>>> socket.inet_pton(socket.AF_INET, "1.2.3.4")
b'\x01\x02\x03\x04'

الثوابت

socket.AF_INET: int

عائلة عناوين IPv4.

socket.AF_INET6: int

عائلة عناوين IPv6.

socket.SOCK_STREAM: int

نوع مقبس الدفق (TCP).

socket.SOCK_DGRAM: int

نوع مقبس مخطط البيانات (UDP).

socket.SOCK_RAW: int

نوع المقبس الخام.

socket.IPPROTO_IP: int

مستوى بروتوكول IP. يُستخدم كوسيط level للدالة setsockopt() مع خيارات IP_*.

socket.IPPROTO_TCP: int

بروتوكول TCP. لست بحاجة إلى تمريره إلى socket (إذ يختاره نوع المقبس SOCK_STREAM تلقائيًا)؛ واستخدامه الفعلي الوحيد هو كوسيط level للدالة setsockopt() مع خيارات TCP_*.

socket.SOL_SOCKET: int

مستوى خيار المقبس. يُستخدم كوسيط level للدالة setsockopt() مع خيارات SO_*.

socket.SO_REUSEADDR: int

يسمح للمقبس بالارتباط بعنوان/منفذ لا يزال في حالة TIME_WAIT.

socket.SO_BROADCAST: int

يسمح بإرسال مخططات البيانات إلى عنوان بث.

socket.SO_KEEPALIVE: int

يفعّل الإرسال الدوري لمسابر الإبقاء على الاتصال على مقبس متصل.

socket.SO_SNDTIMEO: int

مهلة الإرسال، بالميلي ثانية، تُمرَّر كوسيط value للدالة setsockopt().

socket.SO_RCVTIMEO: int

مهلة الاستقبال، بالميلي ثانية، تُمرَّر كوسيط value للدالة setsockopt().

socket.IP_ADD_MEMBERSHIP: int

ينضم إلى مجموعة بث متعدد. خيار من مستوى IPPROTO_IP للدالة setsockopt().

socket.IP_DROP_MEMBERSHIP: int

يغادر مجموعة بث متعدد. خيار من مستوى IPPROTO_IP للدالة setsockopt().

socket.TCP_NODELAY: int

يعطّل خوارزمية Nagle. خيار من مستوى IPPROTO_TCP للدالة setsockopt().

socket.MSG_PEEK: int

لـ recv() / recvfrom(): يُرجع البيانات دون إزالتها من طابور الإدخال.

socket.MSG_DONTWAIT: int

لـ recv() / recvfrom(): ينفّذ العملية في الوضع غير الحاجب.

الأصناف

class socket.socket(af: int = AF_INET, type: int = SOCK_STREAM, proto: int = IPPROTO_TCP, /)

ينشئ مقبسًا جديدًا باستخدام عائلة العناوين ونوع المقبس ورقم البروتوكول المعطاة. وتحديد proto غير مطلوب في معظم الحالات (وغير موصى به)؛ إذ يختار وسيط type البروتوكول اللازم تلقائيًا:

# Create STREAM TCP socket
socket(AF_INET, SOCK_STREAM)
# Create DGRAM UDP socket
socket(AF_INET, SOCK_DGRAM)
close() None

يضع علامة على المقبس بأنه مغلق ويحرّر جميع الموارد. وبمجرد حدوث ذلك، ستفشل جميع العمليات المستقبلية على كائن المقبس. وسيتلقى الطرف البعيد إشارة EOF إذا كان البروتوكول يدعم ذلك.

تُغلق المقابس تلقائيًا عند جمعها بآلية كنس المهملات، لكن يُوصى بإغلاقها صراحةً بـ close() بمجرد الانتهاء من العمل بها.

bind(address: Any) None

يربط المقبس بـ address. يجب ألا يكون المقبس مرتبطًا مسبقًا.

listen(backlog: int = 2) None

يمكّن خادمًا من قبول الاتصالات. وإذا حُدّد backlog فيجب أن يكون 0 على الأقل (وإذا كان أقل، فسيُضبط إلى 0)؛ وهو يحدد عدد الاتصالات غير المقبولة التي سيسمح بها النظام قبل رفض الاتصالات الجديدة. وإذا لم يُحدّد، فسيُختار قيمة افتراضية معقولة.

accept() Tuple['socket', Tuple]

يقبل اتصالًا. يجب أن يكون المقبس مرتبطًا بعنوان ومنصتًا للاتصالات. والقيمة المُرجَعة زوج (conn, address) حيث conn كائن مقبس جديد قابل للاستخدام لإرسال البيانات واستقبالها على الاتصال، وaddress هو العنوان المرتبط بالمقبس على الطرف الآخر من الاتصال.

connect(address: Any) None

يتصل بمقبس بعيد عند address.

send(bytes: bytes) int

يرسل بيانات إلى المقبس. يجب أن يكون المقبس متصلًا بمقبس بعيد. يُرجع عدد البايتات المُرسَلة، الذي قد يكون أصغر من طول البيانات ("كتابة قصيرة").

sendall(bytes: bytes) None

يرسل جميع البيانات إلى المقبس. يجب أن يكون المقبس متصلًا بمقبس بعيد. وخلافًا لـ send()، ستحاول هذه الدالة إرسال جميع البيانات، بإرسالها قطعة قطعة على التوالي.

سلوك هذه الدالة على المقابس غير الحاجبة غير معرّف. ولهذا السبب، يُوصى في MicroPython باستخدام الدالة write() بدلًا منها، التي تتبع سياسة "بلا كتابات قصيرة" نفسها للمقابس الحاجبة، وتُرجع عدد البايتات المُرسَلة على المقابس غير الحاجبة.

recv(bufsize: int, flags: int = 0) bytes

يستقبل بيانات من المقبس. والقيمة المُرجَعة كائن بايتات يمثّل البيانات المستقبَلة. ويُحدّد bufsize الحد الأقصى لكمية البيانات التي يمكن استقبالها دفعةً واحدة.

وسيط flags الاختياري هو OR على مستوى البتات لأعلام الرسائل (MSG_PEEK وMSG_DONTWAIT)، التي لها المعنى نفسه كما في CPython.

sendto(bytes: bytes, address: Any) int

يرسل بيانات إلى المقبس. يجب ألا يكون المقبس متصلًا بمقبس بعيد، إذ يُحدَّد مقبس الوجهة بواسطة address.

recvfrom(bufsize: int, flags: int = 0) Tuple[bytes, Tuple]

يستقبل بيانات من المقبس. والقيمة المُرجَعة زوج (bytes, address) حيث bytes كائن بايتات يمثّل البيانات المستقبَلة وaddress هو عنوان المقبس الذي يرسل البيانات.

راجع الدالة recv() لشرح وسيط flags الاختياري.

setsockopt(level: int, optname: int, value: int | bytes) None

يضبط قيمة خيار المقبس المعطى. الثوابت الرمزية اللازمة معرّفة في وحدة socket (SO_* وغيرها). ويمكن أن تكون value عددًا صحيحًا أو كائنًا شبيهًا بالبايتات يمثّل مخزنًا مؤقتًا.

settimeout(value: float | None) None

يضبط مهلة على عمليات المقبس الحاجبة. يمكن أن يكون وسيط القيمة عددًا عشريًا غير سالب يعبّر عن الثواني، أو None. وإذا أُعطيت قيمة غير صفرية، فستثير عمليات المقبس اللاحقة استثناء OSError إذا انقضت قيمة فترة المهلة قبل اكتمال العملية. وإذا أُعطي صفر، يوضع المقبس في الوضع غير الحاجب. وإذا أُعطيت None، يوضع المقبس في الوضع الحاجب.

البديل القابل للنقل والعام هو استخدام كائن select.poll. وهذا يتيح الانتظار على كائنات متعددة في الوقت نفسه (وليس على المقابس فحسب، بل على كائنات stream العامة التي تدعم الاستطلاع). مثال:

# Instead of:
s.settimeout(1.0)  # time in seconds
s.read(10)  # may timeout

# Use:
poller = select.poll()
poller.register(s, select.POLLIN)
res = poller.poll(1000)  # time in milliseconds
if not res:
    # s is still not ready for input, i.e. operation timed out

الاختلاف عن CPython

يثير CPython استثناء socket.timeout في حال انقضاء المهلة، وهو صنف فرعي من OSError. أما MicroPython فيثير OSError مباشرةً بدلًا منه. وإذا استخدمت except OSError: لالتقاط الاستثناء، فسيعمل كودك في كل من MicroPython وCPython.

setblocking(flag: bool) None

يضبط الوضع الحاجب أو غير الحاجب للمقبس: إذا كان flag خطأً (false)، يُضبط المقبس على الوضع غير الحاجب، وإلا فعلى الوضع الحاجب.

هذه الدالة اختصار لاستدعاءات settimeout() معينة:

  • sock.setblocking(True) يكافئ sock.settimeout(None)

  • sock.setblocking(False) يكافئ sock.settimeout(0)

makefile(mode: str = 'rb', buffering: int = 0, /) Any

يُرجع كائن ملف مرتبطًا بالمقبس. ويعتمد النوع المُرجَع بالضبط على الوسائط المعطاة لـ makefile(). والدعم مقتصر على الأوضاع الثنائية فقط ('rb' و'wb' و'rwb'). أما وسائط CPython: encoding وerrors وnewline فغير مدعومة.

الاختلاف عن CPython

بما أن MicroPython لا يدعم الدفق المخزَّن مؤقتًا، فإن قيم المعامل buffering تُتجاهل وتُعامَل كأنها 0 (غير مخزَّن مؤقتًا).

الاختلاف عن CPython

إغلاق كائن الملف المُرجَع من makefile() سيُغلق المقبس الأصلي أيضًا.

read(size: int | None = None) bytes

يقرأ ما يصل إلى size بايتًا من المقبس. ويُرجع كائن بايتات. وإذا لم تُعطَ size، فإنه يقرأ جميع البيانات المتاحة من المقبس حتى EOF؛ وبالتالي لن تُرجع الدالة حتى يُغلق المقبس. وتحاول هذه الدالة قراءة أكبر قدر ممكن من البيانات المطلوبة (بلا "قراءات قصيرة"). غير أن ذلك قد لا يكون ممكنًا مع المقبس غير الحاجب، وعندها ستُرجَع بيانات أقل.

readinto(buf: bytearray | memoryview, nbytes: int | None = None) int

يقرأ بايتات إلى buf. وإذا حُدّد nbytes، فإنه يقرأ على الأكثر ذلك العدد من البايتات. وإلا، فإنه يقرأ على الأكثر len(buf) بايتًا. وتمامًا كـ read()، تتبع هذه الدالة سياسة "بلا قراءات قصيرة".

القيمة المُرجَعة: عدد البايتات المقروءة والمخزَّنة في buf.

readline() bytes

يقرأ سطرًا ينتهي بحرف سطر جديد.

القيمة المُرجَعة: السطر المقروء.

write(buf: bytes) int

يكتب مخزن البايتات المؤقت إلى المقبس. وستحاول هذه الدالة كتابة جميع البيانات إلى مقبس (بلا "كتابات قصيرة"). غير أن ذلك قد لا يكون ممكنًا مع مقبس غير حاجب، وعندها ستكون القيمة المُرجَعة أقل من طول buf.

القيمة المُرجَعة: عدد البايتات المكتوبة.

ملاحظة

لا ينفّذ MicroPython socket.error. ويملك CPython استثناء socket.error مهملًا وهو اسم بديل لـ OSError؛ ففي MicroPython، استخدم OSError مباشرةً لالتقاط الأخطاء المتعلقة بالمقابس.