Learning Rust: Wie ich UDP-Chat mit Azul gemacht habe



Ich lerne weiter Rust. Ich weiß immer noch nicht viel, deshalb mache ich viele Fehler. Das letzte Mal habe ich versucht, ein Snake- Spiel zu machen. Ich habe Zyklen und Sammlungen ausprobiert und mit 3D Three.rs gearbeitet . Ich habe etwas über Ggez und Amethyst gelernt. Diesmal habe ich versucht, einen Client und einen Server für den Chat zu erstellen. Für GUI verwendet Azul . Sah auch Conrod , Yew und Orbtk . Ich habe Multithreading, Kanäle und Networking ausprobiert. Ich habe die Fehler des vorherigen Artikels berücksichtigt und versucht, dies detaillierter zu gestalten. Für Details willkommen bei Katze.

Quellen, funktioniert unter Windows 10 x64

Für die Vernetzung habe ich UDP verwendet, weil ich mein nächstes Projekt mit diesem Protokoll erstellen und hier damit trainieren wollte. Für die GUI habe ich schnell Projekte auf Rust googelt, mir die grundlegenden Beispiele für sie angesehen und Azul hat mich begeistert, weil sie ein Dokumentobjektmodell und eine Engine im CSS-Stil verwendet, und ich war lange Zeit mit der Webentwicklung beschäftigt. Im Allgemeinen habe ich das Framework subjektiv gewählt. Bisher ist es in Deep Alpha: Scrollen funktioniert nicht, Eingabefokus funktioniert nicht, es gibt keinen Cursor. Um Daten in ein Textfeld einzugeben, müssen Sie den Mauszeiger darüber halten und sie während der Eingabe direkt darüber halten. Weitere Details ...

Tatsächlich besteht der größte Teil des Artikels aus Codekommentaren.

Azul


GUI-Framework mit funktionalem Stil, DOM, CSS. Ihre Schnittstelle besteht aus einem Stammelement mit vielen Nachkommen, die ihre eigenen Nachkommen haben können, z. B. in HTML und XML. Die gesamte Schnittstelle wird basierend auf Daten aus einem einzelnen DataModel erstellt. Darin werden im Allgemeinen alle Daten zur Präsentation übertragen. Wenn jemand mit ASP.NET vertraut ist, sind Azul und sein DataModel wie Razor und sein ViewModel. Wie in HTML können Sie Funktionen an Ereignisse von DOM-Elementen binden. Sie können Elemente mithilfe des CSS-Frameworks formatieren. Dies ist nicht dasselbe CSS wie HTML, aber sehr ähnlich. Es gibt auch eine bidirektionale Bindung wie in Angular oder MVVM in WPF, UWP. Weitere Informationen auf der Website .

Ein kurzer Überblick über den Rest der Frameworks


  • Orbtk - Fast das gleiche wie Azul und auch in Deep Alpha
  • Conrod - Video Sie können plattformübergreifende Desktop-Anwendungen erstellen.
  • Yew ist WebAssembly und ähnelt React. Für die Webentwicklung.

Kunde


Die Struktur, in der Hilfsfunktionen zum Lesen und Schreiben in einen Socket gruppiert sind


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. Lesen Sie Daten aus der Steckdose
  2. Puffer für Daten, die aus dem Socket gelesen werden sollen.
  3. Anruf blockieren. Hier stoppt der Ausführungsthread, bis Daten gelesen werden oder eine Zeitüberschreitung auftritt.
  4. Wir erhalten eine Zeichenfolge aus einem Byte-Array in UTF8-Codierung.
  5. Wir kommen hierher, wenn die Verbindung durch ein Timeout unterbrochen wurde oder ein anderer Fehler aufgetreten ist.
  6. Sendet eine Zeichenfolge an einen Socket.
  7. Konvertieren Sie die Zeichenfolge in Bytes in UTF8-Codierung und senden Sie die Daten an den Socket. Das Schreiben von Daten in einen Socket blockiert nicht, d.h. Der Ausführungsthread setzt seine Arbeit fort. Wenn die Daten nicht gesendet werden konnten, unterbrechen wir das Programm mit der Meldung "Kann nicht senden".

