Client Server في Python نموذج يكون فيه الخادم مستعدًا لقبول اتصال، بينما يبدأ العميل الاتصال بعنوان ومنفذ معروفين. يستخدم هذا الدرس TCP على الجهاز نفسه عبر 127.0.0.1 كي تركز على ترتيب العمليات دون فتح خدمة للإنترنت. الخادم يستقبل رسالة واحدة ويرد ثم يغلق، لذلك ستحتاج تشغيله من جديد لتجربة عميل آخر. socket جزء من مكتبة Python القياسية ولا يحتاج تثبيت حزمة تحمل اسمًا مشابهًا.
التجهيز والمتطلبات
# socket is included with Python; no pip package is needed.العنوان والمنفذ وتمرير tuple
تستقبل bind وcreate_connection زوجًا مثل (host, port)، وليس وسيطين منفصلين للعنوان والمنفذ. يربط bind المقبس بعنوان محلي، ثم listen يجعله ينتظر وaccept يعيد اتصالًا جديدًا خاصًا بالعميل. يستخدم العميل create_connection التي تفتح الاتصال وتطبق مهلة. المنفذ 4980 اختير للتجربة؛ إذا كان مشغولًا غيّره في الطرفين. لا تحول العنوان إلى 0.0.0.0 دون فهم أثر إتاحة الاستماع على واجهات الشبكة.
لماذا لا تكفي recv واحدة؟
TCP ينقل تدفق بايتات وليس رسائل منفصلة. قد تصل الكلمة على عدة قراءات، أو تصل رسالتان في قراءة واحدة. نختار هنا سطرًا منتهيًا بمحرف newline ليكون بروتوكولًا بسيطًا، ونجمع البايتات قبل فك UTF-8 كي لا ينكسر حرف عربي بين القراءات. تقرأ الدالة بايتًا في كل مرة لتوضيح الفكرة، لكن التطبيقات السريعة تستخدم مخزنًا وتبحث داخله عن فواصل الرسائل.
المثال البرمجي الكامل
import socket
MAX_MESSAGE = 4096
def receive_line(connection):
data = bytearray()
while True:
part = connection.recv(1)
if not part:
raise ConnectionError("Connection closed before newline")
if part == b"\n":
return data.decode("utf-8")
if len(data) >= MAX_MESSAGE:
raise ValueError("Message is too large")
data.extend(part)
def serve_once(host="127.0.0.1", port=4980):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.settimeout(15)
server.bind((host, port))
server.listen(1)
connection, address = server.accept()
with connection:
connection.settimeout(5)
message = receive_line(connection)
connection.sendall(("Received: " + message + "\n").encode("utf-8"))
def request(message, host="127.0.0.1", port=4980):
if "\n" in message or len(message.encode("utf-8")) > MAX_MESSAGE - 10:
raise ValueError("Use one short line")
with socket.create_connection((host, port), timeout=5) as client:
client.sendall((message + "\n").encode("utf-8"))
return receive_line(client)
if __name__ == "__main__":
import sys
if len(sys.argv) != 2 or sys.argv[1] not in {"server", "client"}:
raise SystemExit("Use: python tcp_demo.py server OR client")
if sys.argv[1] == "server":
serve_once()
else:
print(request("Hello from Python"))
تشغيل الطرفين وفحص الرد
احفظ الملف باسم tcp_demo.py. في الطرفية الأولى نفذ python tcp_demo.py server، وخلال 15 ثانية شغل python tcp_demo.py client في طرفية ثانية. يفترض أن يطبع العميل Received متبوعة بالرسالة. sendall تحاول إرسال البيانات كلها أو ترفع خطأ، بينما send قد ترسل جزءًا فقط. نغلق كل مقبس ضمن with، ونرفض الرسالة الطويلة أو متعددة الأسطر ونضبط مهلة لتجنب انتظار غير محدود.
الأخطاء وحدود هذا التطبيق
ConnectionRefusedError يعني غالبًا أن الخادم لم يبدأ أو أن المنفذ مختلف. انتهاء مهلة الخادم قبل تشغيل العميل يستلزم تشغيله مجددًا. انتهاء الاتصال دون newline يرفض الرسالة بدل اعتبار جزء مبتور نتيجة كاملة. المثال ليس خادم محادثة متعدد العملاء، ولا يضيف تشفير النقل أو المصادقة؛ أي توسع عبر شبكة يحتاج تصميمًا مناسبًا. جرّب رسالة عربية ورسالة فارغة وحدًا زائدًا للحجم، ولا تعتمد نجاح رسالة إنجليزية قصيرة وحدها.
كيف تعمل الرسائل العربية؟
encode يحول النص إلى UTF-8، لكن حدود بايتات الحرف لا تتوافق بالضرورة مع حدود استدعاءات recv. لذلك نجمع الرسالة كاملة ثم نفكها مرة واحدة. إذا فككت كل جزء منفصلًا فقد ينقسم حرف وتظهر UnicodeDecodeError. المثال يضع حد الحجم بالبايتات، ولهذا قد تصل إلى الحد بعدد أحرف عربية أقل من الأحرف الإنجليزية. حدد وحدة الحد في أي واجهة للمستخدم ولا تفترض أن len للنص يساوي عدد البايتات المرسلة.
لماذا توجد مهلة للخادم والعميل؟
الانتظار المفتوح يجعل مثالًا تعليميًا يبدو معلقًا إذا لم يبدأ الطرف الثاني. مهلة accept تحد زمن انتظار اتصال، ومهلة المقبس المتصل تحد زمن العمليات التالية. عندما تنتهي المهلة، يعاد التحكم إلى البرنامج ويمكن عرض خطأ أو إعادة محاولة محدودة. لا تستعمل حلقة تعيد الاتصال فورًا بلا تأخير أو عدد محاولات أقصى. في التطبيق الحقيقي سجل المرحلة التي فشلت كي تميز فشل الاتصال عن اتصال ناجح أرسل رسالة ناقصة.






