Learning Rust: Bagaimana saya melakukan UDP mengobrol dengan Azul



Saya terus belajar Rust. Saya masih belum tahu banyak, jadi saya membuat banyak kesalahan. Terakhir kali saya mencoba membuat game Snake . Saya mencoba siklus, koleksi, bekerja dengan 3D Three.rs . Belajar tentang ggez dan Amethyst . Kali ini saya mencoba membuat klien dan server untuk mengobrol. Untuk GUI digunakan Azul . Juga menyaksikan Conrod , Yew dan Orbtk . Saya mencoba multithreading, saluran, dan jaringan. Saya memperhitungkan kesalahan-kesalahan dari artikel sebelumnya dan mencoba membuatnya lebih terperinci. Untuk detailnya, selamat datang di kucing.

Sumber, bekerja di Windows 10 x64

Untuk jaringan, saya menggunakan UDP karena saya ingin membuat proyek saya berikutnya menggunakan protokol ini dan saya ingin berlatih dengannya di sini. Untuk GUI, saya segera membuka proyek Google di Rust, melihat contoh dasar untuk mereka dan Azul mengaitkan saya karena menggunakan Model Objek Dokumen dan mesin gaya CSS, dan saya terlibat dalam pengembangan web untuk waktu yang lama. Secara umum, saya memilih Framework secara subyektif. Sejauh ini, dalam alfa dalam: pengguliran tidak bekerja, fokus input tidak berfungsi, tidak ada kursor. Untuk memasukkan data dalam bidang teks, Anda harus mengarahkan kursor dan memegangnya langsung di atasnya saat Anda mengetik. Lebih detail ...

Sebenarnya, sebagian besar artikel adalah komentar kode.

Azul


Kerangka kerja GUI menggunakan gaya fungsional, DOM, CSS. Antarmuka Anda terdiri dari elemen root, yang memiliki banyak keturunan, yang dapat memiliki keturunan sendiri, seperti dalam HTML dan XML. Seluruh antarmuka dibuat berdasarkan data dari satu DataModel tunggal. Di dalamnya, semua data ditransfer ke presentasi secara umum. Jika ada yang akrab dengan ASP.NET, maka Azul dan DataModelnya seperti Razor dan ViewModel-nya. Seperti dalam HTML, Anda dapat mengikat fungsi ke acara elemen DOM. Anda bisa mendesain elemen menggunakan kerangka CSS. Ini bukan CSS yang sama dengan HTML, tetapi sangat mirip dengannya. Ada juga penjilidan dua arah seperti pada Angular atau MVVM di WPF, UWP. Informasi lebih lanjut di situs .

Tinjauan singkat tentang sisa kerangka kerja


  • Orbtk - Hampir sama dengan Azul dan juga dalam alpha yang dalam
  • Conrod - Video Anda dapat membuat aplikasi desktop lintas platform.
  • Yew adalah WebAssembly dan mirip dengan Bereaksi. Untuk pengembangan web.

Pelanggan


Struktur di mana fungsi bantu untuk membaca dan menulis ke soket dikelompokkan


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. Baca data dari soket
  2. Buffer agar data dapat dibaca dari soket.
  3. Memblokir panggilan. Di sini, utas eksekusi berhenti sampai data dibaca atau terjadi timeout.
  4. Kami mendapatkan string dari array byte dalam pengkodean UTF8.
  5. Kami tiba di sini jika koneksi terputus oleh batas waktu atau kesalahan lain terjadi.
  6. Mengirim string ke soket.
  7. Konversi string ke byte dalam pengkodean UTF8 dan kirim data ke soket. Menulis data ke soket tidak menghalangi, mis. utas eksekusi akan melanjutkan pekerjaannya. Jika data tidak dapat dikirim, maka kami mengganggu program dengan pesan "tidak dapat mengirim".