Eine Struktur, die Funktionen zum Behandeln von Ereignissen des Benutzers und zum Ändern unseres DataModel gruppiert


 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. Das Zeitlimit in Millisekunden, nach dem der Blockiervorgang des Lesens vom Socket unterbrochen wird.
  2. Die Funktion wird erfüllt, wenn der Benutzer eine neue Nachricht an den Server senden möchte.
  3. Wir nehmen den Mutex mit unserem Datenmodell in Besitz. Dies blockiert den Schnittstellen-Thread zum erneuten Zeichnen der Schnittstelle, bis der Mutex freigegeben wird.
  4. Wir erstellen eine Kopie des vom Benutzer eingegebenen Textes, um ihn weiter zu übertragen und das Texteingabefeld zu löschen.
  5. Wir senden eine Nachricht.
  6. Wir informieren das Framework, dass wir nach der Verarbeitung dieses Ereignisses die Schnittstelle neu zeichnen müssen.
  7. Die Funktion funktioniert, wenn der Benutzer eine Verbindung zum Server herstellen möchte.
  8. Wir verbinden die Struktur, um die Zeitdauer aus der Standardbibliothek darzustellen.
  9. Wenn wir bereits mit dem Server verbunden sind, unterbrechen wir die Ausführung der Funktion und teilen dem Framework mit, dass die Schnittstelle nicht neu gezeichnet werden muss.
  10. Fügen Sie eine Aufgabe hinzu, die asynchron im Thread aus dem Thread-Pool des Azul Framework ausgeführt wird. Der Zugriff auf den Mutex mit dem Datenmodell blockiert die Aktualisierung der Benutzeroberfläche, bis der Mutex freigegeben wird.
  11. Fügen Sie eine wiederkehrende Aufgabe hinzu, die im Hauptthread ausgeführt wird. Alle langwierigen Berechnungen in diesem Daemon werden durch die Schnittstellenaktualisierung blockiert.
  12. Wir gelangen in den Besitz des Mutex.
  13. Wir lesen den vom Benutzer eingegebenen Port und erstellen darauf basierend eine lokale Adresse. Wir werden abhören.
  14. Erstellen Sie einen UDP-Socket, der Pakete liest, die an der lokalen Adresse ankommen.
  15. Wir lesen die vom Benutzer eingegebene Serveradresse.
  16. Wir weisen unseren UDP-Socket an, nur Pakete von diesem Server zu lesen.
  17. Stellen Sie das Zeitlimit für den Lesevorgang am Socket ein. Das Schreiben in den Socket erfolgt ohne Wartezeit, dh wir schreiben einfach Daten und erwarten nichts. Der Lesevorgang vom Socket blockiert den Stream und wartet, bis die lesbaren Daten eintreffen. Wenn Sie kein Timeout festlegen, wartet der Lesevorgang vom Socket auf unbestimmte Zeit.
  18. Setzen Sie ein Flag, das angibt, dass der Benutzer bereits eine Verbindung zum Server hergestellt hat.
  19. Wir übergeben den erstellten Socket an das Datenmodell.
  20. Wir informieren das Framework, dass wir nach der Verarbeitung dieses Ereignisses die Schnittstelle neu zeichnen müssen.
  21. Eine asynchrone Operation, die im Thread-Pool des Azul Framework ausgeführt wird.
  22. Holen Sie sich eine Kopie des Sockets aus unserem Datenmodell.
  23. Es wird versucht, Daten aus einem Socket zu lesen. Wenn Sie keine Kopie des Sockets erstellen und hier direkt warten, bis eine Nachricht von dem Socket eingeht, der sich in unserem Datenmodell im Mutex befindet, wird die gesamte Schnittstelle nicht mehr aktualisiert, bis wir den Mutex freigeben.
  24. Wenn wir eine Nachricht erhalten, ändert das Ändern unseres Datenmodells und Ändern dasselbe wie lock (). Unwrap (), indem das Ergebnis an das Lambda übergeben und der Mutex nach dem Ende des Lambda-Codes freigegeben wird.
  25. Setzen Sie ein Flag, um anzuzeigen, dass wir eine neue Nachricht haben.
  26. Fügen Sie dem Array aller Chat-Nachrichten eine Nachricht hinzu.
  27. Eine sich wiederholende synchrone Operation, die im Haupt-Thread ausgeführt wird.
  28. Wenn wir eine neue Nachricht haben, teilen wir dem Framework mit, dass wir die Schnittstelle von Grund auf neu zeichnen und mit diesem Dämon weiterarbeiten müssen. Andernfalls wird die Schnittstelle nicht von Anfang an gezeichnet, sondern diese Funktion im nächsten Zyklus aufgerufen.
  29. Erstellt eine Kopie unseres Sockets, um den Mutex nicht mit unserem Datenmodell zu sperren.
  30. Wir bekommen den Mutex und bekommen einen Link zum Socket.
  31. Erstellen Sie eine Kopie des Sockets. Mutex wird beim Beenden einer Funktion automatisch freigegeben.

