تعلم الصدأ: كيف فعلت UDP الدردشة مع ازول



واصل تعلم الصدأ. ما زلت لا أعرف الكثير ، لذلك ارتكبت الكثير من الأخطاء. آخر مرة حاولت فيها صنع لعبة Snake . جربت الدورات والمجموعات والعمل مع 3D Three.rs . تعلمت عن ggez والجمشت . هذه المرة حاولت إنشاء عميل وخادم للدردشة. لغوي تستخدم ازول . شاهد أيضا كونرود ويو وأوربتك . لقد حاولت multithreading ، والقنوات ، والشبكات. لقد أخذت في الاعتبار أخطاء المقالة السابقة وحاولت جعلها أكثر تفصيلاً. لمزيد من التفاصيل ، مرحبا بكم في القط.

مصادر ، يعمل على ويندوز 10 إلى x64

بالنسبة للشبكات ، استخدمت UDP لأنني أريد أن أجعل مشروعي التالي باستخدام هذا البروتوكول وأردت التدريب عليه. بالنسبة إلى واجهة المستخدم الرسومية ، سرعان ما ذهبت إلى مشاريع google على Rust ، ونظرت في الأمثلة الأساسية لهم وربط Azul بي لأنه يستخدم نموذج كائن مستند ومحرك بنمط CSS ، وقد شاركت في تطوير الويب لفترة طويلة. بشكل عام ، اخترت الإطار ذاتي. هو ، حتى الآن ، في عمق ألفا: التمرير لا يعمل ، لا يعمل تركيز الإدخال ، لا يوجد مؤشر. لإدخال البيانات في حقل النص ، تحتاج إلى التمرير فوقها والاحتفاظ بها فوقها مباشرةً أثناء الكتابة. مزيد من التفاصيل ...

في الواقع ، فإن معظم المقال هو تعليقات رمز.

ازول


إطار واجهة المستخدم الرسومية باستخدام الأسلوب الوظيفي ، DOM ، CSS. تتكون الواجهة الخاصة بك من عنصر الجذر ، الذي يحتوي على العديد من الأحفاد ، والتي يمكن أن يكون لها أحفاد خاصة بهم ، مثل HTML و XML. يتم إنشاء الواجهة بالكامل بناءً على بيانات من DataModel واحد. في ذلك ، يتم نقل جميع البيانات إلى العرض التقديمي بشكل عام. إذا كان أي شخص على دراية بـ ASP.NET ، فإن Azul و DataModel الخاص بهما مثل Razor و ViewModel الخاص به. كما في HTML ، يمكنك ربط الوظائف بأحداث عناصر DOM. يمكنك تصميم عناصر باستخدام إطار CSS. هذا ليس هو نفسه CSS مثل HTML ، لكنه مشابه جدًا له. هناك أيضًا ربط ثنائي الاتجاه كما هو الحال في الزاوي أو MVVM في WPF ، UWP. مزيد من المعلومات على الموقع .

لمحة موجزة عن بقية الأطر


  • Orbtk - تقريبا نفس أزول وأيضا في عمق ألفا
  • Conrod - فيديو يمكنك إنشاء تطبيقات سطح مكتب مشتركة.
  • الطقسوس هو WebAssembly ويشبه React. لتطوير الشبكة.

العملاء


هيكل يتم فيه تجميع الوظائف المساعدة للقراءة والكتابة على المقبس


struct ChatService {} impl ChatService { //1 fn read_data(socket: &Option<UdpSocket>) -> Option<String> { //2 let mut buf = [0u8; 4096]; match socket { Some(s) => { //3 match s.recv(&mut buf) { //4 Ok(count) => Some(String::from_utf8(buf[..count].into()) .expect("can't parse to String")), Err(e) => { //5 println!("Error {}", e); None } } } _ => None, } } //6 fn send_to_socket(message: String, socket: &Option<UdpSocket>) { match socket { //7 Some(s) => { s.send(message.as_bytes()).expect("can't send"); } _ => return, } } } 