Struktur yang mengelompokkan fungsi untuk menangani acara dari pengguna dan memodifikasi DataModel kami


 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. Batas waktu dalam milidetik setelah itu operasi pemblokiran pembacaan dari soket akan terputus.
  2. Fungsi terpenuhi ketika pengguna ingin mengirim pesan baru ke server.
  3. Kami memiliki mutex dengan model data kami. Ini memblok antarmuka redraw thread sampai mutex dibebaskan.
  4. Kami membuat salinan teks yang dimasukkan oleh pengguna untuk mentransfernya lebih lanjut dan menghapus bidang input teks.
  5. Kami mengirim pesan.
  6. Kami memberi tahu Kerangka bahwa setelah memproses acara ini, kami perlu menggambar ulang antarmuka.
  7. Fungsi ini berfungsi ketika pengguna ingin terhubung ke server.
  8. Kami menghubungkan struktur untuk mewakili lamanya waktu dari perpustakaan standar.
  9. Jika kami sudah terhubung ke server, kami mengganggu pelaksanaan fungsi dan memberi tahu Kerangka bahwa tidak perlu menggambar ulang antarmuka.
  10. Tambahkan tugas yang akan dijalankan secara asinkron di utas dari kumpulan utas dari Azul Framework. Mengakses mutex dengan blok data model memperbarui UI sampai mutex dibebaskan.
  11. Tambahkan tugas berulang yang berjalan di utas utama. Setiap perhitungan panjang dalam daemon ini diblokir oleh pembaruan antarmuka.
  12. Kami masuk ke kepemilikan mutex.
  13. Kami membaca port yang dimasukkan oleh pengguna dan membuat alamat lokal berdasarkan itu, kami akan mendengarkan.
  14. Buat soket UDP yang bertuliskan paket yang tiba di alamat lokal.
  15. Kami membaca alamat server yang dimasukkan oleh pengguna.
  16. Kami memberi tahu soket UDP kami untuk membaca paket hanya dari server ini.
  17. Atur batas waktu untuk operasi baca dari soket. Menulis ke soket terjadi tanpa menunggu, yaitu, kami hanya menulis data dan tidak mengharapkan apa-apa, dan operasi membaca dari soket memblokir aliran dan menunggu sampai data yang dapat dibaca tiba. Jika Anda tidak menetapkan batas waktu, maka operasi baca dari soket akan menunggu tanpa batas waktu.
  18. Tetapkan tanda yang menunjukkan bahwa pengguna telah terhubung ke server.
  19. Kami meneruskan soket yang dibuat ke model data.
  20. Kami memberi tahu Kerangka bahwa setelah memproses acara ini, kami perlu menggambar ulang antarmuka.
  21. Operasi asinkron yang berjalan di kumpulan utas dari Azul Framework.
  22. Dapatkan salinan soket dari model data kami.
  23. Mencoba membaca data dari soket. Jika Anda tidak membuat salinan soket dan langsung menunggu di sini sampai pesan datang dari soket yang ada di mutex dalam model data kami, maka seluruh antarmuka akan berhenti diperbarui hingga kami merilis mutex.
  24. Jika kami mendapatkan beberapa jenis pesan, kemudian mengubah model data kami, modifikasi melakukan hal yang sama dengan kunci (). Lepaskan () dengan meneruskan hasilnya ke lambda dan melepaskan mutex setelah kode lambda berakhir.
  25. Tetapkan bendera untuk menunjukkan bahwa kami memiliki pesan baru.
  26. Tambahkan pesan ke larik semua pesan obrolan.
  27. Operasi sinkron berulang yang berjalan di utas utama.
  28. Jika kami memiliki pesan baru, kami memberi tahu Kerangka Kerja bahwa kami perlu menggambar ulang antarmuka dari awal dan terus bekerja dengan daemon ini; jika tidak, kami tidak akan menggambar antarmuka dari awal, tetapi masih memanggil Fungsi ini di siklus berikutnya.
  29. Membuat salinan soket kami agar tidak menjaga mutex terkunci dengan model data kami.
  30. Kami mendapatkan mutex dan mendapatkan tautan ke soket.
  31. Buat salinan soket. Mutex akan dibebaskan secara otomatis saat keluar dari suatu Fungsi.

Pemrosesan data dan dasmon asinkron di 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; }); } 

Daemon selalu dieksekusi di utas utama, jadi penguncian tidak bisa dihindari di sana. Dengan tugas yang tidak sinkron, jika Anda melakukannya, misalnya, seperti ini, tidak akan ada kunci selama 10 detik.

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

Fungsi panggilan memodifikasi kunci () dan mutex dengan model data karena itu memblokir memperbarui antarmuka selama pelaksanaannya.

Gaya kami


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

