برمجة الشبكات بلغة Rust: كتابة خادم TCP سريع وآمن

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا Rust هو الملك الجديد لبرمجة الشبكات؟

\n\n

هل سبق لك أن كتبت خادم شبكة بلغة C أو C++ وواجهت خطأ تجزئة (segmentation fault) بسبب كائن مزامنة (mutex) منسي؟ أو هل ناضلت مع سباقات البيانات (data races) في Go محاولاً فهم لماذا توقفت الروتينات (goroutines)؟ Rust يحل هذه المشاكل مرة واحدة وإلى الأبد. نظام الملكية (ownership) ونموذج التزامن "بدون خوف" (fearless concurrency) يجعلان برمجة الشبكات بلغة Rust ليست آمنة فحسب، بل عالية الأداء بشكل لا يصدق.

\n\n

في هذه المقالة، سنشرح كيفية إنشاء خوادم TCP و UDP بلغة Rust، باستخدام كل من المكتبة القياسية (std::net) ومكدس async الحديث القائم على Tokio. سترى أن Rust ليست مجرد لغة للبرمجة النظامية، بل هي أيضاً أداة ممتازة لكتابة خدمات شبكية عالية التحميل.

\n\n

الأساسيات: std::net — خادم TCP الصدى في 5 دقائق

\n\n

لنبدأ بالكلاسيكيات — خادم الصدى. يستقبل اتصال TCP، ويقرأ البيانات، ويرسلها مرة أخرى إلى العميل. في Rust، يتم ذلك بإيجاز مدهش.

\n\n
use std::io::{Read, Write};\nuse std::net::{TcpListener, TcpStream};\nuse std::thread;\n\nfn handle_client(mut stream: TcpStream) {\n    let mut buffer = [0; 1024];\n    loop {\n        match stream.read(&mut buffer) {\n            Ok(0) => {\n                println!("العميل قطع الاتصال");\n                break;\n            }\n            Ok(n) => {\n                if let Err(e) = stream.write_all(&buffer[..n]) {\n                    eprintln!("خطأ في الكتابة: {}", e);\n                    break;\n                }\n            }\n            Err(e) => {\n                eprintln!("خطأ في القراءة: {}", e);\n                break;\n            }\n        }\n    }\n}\n\nfn main() -> std::io::Result<()> {\n    let listener = TcpListener::bind("127.0.0.1:7878")?;\n    println!("الخادم يعمل على المنفذ 7878");\n\n    for stream in listener.incoming() {\n        match stream {\n            Ok(stream) => {\n                thread::spawn(|| handle_client(stream));\n            }\n            Err(e) => {\n                eprintln!("خطأ في الاتصال: {}", e);\n            }\n        }\n    }\n    Ok(())\n}\n
\n\n

ماذا يحدث هنا؟ TcpListener::bind ينشئ مقبس استماع. الطريقة incoming() ترجع مكرراً (iterator) على الاتصالات الواردة. لكل عميل جديد، نقوم بتشغيل خيط منفصل — thread::spawn. داخل handle_client نقرأ البيانات في مخزن مؤقت ونرسلها مرة أخرى عبر write_all.

\n\n

مهم: Rust يضمن أن TcpStream لن يُستخدم عن طريق الخطأ من خيطين في نفس الوقت — المترجم ببساطة لن يسمح بذلك. هذا هو ما يسمى التزامن بدون خوف (fearless concurrency).

\n\n

مشاكل هذا النهج

\n
    \n
  • التوسع: كل عميل هو خيط منفصل. مع 10,000 عميل، سيعاني نظام التشغيل ببساطة.
  • \n
  • الموارد: مكدس الخيط الافتراضي هو 2 ميجابايت. 10,000 عميل = 20 جيجابايت من الذاكرة.
  • \n
\n\n

هنا تأتي البرمجة غير المتزامنة (asynchronous programming) للإنقاذ.

\n\n

ثورة Async: Tokio و async/await

\n\n

Rust لا يحتوي على بيئة تشغيل غير متزامنة مدمجة (مثل Go أو Node.js). بدلاً من ذلك، يوفر خصائص (traits) Future والكلمات المفتاحية async/await، واختيار بيئة التشغيل (runtime) متروك لك. الأكثر شهرة هو Tokio. إنها ليست مجرد مكتبة، بل نظام بيئي كامل: TCP/UDP، إدخال/إخراج ملفات، مؤقتات، قنوات، وحتى جدولة خاصة بها.

\n\n

دعنا نعيد كتابة خادم الصدى الخاص بنا باستخدام Tokio.

\n\n

خادم الصدى باستخدام Tokio

\n

أضف إلى Cargo.toml:

\n
[dependencies]\ntokio = { version = "1", features = ["full"] }
\n\n

الآن الكود:

\n\n
use tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n                

المدونات

توصيات الكتب