  1. قراءة البيانات من المقبس
  2. مخزن مؤقت للبيانات المراد قراءتها من المقبس.
  3. حظر المكالمات. هنا ، يتوقف مؤشر ترابط التنفيذ حتى تتم قراءة البيانات أو حدوث مهلة.
  4. نحصل على سلسلة من صفيف بايت في ترميز UTF8.
  5. وصلنا إلى هنا إذا تمت مقاطعة الاتصال بسبب انتهاء المهلة أو حدث خطأ آخر.
  6. يرسل سلسلة إلى المقبس.
  7. تحويل السلسلة إلى بايت في ترميز UTF8 وإرسال البيانات إلى المقبس. لا تمنع كتابة البيانات إلى المقبس ، أي سوف خيط التنفيذ مواصلة عملها. إذا تعذر إرسال البيانات ، فإننا نقطع البرنامج برسالة "لا يمكن إرسال".

بنية تجمع وظائف للتعامل مع الأحداث من المستخدم وتعديل DataModel الخاص بنا


 struct Controller {} //1 const TIMEOUT_IN_MILLIS: u64 = 2000; impl Controller { //2 fn send_pressed(app_state: &mut azul::prelude::AppState<ChatDataModel>, _event: azul::prelude::WindowEvent<ChatDataModel>) -> azul::prelude::UpdateScreen { //3 let data = app_state.data.lock().unwrap(); //4 let message = data.messaging_model.text_input_state.text.clone(); data.messaging_model.text_input_state.text = "".into(); //5 ChatService::send_to_socket(message, &data.messaging_model.socket); //6 azul::prelude::UpdateScreen::Redraw } //7 fn login_pressed(app_state: &mut azul::prelude::AppState<ChatDataModel>, _event: azul::prelude::WindowEvent<ChatDataModel>) -> azul::prelude::UpdateScreen { //8 use std::time::Duration; //9 if let Some(ref _s) = app_state.data.clone().lock().unwrap().messaging_model.socket { return azul::prelude::UpdateScreen::DontRedraw; } //10 app_state.add_task(Controller::read_from_socket_async, &[]); //11 app_state.add_daemon(azul::prelude::Daemon::unique(azul::prelude::DaemonCallback(Controller::redraw_daemon))); //12 let mut data = app_state.data.lock().unwrap(); //13 let local_address = format!("127.0.0.1:{}", data.login_model.port_input.text.clone().trim()); //14 let socket = UdpSocket::bind(&local_address) .expect(format!("can't bind socket to {}", local_address).as_str()); //15 let remote_address = data.login_model.address_input.text.clone().trim().to_string(); //16 socket.connect(&remote_address) .expect(format!("can't connect to {}", &remote_address).as_str()); //17 socket.set_read_timeout(Some(Duration::from_millis(TIMEOUT_IN_MILLIS))) .expect("can't set time out to read"); // 18 data.logged_in = true; // 19 data.messaging_model.socket = Option::Some(socket); //20 azul::prelude::UpdateScreen::Redraw } //21 fn read_from_socket_async(app_data: Arc<Mutex<ChatDataModel>>, _: Arc<()>) { //22 let socket = Controller::get_socket(app_data.clone()); loop { //23 if let Some(message) = ChatService::read_data(&socket) { //24 app_data.modify(|state| { //25 state.messaging_model.has_new_message = true; //26 state.messaging_model.messages.push(message); }); } } } //27 fn redraw_daemon(state: &mut ChatDataModel, _repres: &mut azul::prelude::Apprepres) -> (azul::prelude::UpdateScreen, azul::prelude::TerminateDaemon) { //28 if state.messaging_model.has_new_message { state.messaging_model.has_new_message = false; (azul::prelude::UpdateScreen::Redraw, azul::prelude::TerminateDaemon::Continue) } else { (azul::prelude::UpdateScreen::DontRedraw, azul::prelude::TerminateDaemon::Continue) } } //29 fn get_socket(app_data: Arc<Mutex<ChatDataModel>>) -> Option<UdpSocket> { //30 let ref_model = &(app_data.lock().unwrap().messaging_model.socket); //31 match ref_model { Some(s) => Some(s.try_clone().unwrap()), _ => None } } } 