Sebenarnya, Fungsi untuk membuat DOM kami ditampilkan kepada penggunanya


 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. Fungsi yang menciptakan DOM terakhir, dan dipanggil setiap kali Anda perlu menggambar ulang antarmuka.
  2. Jika kami sudah terhubung ke server, maka kami menunjukkan formulir untuk mengirim dan membaca pesan, kalau tidak, kami menampilkan formulir untuk menghubungkan ke server.
  3. Membuat formulir untuk memasukkan data yang diperlukan untuk terhubung ke server.
  4. Buat tombol dengan tulisan tulisan Login.
  5. Konversikan ke objek DOM.
  6. Tambahkan kelas baris ke sana.
  7. Tambahkan oranye kelas css ke dalamnya.
  8. Tambahkan pengendali acara untuk mengklik tombol.
  9. Buat label teks dengan teks untuk ditampilkan kepada pengguna dan baris kelas css.
  10. Kami membuat kotak teks untuk memasukkan teks dengan teks dari properti model kami dan baris kelas css.
  11. Ikatkan bidang teks ke properti DataModel kami. Ini mengikat dua arah. Sekarang mengedit TextInput secara otomatis mengubah teks di properti model kami dan sebaliknya juga benar. Jika kita mengubah teks dalam model kita, teks dalam TextInput akan berubah.
  12. Kami membuat elemen DOM root di mana kami menempatkan elemen UI kami.
  13. Membuat formulir untuk mengirim dan membaca pesan.
  14. Buat tombol dengan teks "Kirim" dan css dengan kelas "baris", "oranye" dan pengendali acara saat diklik.
  15. Buat bidang input teks dua arah dengan properti model self.messaging_model.text_input_state dan css dengan kelas "baris".
  16. Tambahkan label teks yang menampilkan pesan yang ditulis dalam obrolan.

Model kami yang menyimpan keadaan antarmuka kami


Dokumentasi Azul mengatakan bahwa ia harus menyimpan semua data aplikasi, termasuk koneksi ke database, jadi saya memasukkan soket UDP ke dalamnya.

 //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. Ini akan memungkinkan kami untuk menampilkan struktur kami sebagai string di templat formulir {:?}
  2. Model data kami. Sehingga bisa digunakan di Azul. Dia harus menerapkan sifat Tata Letak.
  3. Bendera untuk memeriksa apakah pengguna terhubung ke server atau tidak.
  4. Model untuk menampilkan formulir untuk mengirim pesan ke server dan menyimpan pesan yang diterima dari server.
  5. Model untuk menampilkan formulir untuk menghubungkan ke server.
  6. Port yang dimasukkan pengguna. Kami akan mendengarkannya dengan soket kami.
  7. Alamat server yang dimasukkan pengguna. Kami akan terhubung dengannya.
  8. Pesan pengguna. Kami akan mengirimkannya ke server.
  9. Array pesan yang datang dari server.
  10. Soket tempat kami berkomunikasi dengan server.
  11. Bendera untuk memeriksa apakah pesan baru telah tiba dari server.

Dan akhirnya, titik masuk utama ke aplikasi. Mulai siklus dari gambar GUI dan pemrosesan input pengguna


 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. Kami membuat aplikasi dengan data awal.
  2. Gaya yang digunakan oleh aplikasi secara default.
  3. Tambahkan gaya kita sendiri ke mereka.
  4. Kami membuat jendela di mana aplikasi kami akan ditampilkan.
  5. Luncurkan aplikasi di jendela ini.

Server


Titik masuk utama ke aplikasi


Di sini kita biasanya memiliki aplikasi konsol.

 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. Buat soket.
  2. Kami membuat saluran satu arah dengan satu pengirim pesan sx dan banyak penerima rx.
  3. Kami mulai mengirim pesan ke semua penerima dalam aliran terpisah.
  4. Kami membaca data dari soket dan mengirimkannya ke aliran, yang mengirim pesan ke klien yang terhubung ke server.

Berfungsi untuk membuat aliran untuk mengirim pesan ke klien


 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. Mulai utas baru. move berarti bahwa masing-masing variabel mengambil alih lambda dan aliran. Lebih khusus lagi, utas baru kami akan "menyerap" variabel rx dan socket.
  2. Kumpulan alamat yang terhubung dengan kami oleh pelanggan. Kami akan mengirim mereka semua pesan kami. Secara umum, dalam proyek nyata akan diperlukan untuk melakukan pemrosesan pemutusan klien dari kami dan menghapus alamatnya dari array ini.
  3. Kami memulai loop tak terbatas.
  4. Kami membaca data dari saluran. Di sini aliran akan diblokir sampai data baru tiba.
  5. Jika tidak ada alamat seperti itu di array kami, maka tambahkan di sana.
  6. Dekode string UTF8 dari array byte.
  7. Kami membuat array byte yang akan kami kirim ke semua pelanggan kami.
  8. Kami melalui pengumpulan alamat dan mengirim data ke semua orang.
  9. Operasi tulis ke soket UDP adalah non-pemblokiran, jadi di sini Fungsi tidak akan menunggu sampai pesan tiba di penerima dan dijalankan hampir secara instan.
  10. berharap jika terjadi kesalahan akan membuat keluar darurat dari program dengan pesan yang diberikan.

Fungsi membuat soket berdasarkan input pengguna


 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. Kami membaca port yang akan didengarkan server kami dan membuat alamat server lokal berdasarkan itu.
  2. Buat soket UDP mendengarkan pada alamat ini.
  3. Atur batas waktu untuk operasi baca. Operasi baca memblokir dan akan memblokir aliran sampai data baru tiba atau waktu habis terjadi.
  4. Kami mengembalikan soket yang dibuat dari Function.
  5. Fungsi membaca data dari soket dan mengembalikannya bersama dengan alamat pengirim.