Asynchrone Datenverarbeitung und Daemons in Azul


 // 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; }); } 

Der Daemon wird immer im Hauptthread ausgeführt, sodass dort ein Blockieren unvermeidlich ist. Wenn Sie dies beispielsweise bei einer asynchronen Aufgabe tun, wird 10 Sekunden lang keine Sperre angezeigt.

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

Die Änderungsfunktion ruft lock () auf und der Mutex mit dem Datenmodell blockiert daher die Aktualisierung der Schnittstelle für die Dauer ihrer Ausführung.

Unsere Stile


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

Tatsächlich Funktionen zum Erstellen unseres DOM, die dem Benutzer angezeigt werden sollen


 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. Die Funktion, die das endgültige DOM erstellt und jedes Mal aufgerufen wird, wenn Sie die Schnittstelle neu zeichnen müssen.
  2. Wenn wir bereits mit dem Server verbunden sind, wird das Formular zum Senden und Lesen von Nachrichten angezeigt. Andernfalls wird das Formular zum Herstellen einer Verbindung zum Server angezeigt.
  3. Erstellt ein Formular zur Eingabe der Daten, die für die Verbindung zum Server erforderlich sind.
  4. Erstellen Sie eine Schaltfläche mit der Textbeschriftung Login.
  5. Konvertieren Sie es in ein DOM-Objekt.
  6. Fügen Sie die Zeilenklasse hinzu.
  7. Fügen Sie die CSS-Klasse Orange hinzu.
  8. Fügen Sie einen Ereignishandler hinzu, um auf die Schaltfläche zu klicken.
  9. Erstellen Sie eine Textbeschriftung mit Text, der dem Benutzer und der CSS-Klassenzeile angezeigt werden soll.
  10. Wir erstellen ein Textfeld für die Eingabe von Text mit Text aus der Eigenschaft unserer Modell- und CSS-Klassenzeile.
  11. Binden Sie das Textfeld an die Eigenschaft unseres DataModel. Dies ist eine Zwei-Wege-Bindung. Wenn Sie jetzt TextInput bearbeiten, wird der Text in der Eigenschaft unseres Modells automatisch geändert, und das Gegenteil ist auch der Fall. Wenn wir den Text in unserem Modell ändern, ändert sich der Text in TextInput.
  12. Wir erstellen ein Root-DOM-Element, in das wir unsere UI-Elemente einfügen.
  13. Erstellt ein Formular zum Senden und Lesen von Nachrichten.
  14. Erstellen Sie eine Schaltfläche mit dem Text "Senden" und CSS mit den Klassen "Zeile", "Orange" und einem Ereignishandler, wenn Sie darauf klicken.
  15. Wir erstellen ein Texteingabefeld mit bidirektionaler Bindung mit der Modelleigenschaft self.messaging_model.text_input_state und css mit der Klasse "row".
  16. Fügen Sie Textbeschriftungen hinzu, die Nachrichten anzeigen, die im Chat geschrieben wurden.

Unser Modell, das den Status unserer Schnittstelle speichert


