Kami membuat messenger * yang berfungsi bahkan di lift

* sebenarnya, kami hanya akan menulis prototipe protokol.

Mungkin Anda pernah mengalami situasi yang sama - duduk di messenger favorit Anda, mengobrol dengan teman-teman, pergi ke lift / terowongan / kereta, dan Internet tampaknya masih menangkap, tetapi tidak ada yang dapat dikirim? Atau kadang-kadang penyedia komunikasi Anda mengkonfigurasi jaringan dengan salah dan 50% dari paket hilang, dan tidak ada yang berhasil. Mungkin Anda sedang berpikir pada saat itu - yah, Anda mungkin dapat melakukan sesuatu sehingga dengan koneksi yang buruk Anda masih dapat mengirim teks kecil yang Anda inginkan? Kamu tidak sendiri

Sumber gambar

Pada artikel ini saya akan berbicara tentang ide saya untuk mengimplementasikan protokol berdasarkan UDP, yang dapat membantu dalam situasi ini.

Masalah TCP / IP


Ketika kita memiliki koneksi (ponsel) yang buruk, maka sebagian besar paket mulai hilang (atau pergi dengan penundaan yang sangat lama), dan protokol TCP / IP dapat menganggap ini sebagai sinyal bahwa jaringan padat, dan semuanya mulai bekerja dengan susah payah jika berfungsi secara umum. Itu tidak menambah sukacita bahwa pembentukan koneksi (terutama TLS) memerlukan pengiriman dan penerimaan beberapa paket, dan bahkan kerugian kecil mempengaruhi operasinya sangat buruk. Ini juga sering mengharuskan mengakses DNS sebelum membuat koneksi - beberapa paket tambahan.

Secara keseluruhan, masalah REST API berbasis TCP / IP tipikal dengan koneksi yang buruk:

  • Respons buruk terhadap kehilangan paket (pengurangan kecepatan tajam, batas waktu besar)
  • Membuat koneksi memerlukan pertukaran paket (+3 paket)
  • Seringkali Anda memerlukan kueri DNS "ekstra" untuk mencari tahu server IP (+2 paket)
  • Seringkali membutuhkan TLS (+2 paket minimum)

Secara total, ini berarti bahwa hanya untuk menyambung ke server kita perlu mengirim 3-7 paket, dan dengan persentase kerugian yang tinggi, koneksi dapat mengambil banyak waktu, dan kami bahkan belum mengirim apa pun.

Ide implementasi


Idenya adalah ini: kita hanya perlu mengirim satu paket UDP ke alamat IP pra-kabel server dengan data otorisasi yang diperlukan dan teks pesan, dan dapatkan jawabannya. Semua data dapat dienkripsi tambahan (ini tidak ada dalam prototipe). Jika jawabannya belum tiba dalam sedetik, maka kami percaya bahwa permintaan itu hilang dan mencoba mengirimkannya lagi. Server harus dapat menghapus pesan duplikat, jadi mengirim ulang seharusnya tidak menimbulkan masalah.

Kemungkinan jebakan untuk implementasi siap-produksi


Berikut ini adalah (tidak berarti semua) hal-hal yang perlu Anda pikirkan sebelum menggunakan sesuatu seperti ini dalam kondisi "pertempuran":

  1. UDP dapat "dipotong" oleh penyedia - Anda harus dapat bekerja melalui TCP / IP
  2. UDP tidak bersahabat dengan NAT - biasanya hanya ada sedikit (~ 30 detik) waktu untuk menanggapi permintaan klien
  3. Server harus tahan untuk mendapatkan serangan - Anda perlu memastikan bahwa paket respons tidak lebih dari paket permintaan
  4. Enkripsi sulit, dan jika Anda bukan pakar keamanan, Anda memiliki sedikit peluang untuk menerapkannya dengan benar
  5. Jika Anda mengatur interval pengiriman ulang secara tidak benar (misalnya, alih-alih mencoba lagi setiap detik, mencoba lagi tanpa berhenti), maka Anda dapat melakukan jauh lebih buruk daripada TCP / IP
  6. Lebih banyak lalu lintas mungkin mulai masuk ke server Anda karena kurangnya umpan balik dalam UDP dan coba lagi yang tak ada habisnya
  7. Server dapat memiliki beberapa alamat IP, dan mereka dapat berubah dari waktu ke waktu, jadi Anda harus dapat memperbarui cache (Telegram berfungsi dengan baik :))

Implementasi


Kami akan menulis server yang akan mengirim respons melalui UDP dan mengirimkan dalam respons jumlah permintaan yang datang kepadanya (permintaan tersebut tampak seperti "teks pesan permintaan-ts"), serta stempel waktu menerima respons:

//  Go. //      buf := make([]byte, maxUDPPacketSize) //   UDP addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", serverPort)) conn, _ := net.ListenUDP("udp", addr) for { //   UDP,      n, uaddr, _ := conn.ReadFromUDP(buf) req := string(buf[0:n]) parts := strings.SplitN(req, " ", 2) //          curTs := time.Now().UnixNano() clientTs, _ := strconv.Atoi(parts[0]) //       -      //   conn.WriteToUDP([]byte(fmt.Sprintf("%d %d", curTs, clientTs)), uaddr) } 