  1. المهلة بالميللي ثانية بعدها ستتم مقاطعة عملية حظر القراءة من المقبس.
  2. تتحقق الوظيفة عندما يريد المستخدم إرسال رسالة جديدة إلى الخادم.
  3. نحن نمتلك كائن المزامنة من خلال نموذج البيانات الخاص بنا. هذا بحظر مؤشر ترابط إعادة رسم الواجهة حتى يتم تحرير كائن المزامنة.
  4. نحن نصنع نسخة من النص الذي أدخله المستخدم لنقله إلى أبعد من ذلك ومسح حقل إدخال النص.
  5. نحن نرسل رسالة.
  6. نعلم إطار العمل بأنه بعد معالجة هذا الحدث ، نحتاج إلى إعادة رسم الواجهة.
  7. تعمل الوظيفة عندما يريد المستخدم الاتصال بالخادم.
  8. نقوم بتوصيل الهيكل لتمثيل طول الوقت من المكتبة القياسية.
  9. إذا كنا متصلاً بالفعل بالخادم ، فسنقطع تنفيذ الوظيفة ونخبر إطار العمل بأنه لا توجد حاجة لإعادة رسم الواجهة.
  10. قم بإضافة مهمة سيتم تنفيذها بشكل غير متزامن في سلسلة الرسائل من تجمع مؤشرات الترابط في Azul Framework. يؤدي الوصول إلى كائن المزامنة (mutex) باستخدام نموذج البيانات إلى حظر تحديث واجهة المستخدم حتى يتم تحرير كائن المزامنة (mutex).
  11. أضف مهمة متكررة يتم تشغيلها في الخيط الرئيسي. يتم حظر أي حسابات طويلة في هذا البرنامج الخفي بواسطة تحديث الواجهة.
  12. نحن ندخل في حوزة mutex.
  13. نقرأ المنفذ الذي أدخله المستخدم وإنشاء عنوان محلي بناءً عليه ، وسوف نستمع.
  14. إنشاء مأخذ توصيل UDP يقرأ الحزم التي تصل إلى العنوان المحلي.
  15. نقرأ عنوان الخادم الذي أدخله المستخدم.
  16. نقول مأخذ UDP لدينا لقراءة الحزم فقط من هذا الخادم.
  17. اضبط مهلة عملية القراءة من المقبس. تحدث الكتابة إلى المقبس دون انتظار ، أي أننا فقط نكتب البيانات ولا ننتظر أي شيء ، وعملية القراءة من المقبس تحظر التيار وتنتظر حتى تصل البيانات التي يمكن قراءتها. إذا لم تقم بتعيين مهلة ، فستنتظر عملية القراءة من المقبس إلى أجل غير مسمى.
  18. قم بتعيين إشارة تشير إلى أن المستخدم قد وصل بالفعل إلى الخادم.
  19. نقوم بتمرير مأخذ التوصيل إلى نموذج البيانات.
  20. نعلم إطار العمل بأنه بعد معالجة هذا الحدث ، نحتاج إلى إعادة رسم الواجهة.
  21. عملية غير متزامنة يتم تشغيلها في تجمّع مؤشرات الترابط من Azul Framework.
  22. الحصول على نسخة من المقبس من نموذج البيانات لدينا.
  23. محاولة قراءة البيانات من مأخذ التوصيل. إذا لم تقم بعمل نسخة من المقبس وانتظرت مباشرةً هنا حتى تصل رسالة من المقبس الموجود في كائن المزامنة في نموذج البيانات الخاص بنا ، فسوف تتوقف الواجهة بأكملها ليتم تحديثها حتى نصدر كائن المزامنة.
  24. إذا تلقينا نوعًا من الرسائل ، ثم قمنا بتغيير نموذج البيانات الخاص بنا ، فإن التعديل يقوم بنفس الشيء مثل lock (). Unwrap () بتمرير النتيجة إلى lambda وإطلاق mutex بعد انتهاء رمز lambda.
  25. قم بتعيين إشارة للإشارة إلى أن لدينا رسالة جديدة.
  26. إضافة رسالة إلى مجموعة من جميع رسائل الدردشة.
  27. عملية متكررة متكررة تعمل في الخيط الرئيسي.
  28. إذا كانت لدينا رسالة جديدة ، فإننا نبلغ إطار العمل بأنه يتعين علينا إعادة رسم الواجهة من البداية ومواصلة العمل مع هذا البرنامج الخفي ؛ وإلا ، فلن نرسم الواجهة من البداية ، ولكننا سنستمر في استدعاء هذه الوظيفة في الدورة التالية.
  29. يقوم بإنشاء نسخة من المقبس الخاص بنا حتى لا يحتفظ بميزة mutex مع نموذج البيانات الخاص بنا.
  30. نحصل على mutex ونحصل على رابط للمقبس.
  31. إنشاء نسخة من المقبس. سيتم تحرير Mutex تلقائيًا عند الخروج من الوظيفة.

معالجة البيانات غير المتزامنة والشياطين في أزول