In der Azul-Dokumentation heißt es, dass alle Anwendungsdaten, einschließlich der Verbindung zur Datenbank, gespeichert werden sollen. Daher habe ich einen UDP-Socket eingefügt.

 //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. Auf diese Weise können wir unsere Struktur als Zeichenfolge in einer Vorlage des Formulars {:?} Anzeigen.
  2. Unser Datenmodell. Damit es in Azul verwendet werden kann. Sie muss das Layout-Merkmal implementieren.
  3. Ein Flag, mit dem überprüft wird, ob der Benutzer mit dem Server verbunden ist oder nicht.
  4. Ein Modell zum Anzeigen eines Formulars zum Senden von Nachrichten an den Server und zum Speichern von vom Server empfangenen Nachrichten.
  5. Modell zum Anzeigen des Formulars für die Verbindung zum Server.
  6. Der Port, den der Benutzer eingegeben hat. Wir werden es mit unserer Steckdose hören.
  7. Die vom Benutzer eingegebene Serveradresse. Wir werden uns damit verbinden.
  8. Benutzermeldung. Wir werden es an den Server senden.
  9. Ein Array von Nachrichten, die vom Server kamen.
  10. Der Socket, über den wir mit dem Server kommunizieren.
  11. Ein Flag, mit dem überprüft wird, ob eine neue Nachricht vom Server eingegangen ist.

Und schließlich der Haupteinstiegspunkt für die Anwendung. Startet einen Zyklus aus der GUI-Zeichnung und der Verarbeitung von Benutzereingaben


 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. Wir erstellen eine Anwendung mit Startdaten.
  2. Die von der Anwendung standardmäßig verwendeten Stile.
  3. Fügen Sie ihnen unsere eigenen Stile hinzu.
  4. Wir erstellen ein Fenster, in dem unsere Anwendung angezeigt wird.
  5. Starten Sie die Anwendung in diesem Fenster.

Server


Der Haupteinstiegspunkt für die Anwendung


Hier haben wir normalerweise eine Konsolenanwendung.

 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. Erstellen Sie einen Socket.
  2. Wir erstellen einen Einwegkanal mit einem SX-Nachrichtensender und vielen RX-Empfängern.
  3. Wir senden Nachrichten an alle Empfänger in einem separaten Stream.
  4. Wir lesen die Daten vom Socket und senden sie an den Stream, der Nachrichten an mit dem Server verbundene Clients sendet.

Funktion zum Erstellen eines Streams zum Senden von Nachrichten an Clients


 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. Starten Sie einen neuen Thread. Verschieben bedeutet, dass die Variablen das Lambda bzw. den Fluss übernehmen. Insbesondere wird unser neuer Thread die RX- und Socket-Variablen "absorbieren".
  2. Eine Sammlung von Adressen, die von Kunden mit uns verbunden wurden. Wir werden allen unsere Nachrichten senden. Im Allgemeinen wäre es in einem realen Projekt erforderlich, einen Client von uns zu trennen und seine Adresse aus diesem Array zu entfernen.
  3. Wir starten eine Endlosschleife.
  4. Wir lesen die Daten aus dem Kanal. Hier wird der Stream blockiert, bis neue Daten eintreffen.
  5. Wenn unser Array keine solche Adresse enthält, fügen Sie sie dort hinzu.
  6. Dekodieren Sie eine UTF8-Zeichenfolge aus einem Byte-Array.
  7. Wir erstellen eine Reihe von Bytes, die wir an alle unsere Kunden senden werden.
  8. Wir gehen die Adressensammlung durch und senden Daten an alle.
  9. Der Schreibvorgang in den UDP-Socket ist nicht blockierend, daher wartet die Funktion hier nicht, bis die Nachricht beim Empfänger eintrifft und fast sofort ausgeführt wird.
  10. Erwarten Sie im Fehlerfall einen Notausgang aus dem Programm mit der angegebenen Meldung.

Die Funktion erstellt einen Socket basierend auf Benutzereingaben


 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. Wir lesen den Port, den unser Server abhört, und erstellen darauf basierend eine lokale Serveradresse.
  2. Erstellen Sie einen UDP-Socket, der diese Adresse überwacht.
  3. Stellen Sie das Zeitlimit für den Lesevorgang ein. Der Lesevorgang blockiert und blockiert den Stream, bis neue Daten eintreffen oder eine Zeitüberschreitung auftritt.
  4. Wir geben den erstellten Socket von der Funktion zurück.
  5. Die Funktion liest Daten aus dem Socket und gibt sie zusammen mit der Absenderadresse zurück.

Funktion zum Lesen von Daten aus einem Socket


 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. Der Puffer ist der Ort, an dem wir die Daten lesen werden.
  2. Startet eine Schleife, die ausgeführt wird, bis gültige Daten gelesen werden.
  3. Wir erhalten die Anzahl der gelesenen Bytes und die Absenderadresse.
  4. Wir schneiden das Array von Anfang an auf die Anzahl der gelesenen Bytes und konvertieren es in einen Bytevektor.
  5. Wenn ein Timeout oder ein anderer Fehler auftritt, fahren Sie mit der nächsten Iteration der Schleife fort.