Sekarang bagian yang sulit adalah klien. Kami akan mengirim pesan satu per satu dan menunggu server merespons sebelum mengirim yang berikutnya. Kami akan mengirimkan stempel waktu saat ini dan sepotong teks - stempel waktu akan berfungsi sebagai pengidentifikasi permintaan.

 //   addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverIP, serverPort)) conn, _ := net.DialUDP("udp", nil, addr) //  UDP      ,     . resCh := make(chan udpResult, 10) go readResponse(conn, resCh) for i := 0; i < numMessages; i++ { requestID := time.Now().UnixNano() send(conn, requestID, resCh) } 

Kode Fitur:

 func send(conn *net.UDPConn, requestID int64, resCh chan udpResult) { for { //     ,       . conn.Write([]byte(fmt.Sprintf("%d %s", requestID, testMessageText))) if waitReply(requestID, time.After(time.Second), resCh) { return } } } //   ,  . //      ,   ,   // ,        , //   . func waitReply(requestID int64, timeout <-chan time.Time, resCh chan udpResult) (ok bool) { for { select { case res := <-resCh: if res.requestTs == requestID { return true } case <-timeout: return false } } } //    type udpResult struct { serverTs int64 requestTs int64 } //           . func readResp(conn *net.UDPConn, resCh chan udpResult) { buf := make([]byte, maxUDPPacketSize) for { n, _, _ := conn.ReadFromUDP(buf) respStr := string(buf[0:n]) parts := strings.SplitN(respStr, " ", 2) var res udpResult res.serverTs, _ = strconv.ParseInt(parts[0], 10, 64) res.requestTs, _ = strconv.ParseInt(parts[1], 10, 64) resCh <- res } } 

Saya juga menerapkan hal yang sama berdasarkan (lebih atau kurang) standar REST: menggunakan HTTP POST, kami mengirim requestTs dan teks pesan yang sama dan menunggu jawaban, kemudian pergi ke yang berikutnya. Banding dibuat dengan nama domain, caching DNS tidak dilarang dalam sistem. HTTPS tidak digunakan untuk membuat perbandingan lebih jujur ​​(tidak ada enkripsi dalam prototipe). Batas waktu ditetapkan pada 15 detik: TCP / IP sudah memiliki penerusan paket yang hilang, dan kemungkinan besar pengguna tidak akan menunggu lebih dari 15 detik.

Pengujian, Hasil


Saat menguji prototipe, hal-hal berikut diukur (semua dalam milidetik ):

  1. Waktu respons pertama (pertama)
  2. Waktu respons rata-rata (rata-rata)
  3. Waktu respons maksimum (maks)
  4. H / U - rasio "Waktu HTTP" / "Waktu UDP" - berapa kali lebih sedikit menunda saat menggunakan UDP

100 seri dari 10 permintaan dibuat - kami mensimulasikan situasi ketika Anda perlu mengirim hanya beberapa pesan dan setelah itu Internet normal (misalnya, Wi-Fi di metro, atau 3G / LTE di jalan) menjadi tersedia.

Jenis komunikasi yang diuji:


  1. Profil “Very Bad Network” (Kerugian 10%, latensi 500 ms, 1 Mbps) di Network Link Conditioner - “Sangat Buruk”
  2. EDGE, telepon di lemari es ("lift") - lemari es
  3. EDGE
  4. 3G
  5. LTE
  6. WiFi

Hasil (waktu dalam milidetik):




( sama dalam format CSV )

Kesimpulan


Berikut adalah kesimpulan yang bisa diambil dari hasil:

  1. Terlepas dari anomali LTE, perbedaan dalam mengirim pesan pertama adalah semakin besar, semakin buruk koneksinya (rata-rata, 2-3 kali lebih cepat)
  2. Pengiriman pesan berikutnya dalam HTTP tidak jauh lebih lambat - rata-rata 1,3 kali lebih lambat, tetapi pada Wi-Fi yang stabil tidak ada perbedaan sama sekali
  3. Waktu respons berbasis UDP jauh lebih stabil, yang secara tidak langsung dilihat oleh latensi maksimum - juga 1,4-1,8 kali lebih sedikit

Dengan kata lain, dalam kondisi yang sesuai ("buruk"), protokol kami akan bekerja jauh lebih baik, terutama ketika mengirim pesan pertama (seringkali hanya ini yang perlu dikirim).

Implementasi prototipe


Prototipe diposting di github . Jangan menggunakannya dalam produksi!

Perintah untuk memulai klien di ponsel atau komputer:
 instant-im -client -num 10 
. Server masih berjalan :). Adalah perlu untuk melihat pertama-tama pada saat jawaban pertama, serta pada keterlambatan maksimum. Semua data ini dicetak di bagian akhir.

Contoh Peluncuran Elevator


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


All Articles