 // Problem - blocks UI :( fn start_connection(app_state: &mut AppState<MyDataModel>, _event: WindowEvent<MyDataModel>) -> UpdateScreen { //   app_state.add_task(start_async_task, &[]); //  app_state.add_daemon(Daemon::unique(DaemonCallback(start_daemon))); UpdateScreen::Redraw } fn start_daemon(state: &mut MyDataModel, _repres: &mut Apprepres) -> (UpdateScreen, TerminateDaemon) { // UI    thread::sleep(Duration::from_secs(10)); state.counter += 10000; (UpdateScreen::Redraw, TerminateDaemon::Continue) } fn start_async_task(app_data: Arc<Mutex<MyDataModel>>, _: Arc<()>) { // simulate slow load app_data.modify(|state| { // UI    thread::sleep(Duration::from_secs(10)); state.counter += 10000; }); } 

يتم تنفيذ البرنامج الخفي دائمًا في الخيط الرئيسي ، لذلك لا مفر من الحظر. بمهمة غير متزامنة ، إذا قمت بذلك ، على سبيل المثال ، مثل هذا ، فلن يكون هناك قفل لمدة 10 ثوانٍ.

 fn start_async_task(app_data: Arc<Mutex<MyDataModel>>, _: Arc<()>) { //  UI.  . thread::sleep(Duration::from_secs(10)); app_data.modify(|state| { state.counter += 10000; }); } 

تقوم وظيفة التعديل باستدعاء lock () و mutex مع نموذج البيانات ، وبالتالي منع تحديث الواجهة طوال مدة تنفيذها.

أساليبنا


 const CUSTOM_CSS: &str = " .row { height: 50px; } .orange { background: linear-gradient(to bottom, #f69135, #f37335); font-color: white; border-bottom: 1px solid #8d8d8d; }"; 

في الواقع ، وظائف لإنشاء DOM لدينا لعرضها على مستخدمها


 impl azul::prelude::Layout for ChatDataModel { //1 fn layout(&self, info: azul::prelude::WindowInfo<Self>) -> azul::prelude::Dom<Self> { //2 if self.logged_in { self.chat_form(info) } else { self.login_form(info) } } } impl ChatDataModel { //3 fn login_form(&self, info: azul::prelude::WindowInfo<Self>) -> azul::prelude::Dom<Self> { //4 let button = azul::widgets::button::Button::with_label("Login") //5 .dom() //6 .with_class("row") //7 .with_class("orange") //8 .with_callback( azul::prelude::On::MouseUp, azul::prelude::Callback(Controller::login_pressed)); //9 let port_label = azul::widgets::label::Label::new("Enter port to listen:") .dom() .with_class("row"); //10 let port = azul::widgets::text_input::TextInput::new() //11 .bind(info.window, &self.login_model.port_input, &self) .dom(&self.login_model.port_input) .with_class("row"); // 9 let address_label = azul::widgets::label::Label::new("Enter server address:") .dom() .with_class("row"); //10 let address = azul::widgets::text_input::TextInput::new() //11 .bind(info.window, &self.login_model.address_input, &self) .dom(&self.login_model.address_input) .with_class("row"); //12 azul::prelude::Dom::new(azul::prelude::NodeType::Div) .with_child(port_label) .with_child(port) .with_child(address_label) .with_child(address) .with_child(button) } //13 fn chat_form(&self, info: azul::prelude::WindowInfo<Self>) -> azul::prelude::Dom<Self> { //14 let button = azul::widgets::button::Button::with_label("Send") .dom() .with_class("row") .with_class("orange") .with_callback(azul::prelude::On::MouseUp, azul::prelude::Callback(Controller::send_pressed)); //15 let text = azul::widgets::text_input::TextInput::new() .bind(info.window, &self.messaging_model.text_input_state, &self) .dom(&self.messaging_model.text_input_state) .with_class("row"); //12 let mut dom = azul::prelude::Dom::new(azul::prelude::NodeType::Div) .with_child(text) .with_child(button); //16 for i in &self.messaging_model.messages { dom.add_child(azul::widgets::label::Label::new(i.clone()).dom().with_class("row")); } dom } } 

  1. الوظيفة التي تنشئ DOM النهائية ، ويتم استدعاؤها في كل مرة تحتاج فيها إلى إعادة رسم الواجهة.
  2. إذا كنا متصلاً بالخادم بالفعل ، فسنعرض النموذج لإرسال الرسائل وقراءتها ، وإلا فإننا نعرض النموذج للاتصال بالخادم.
  3. ينشئ نموذجًا لإدخال البيانات اللازمة للاتصال بالخادم.
  4. إنشاء زر مع النص تسجيل الدخول.
  5. تحويله إلى كائن DOM.
  6. أضف فئة الصف إليها.
  7. أضف css class برتقالي إلى ذلك.
  8. إضافة معالج الأحداث للنقر على الزر.
  9. قم بإنشاء تسمية نص مع نص لعرضه على صف المستخدم و css.
  10. نقوم بإنشاء مربع نص لإدخال نص مع نص من خاصية نموذج صفنا و css.
  11. ربط حقل النص بخاصية DataModel الخاصة بنا. هذا هو اتجاهين ملزمة. الآن تحرير TextInput يغير تلقائيا النص في خاصية نموذجنا والعكس صحيح أيضا. إذا قمنا بتغيير النص في نموذجنا ، فإن النص في TextInput سيتغير.
  12. نقوم بإنشاء عنصر DOM الجذر الذي نضع فيه عناصر واجهة المستخدم الخاصة بنا.
  13. ينشئ نموذجًا لإرسال الرسائل وقراءتها.
  14. قم بإنشاء زر به النص "إرسال" و css باستخدام فئات "الصف" و "البرتقالي" ومعالج الأحداث عند النقر فوقه.
  15. نقوم بإنشاء حقل إدخال نص مع ربط ثنائي الاتجاه مع خاصية النموذج self.messaging_model.text_input_state و css مع الفئة "صف".
  16. أضف ملصقات نصية تعرض الرسائل التي تمت كتابتها في الدردشة.

نموذجنا الذي يخزن حالة واجهة لدينا


تشير وثائق Azul إلى أنه يجب أن تخزن جميع بيانات التطبيق ، بما في ذلك الاتصال بقاعدة البيانات ، لذلك وضعت مقبس UDP فيه.

 //1 #[derive(Debug)] //2 struct ChatDataModel { //3 logged_in: bool, //4 messaging_model: MessagingDataModel, //5 login_model: LoginDataModel, } #[derive(Debug, Default)] struct LoginDataModel { //6 port_input: azul::widgets::text_input::TextInputState, //7 address_input: azul::widgets::text_input::TextInputState, } #[derive(Debug)] struct MessagingDataModel { //8 text_input_state: azul::widgets::text_input::TextInputState, //9 messages: Vec<String>, //10 socket: Option<UdpSocket>, //11 has_new_message: bool, } 

  1. سيتيح لنا ذلك عرض هيكلنا كسلسلة في قالب النموذج {:؟}
  2. نموذج البيانات لدينا. بحيث يمكن استخدامه في ازول. يجب أن تنفذ سمة التخطيط.
  3. إشارة للتحقق مما إذا كان المستخدم متصلاً بالخادم أم لا.
  4. نموذج لعرض نموذج لإرسال الرسائل إلى الخادم وحفظ الرسائل المستلمة من الخادم.
  5. نموذج لعرض النموذج للاتصال بالخادم.
  6. المنفذ الذي أدخله المستخدم. سوف نستمع إليها مع مقبسنا.
  7. عنوان الخادم الذي أدخله المستخدم. سوف نتصل به.
  8. رسالة المستخدم. سوف نرسلها إلى الخادم.
  9. مجموعة من الرسائل التي جاءت من الخادم.
  10. المقبس الذي نتواصل من خلاله مع الخادم.
  11. إشارة للتحقق من وصول رسالة جديدة من الخادم.

وأخيراً ، نقطة الدخول الرئيسية للتطبيق. يبدأ دورة من رسم واجهة المستخدم الرسومية ومعالجة إدخال المستخدم


 pub fn run() { //1 let app = azul::prelude::App::new(ChatDataModel { logged_in: false, messaging_model: MessagingDataModel { text_input_state: azul::widgets::text_input::TextInputState::new(""), messages: Vec::new(), socket: None, has_new_message: false, }, login_model: LoginDataModel::default(), }, azul::prelude::AppConfig::default()); // 2 let mut style = azul::prelude::css::native(); //3 style.merge(azul::prelude::css::from_str(CUSTOM_CSS).unwrap()); //4 let window = azul::prelude::Window::new(azul::prelude::WindowCreateOptions::default(), style).unwrap(); //5 app.run(window).unwrap(); } 

  1. نقوم بإنشاء تطبيق مع بدء البيانات.
  2. الأنماط المستخدمة من قبل التطبيق بشكل افتراضي.
  3. أضف الأنماط الخاصة بهم.
  4. نقوم بإنشاء نافذة سيعرض فيها تطبيقنا.
  5. قم بتشغيل التطبيق في هذه النافذة.

الخادم


نقطة الدخول الرئيسية للتطبيق


هنا لدينا عادة تطبيق وحدة التحكم.

 pub fn run() { //1 let socket = create_socket(); //2 let (sx, rx) = mpsc::channel(); //3 start_sender_thread(rx, socket.try_clone().unwrap()); loop { //4 sx.send(read_data(&socket)).unwrap(); } } 

  1. إنشاء مأخذ.
  2. نقوم بإنشاء قناة أحادية الاتجاه مع مرسل رسالة sx واحد والعديد من المستلمين.
  3. نبدأ في إرسال الرسائل إلى جميع المستلمين في دفق منفصل.
  4. نقرأ البيانات من المقبس ونرسلها إلى ساحة البث ، والتي ترسل رسائل إلى العملاء المتصلين بالخادم.

وظيفة لإنشاء دفق لإرسال الرسائل إلى العملاء


 fn start_sender_thread(rx: mpsc::Receiver<(Vec<u8>, SocketAddr)>, socket: UdpSocket) { //1 thread::spawn(move || { //2 let mut addresses = Vec::<SocketAddr>::new(); //3 loop { //4 let (bytes, pre) = rx.recv().unwrap(); // 5 if !addresses.contains(&pre) { println!(" {} connected to server", pre); addresses.push(pre.clone()); } //6 let result = String::from_utf8(bytes) .expect("can't parse to String") .trim() .to_string(); println!("received {} from {}", result, pre); //7 let message = format!("FROM: {} MESSAGE: {}", pre, result); let data_to_send = message.as_bytes(); //8 addresses .iter() .for_each(|s| { //9 socket.send_to(data_to_send, s) //10 .expect(format!("can't send to {}", pre).as_str()); }); } }); } 

  1. بدء موضوع جديد. التحرك يعني أن المتغيرات تأخذ على لامدا والتدفق ، على التوالي. وبشكل أكثر تحديدًا ، فإن موضوعنا الجديد سوف "يمتص" المتغيرات rx والمقبس.
  2. مجموعة من العناوين المتصلة بنا من قبل العملاء. سوف نرسل كل منهم رسائلنا. بشكل عام ، في مشروع حقيقي ، سيكون من الضروري القيام بمعالجة فصل العميل عنا وإزالة عنوانه من هذه المجموعة.
  3. نبدأ حلقة لا نهائية.
  4. نقرأ البيانات من القناة. سيتم حظر الدفق هنا حتى وصول بيانات جديدة.
  5. إذا لم يكن هناك مثل هذا العنوان في صفيفنا ، فأضفه إلى هناك.
  6. فك شفرة سلسلة UTF8 من صفيف بايت.
  7. نقوم بإنشاء مجموعة من وحدات البايت التي سنرسلها إلى جميع عملائنا.
  8. نذهب من خلال جمع العناوين وإرسال البيانات إلى الجميع.
  9. عملية الكتابة على مقبس UDP غير قابلة للحظر ، لذلك لن تنتظر الوظيفة حتى تصل الرسالة إلى المستلم ويتم تنفيذها على الفور تقريبًا.
  10. نتوقع في حالة وجود خطأ سيجعل الخروج في حالات الطوارئ من البرنامج مع رسالة معينة.

وظيفة يخلق مأخذ بناء على إدخال المستخدم


 const TIMEOUT_IN_MILLIS: u64 = 2000; fn create_socket() -> UdpSocket { println!("Enter port to listen"); //1 let local_port: String = read!("{}\n"); let local_address = format!("127.0.0.1:{}", local_port.trim()); println!("server address {}", &local_address); //2 let socket = UdpSocket::bind(&local_address.trim()) .expect(format!("can't bind socket to {}", &local_address).as_str()); //3 socket.set_read_timeout(Some(Duration::from_millis(TIMEOUT_IN_MILLIS))) .expect("can't set time out to read"); //4 socket } 

  1. نقرأ المنفذ الذي سيستمع إليه خادمنا وننشئ عنوان خادم محلي بناءً عليه.
  2. إنشاء مأخذ توصيل UDP الاستماع على هذا العنوان.
  3. اضبط مهلة عملية القراءة. تم حظر عملية القراءة وسوف تمنع البث حتى وصول بيانات جديدة أو حدوث مهلة.
  4. نعود مأخذ خلق من وظيفة.
  5. تقوم الوظيفة بقراءة البيانات من المقبس وإرجاعها مع عنوان المرسل.

وظيفة لقراءة البيانات من المقبس


 fn read_data(socket: &UdpSocket) -> (Vec<u8>, SocketAddr) { //1 let mut buf = [0u8; 4096]; //2 loop { match socket.recv_from(&mut buf) { //3 Ok((count, address)) => { //4 return (buf[..count].into(), address); } //5 Err(e) => { println!("Error {}", e); continue; } }; } } 

  1. المخزن المؤقت هو المكان الذي سنقرأ فيه البيانات.
  2. يبدأ حلقة ستعمل حتى يتم قراءة البيانات الصحيحة.
  3. نحصل على عدد البايتات المقروءة وعنوان المرسل.
  4. نقطع الصفيف من بدايته إلى عدد البايتات المقروءة ونحولها إلى ناقل البايت.
  5. في حالة حدوث مهلة أو خطأ آخر ، انتقل إلى التكرار التالي للحلقة.

حول الطبقات في التطبيق


Offtopic: برنامج تعليمي صغير لمدة يومين في العمل. قررت أن أضعها هنا ، ربما سيأتي شخص ما في متناول يدي. شعراء يونيو الحادون هم أمثلة في C # ونحن نتحدث عن ASP.NET
لذلك ، لم يكن هناك شيء أقوم به ، لقد كان في المساء ، وقررت أن أكتب برنامجًا تعليميًا صغيرًا عن الهندسة المعمارية لأرتيم وفيكتور. حسنا ، دعنا نذهب.

في الواقع ، لقد أضفت هنا لأن استعادة الوضع يمكنني فقط كتابة المقالات مرة واحدة في الأسبوع ، والمواد موجودة بالفعل والأسبوع القادم أردت تحميل شيء آخر إلى Habr.

عادة ، يتم تطبيق الطبقات. في كل طبقة ، توجد كائنات تنفذ خاصية سلوك الطبقة التي توجد فيها. و كذلك. هذه هي الطبقات.

  1. طبقة العرض.
  2. منطق عمل الطبقة.
  3. طبقة الوصول إلى البيانات.
  4. الكيانات (المستخدم ، الحيوان ، إلخ)


يمكن أن تحتوي كل طبقة على DTO وفئات تعسفية تمامًا مع طرق تعسفية. الشيء الرئيسي هو أنهم يؤدون الوظيفة المرتبطة بالطبقة التي يوجدون فيها. في التطبيقات البسيطة ، قد تكون بعض الطبقات مفقودة. على سبيل المثال ، يمكن تنفيذ طريقة عرض الطبقة من خلال نموذج MVC أو MVP أو MVVM. وهو اختياري تماما. الشيء الرئيسي هو أن الفئات الموجودة في هذه الطبقة تنفذ الوظيفة المعينة للطبقة. تذكر أن الأنماط والعمارة مجرد توصيات وليست اتجاهات. نمط والهندسة المعمارية ليست قانونا ، هذه هي النصيحة.

وهكذا ، سوف نأخذ بعين الاعتبار كل طبقة في مثال تطبيق ASP.NET قياسي باستخدام Entity Framework القياسي.

طبقة العرض


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

طبقة منطق الأعمال


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

طبقة الوصول إلى البيانات


عادة هنا EF تنفذ وحدة العمل وأنماط مستودع. لذا ، نعم ، DbContext هو ، يمكنك القول ، وحدة العمل ، و DB تعيّن أنها مستودع. هذا ، في الواقع ، هو المكان الذي نضع فيه البيانات ومن أين نحصل عليها. بغض النظر عما إذا كان مصدر البيانات هو قاعدة بيانات ، أو واجهة برمجة تطبيقات لتطبيق آخر ، أو ذاكرة تخزين مؤقت في الذاكرة ، أو مجرد نوع من مولد الأرقام العشوائية. أي مصدر بيانات.

الكيانات


نعم ، فقط كل أنواع المستخدم ، الحيوان ، وأكثر من ذلك. نقطة مهمة واحدة - قد يكون لديهم نوع من السلوك يميزهم فقط. على سبيل المثال:

 class User { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return FirstName + " " + LastName; } } public bool Equal(User user) { return this.FullName == user.FullName; } } 

حسنا ، ومثال بسيط جدا. كان شوبا


 using System; using System.Collections.Generic; using System.Text; //Entities class User { public int Id { get; set; } public string Name { get; set; } } //Data Access Layer class UserRepository { private readonly Dictionary<int, User> _db; public UserRepository() { _db = new Dictionary<int, User>(); } public User Get(int id) { return _db[id]; } public void Save(User user) { _db[user.Id] = user; } } //Business Logic Layer class UserService { private readonly UserRepository _repo; private int _currentId = 0; public UserService() { _repo = new UserRepository(); } public void AddNew() { _currentId++; var user = new User { Id = _currentId, Name = _currentId.ToString() }; _repo.Save(user); } public string GetAll() { StringBuilder sb = new StringBuilder(); for (int i = 1; i <= _currentId; i++) { sb.AppendLine($"Id: {i} Name: {_repo.Get(i).Name}"); } return sb.ToString(); } } //presentation Layer aka Application Layer class UserController { private readonly UserService _service; public UserController() { _service = new UserService(); } public string RunExample() { _service.AddNew(); _service.AddNew(); return _service.GetAll(); } } namespace ConsoleApp1 { class Program { static void Main(string[] args) { var controller = new UserController(); Console.WriteLine(controller.RunExample()); Console.ReadLine(); } } } 


PS


حسنًا ، أريد أن أقول ذلك بفضل Nastya على تصحيح الأخطاء النحوية في المقالة. نعم ، نعم ، أنت لست عبثًا بشهادة حمراء ودافئة بشكل عام. أحبك <3.

Source: https://habr.com/ru/post/ar433624/


All Articles