Informationen zu Ebenen in der Anwendung


Offtopic: Ein kleines Bildungsprogramm für zwei Juni bei der Arbeit. Ich habe beschlossen, es hier zu platzieren, vielleicht wird jemand nützlich sein. June Sharp-Poets sind Beispiele in C # und wir sprechen über ASP.NET
Es gab also nichts zu tun, es war am Abend, und ich beschloss, ein kleines Bildungsprogramm über Architektur für Artem und Victor zu schreiben. Nun, lass uns gehen.

Eigentlich habe ich hier hinzugefügt, weil der Modus Aufklärung ist und ich nur einmal pro Woche Artikel schreiben kann und das Material bereits da ist und ich nächste Woche etwas anderes auf Habr hochladen wollte.

In der Regel wird eine Anwendung geschichtet. In jeder Ebene gibt es Objekte, die die Verhaltenscharakteristik der Ebene implementieren, in der sie sich befinden. Und so. Das sind die Schichten.

  1. Präsentationsschicht.
  2. Layer-Geschäftslogik.
  3. Datenzugriffsschicht.
  4. Entitäten (Benutzer, Tier usw.)


Jede Schicht kann ein eigenes DTO und vollständig beliebige Klassen mit beliebigen Methoden enthalten. Die Hauptsache ist, dass sie die Funktionalität ausführen, die der Ebene zugeordnet ist, in der sie sich befinden. In einfachen Anwendungen fehlen möglicherweise einige der Ebenen. Beispielsweise kann eine Ebenenansicht über das MVC-, MVP- und MVVM-Muster implementiert werden. Welches ist völlig optional. Die Hauptsache ist, dass die Klassen in dieser Schicht die der Schicht zugewiesene Funktionalität implementieren. Denken Sie daran, Muster und Architektur sind nur Empfehlungen, keine Anweisungen. Muster und Architektur sind kein Gesetz, dies ist ein Ratschlag.

Daher betrachten wir jede Ebene am Beispiel einer Standard-ASP.NET-Anwendung unter Verwendung des Standard-Entity-Frameworks.

Präsentationsschicht


Wir haben MVC hier. Dies ist die Ebene, die die Benutzerinteraktion ermöglicht. Befehle kommen hierher und Benutzer erhalten Daten von hier. Nicht unbedingt Menschen, wenn wir eine API haben, dann ist unser Benutzer ein anderes Programm. Autos kommunizieren mit Autos.

Geschäftslogikschicht


Hier werden Klassen normalerweise als Service bezeichnet, z. B. UserService, obwohl es sich um alles handeln kann. Nur eine Reihe von Klassen mit Methoden. Hauptsache, hier finden die Berechnungen und Berechnungen unserer Anwendung statt. Dies ist die dickste und sperrigste Schicht. Es gibt den größten Teil des Codes und verschiedene Klassen. Dies ist in der Tat unsere Anwendung.

Datenzugriffsschicht


Normalerweise implementiert EF hier Unit Of Work- und Repository-Muster. Also ja, DbContext ist, sagen Sie, Unit Of Work, und DB legt fest, dass es sich um ein Repository handelt. Dies ist in der Tat der Ort, an dem wir die Daten ablegen und von dem wir sie beziehen. Unabhängig davon, ob die Datenquelle eine Datenbank, eine API einer anderen Anwendung, ein Cache im Speicher oder nur eine Art Zufallszahlengenerator ist. Beliebige Datenquelle.

Entitäten


Ja, nur alle Arten von Benutzern, Tieren und mehr. Ein wichtiger Punkt - sie haben möglicherweise nur ein Verhalten, das für sie charakteristisch ist. Zum Beispiel:

 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; } } 

Nun, und ein sehr einfaches Beispiel. Shoba war


 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


Nun, sho, ich möchte mich bei meiner Nastya für die Behebung von Grammatikfehlern im Artikel bedanken. Also ja, Nastya, du bist nicht umsonst mit einem roten Diplom und im Allgemeinen cool. Ich liebe dich <3.

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


All Articles