Berfungsi untuk membaca data dari soket


 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. Buffer adalah tempat di mana kita akan membaca data.
  2. Mulai loop yang akan berjalan hingga data yang valid dibaca.
  3. Kami mendapatkan jumlah byte yang dibaca dan alamat pengirim.
  4. Kami memotong array dari awal hingga jumlah byte yang dibaca dan mengubahnya menjadi vektor byte.
  5. Jika terjadi timeout atau kesalahan lainnya, maka lanjutkan ke iterasi loop berikutnya.

Tentang lapisan dalam aplikasi


Offtopic: Program pendidikan kecil untuk dua Juni di tempat kerja. Saya memutuskan untuk meletakkannya di sini, mungkin seseorang akan berguna. Penajam-Juni Juni adalah contoh dalam C # dan kita berbicara tentang ASP.NET
Jadi, tidak ada yang bisa dilakukan, itu di malam hari, dan saya memutuskan untuk menulis program pendidikan kecil tentang arsitektur untuk Artem dan Victor. Baiklah, ayo pergi.

Sebenarnya, saya menambahkan di sini karena pemulihan mode dan saya hanya bisa menulis artikel seminggu sekali, dan materi sudah ada di sana dan minggu depan saya ingin mengunggah sesuatu yang lain ke Habr.

Biasanya, aplikasi berlapis. Setiap lapisan berisi objek yang menerapkan karakteristik perilaku lapisan di mana mereka berada. Dan begitulah. Ini adalah lapisan-lapisannya.

  1. Lapisan presentasi.
  2. Logika bisnis lapisan.
  3. Lapisan akses data.
  4. Entitas (Pengguna, Hewan, dll.)


Setiap layer dapat berisi DTO sendiri dan kelas yang sepenuhnya arbitrer dengan metode arbitrer. Yang utama adalah mereka melakukan fungsionalitas yang terkait dengan lapisan di mana mereka berada. Dalam aplikasi sederhana, beberapa layer mungkin hilang. Sebagai contoh, tampilan layer dapat diimplementasikan melalui pola MVC, MVP, MVVM. Yang sepenuhnya opsional. Hal utama adalah bahwa kelas-kelas yang ada di lapisan ini mengimplementasikan fungsi yang ditugaskan ke lapisan. Ingat, pola dan arsitektur hanyalah rekomendasi, bukan arah. Pola dan arsitektur bukan hukum, ini saran.

Jadi, kami akan mempertimbangkan setiap lapisan pada contoh aplikasi ASP.NET standar menggunakan Kerangka Entitas standar.

Lapisan presentasi


Kami punya MVC di sini. Ini adalah lapisan yang menyediakan interaksi pengguna. Perintah datang ke sini dan pengguna mendapatkan data dari sini. Belum tentu orang, jika kita memiliki API, maka pengguna kita adalah program yang berbeda. Mobil berkomunikasi dengan mobil.

Lapisan logika bisnis


Di sini, biasanya, kelas disebut Layanan, misalnya, UserService, meskipun dapat berupa apa saja. Hanya seperangkat kelas dengan metode. Yang utama adalah bahwa perhitungan dan perhitungan aplikasi kita terjadi di sini. Ini adalah lapisan paling tebal dan paling tebal. Ada sebagian besar kode dan berbagai kelas. Ini sebenarnya adalah aplikasi kita.

Lapisan akses data


Biasanya di sini EF menerapkan pola Unit Kerja dan Repositori. Jadi ya, DbContext adalah, Anda dapat mengatakan, Unit Kerja, dan DB mengaturnya sebagai Repositori. Ini, pada kenyataannya, adalah tempat di mana kita menyimpan data dan dari mana kita mendapatkannya. Terlepas dari apakah sumber data adalah database, API dari aplikasi lain, cache dalam memori, atau hanya beberapa jenis pembangkit angka acak. Sumber data apa saja.

Entitas


Ya, hanya segala macam Pengguna, Hewan, dan banyak lagi. Satu poin penting - mereka mungkin hanya memiliki karakteristik perilaku tertentu. Sebagai contoh:

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

Nah, dan contoh yang sangat sederhana. Shoba dulu


 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


Ya ampun, saya ingin mengucapkan terima kasih kepada Nastya saya untuk memperbaiki kesalahan tata bahasa dalam artikel ini. Jadi iya, Nastya kamu tidak sia-sia dengan ijazah merah dan umumnya keren. Aku mencintaimu <3.

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


All Articles