Apakah ada pola untuk menulis server berbasis giliran yang berkomunikasi dengan n klien di atas soket?

14

Saya sedang mengerjakan server permainan generik yang mengelola permainan untuk sejumlah klien jaringan soket TCP yang sewenang-wenang yang memainkan permainan. Saya memiliki 'desain' diretas bersama dengan lakban yang berfungsi, tetapi tampaknya rapuh dan tidak fleksibel. Apakah ada pola mapan untuk bagaimana menulis komunikasi klien / server yang kuat dan fleksibel? (Jika tidak, bagaimana Anda meningkatkan apa yang saya miliki di bawah ini?)

Kira-kira saya punya ini:

  • Saat menyiapkan permainan, server memiliki satu utas untuk setiap soket pemain yang menangani permintaan sinkron dari klien dan respons dari server.
  • Namun, begitu permainan berjalan, semua utas kecuali untuk satu tidur, dan utas tersebut berputar melalui semua pemain satu per satu untuk berkomunikasi tentang gerakan mereka (dalam permintaan-tanggapan terbalik).

Berikut diagram dari apa yang saya miliki saat ini; klik untuk versi yang lebih besar / dapat dibaca, atau 66kB PDF .

Diagram urutan aliran

Masalah:

  • Ini membutuhkan pemain untuk merespons secara bergiliran dengan pesan yang tepat. (Saya kira saya bisa membiarkan setiap pemain merespons dengan omong kosong acak dan hanya bergerak begitu mereka memberi saya langkah yang valid.)
  • Itu tidak memungkinkan pemain untuk berbicara ke server kecuali jika giliran mereka. (Saya dapat meminta server mengirim pembaruan tentang pemain lain kepada mereka, tetapi tidak memproses permintaan yang tidak sinkron.)

Persyaratan akhir:

  • Kinerja bukan yang terpenting. Ini sebagian besar akan digunakan untuk game non-realtime, dan sebagian besar untuk mengadu AI satu sama lain, bukan manusia yang gelisah.

  • Permainan akan selalu berbasis giliran (bahkan jika pada resolusi yang sangat tinggi). Setiap pemain selalu mendapat satu gerakan diproses sebelum semua pemain lain mendapat giliran.

Implementasi server kebetulan berada di Ruby, jika itu membuat perbedaan.

Phrogz
sumber
Jika Anda menggunakan soket TCP per klien, bukankah ini akan membatasi (65535-1024) klien?
o0 '.
1
Mengapa itu menjadi masalah, Lohoris? Kebanyakan orang akan berjuang untuk mendapatkan 6000 pengguna secara bersamaan, apalagi 60000.
Kylotan
@Kylotan Memang. Jika saya memiliki lebih dari 10 AI bersamaan, saya akan terkejut. :)
Phrogz
Karena penasaran, alat apa yang Anda gunakan untuk membuat diagram itu?
matthias
@matthias Sedihnya, itu terlalu banyak pekerjaan kustom di Adobe Illustrator.
Phrogz

Jawaban:

10

Saya tidak yakin apa yang ingin Anda capai. Tapi, ada satu pola yang digunakan terus-menerus di server gim, dan dapat membantu Anda. Gunakan antrian pesan.

Untuk lebih spesifik: ketika klien mengirim pesan ke server, jangan segera memprosesnya. Sebaliknya, uraikan mereka dan masukkan ke dalam antrian untuk klien khusus ini. Kemudian, dalam beberapa loop utama (bahkan mungkin pada utas lainnya), periksa semua klien secara berurutan, ambil pesan dari antrian dan proseskan. Saat pemrosesan menunjukkan bahwa giliran klien ini selesai, lanjutkan ke yang berikutnya.

Dengan cara ini, klien tidak diharuskan untuk bekerja secara ketat setiap belokan; hanya cukup cepat sehingga Anda memiliki sesuatu dalam antrian pada saat klien diproses (Anda bisa, tentu saja, menunggu klien atau melewatkan gilirannya jika tertinggal). Dan Anda dapat menambahkan dukungan untuk permintaan asinkron dengan menambahkan antrian "async": ketika klien mengirim permintaan khusus, itu ditambahkan ke antrian khusus ini; antrian ini diperiksa dan diproses lebih sering daripada antrian klien.

Lupakan
sumber
1

Utas perangkat keras tidak cukup baik untuk membuat "satu per pemain" ide yang masuk akal untuk jumlah pemain 3 digit, dan logika mengetahui kapan membangunkan mereka adalah kompleksitas yang akan tumbuh. Gagasan yang lebih baik adalah menemukan paket I / O asinkron untuk Ruby yang memungkinkan Anda mengirim dan menerima data tanpa harus menghentikan seluruh utas program saat operasi baca atau tulis berlangsung. Ini juga memecahkan masalah menunggu pemain untuk merespons, karena Anda tidak akan memiliki utas tergantung pada operasi baca. Alih-alih, server Anda hanya dapat memeriksa apakah batas waktu telah kedaluwarsa dan kemudian memberi tahu pemain lain.

Pada dasarnya 'I / O' sinkron adalah 'pola' yang Anda cari, meskipun sebenarnya bukan pola, lebih merupakan pendekatan. Alih-alih secara eksplisit memanggil 'baca' pada soket dan menjeda program sampai data tiba, Anda mengatur sistem untuk memanggil penangan 'onRead' Anda setiap kali ada data yang siap, dan Anda terus memproses hingga saat itu.

setiap soket memiliki belokan

Setiap pemain memiliki giliran, dan setiap pemain memiliki soket yang mengirimkan data, yang sedikit berbeda. Suatu hari Anda mungkin tidak ingin satu soket per pemain. Anda mungkin tidak menggunakan soket sama sekali. Pisahkan bidang tanggung jawab. Maaf jika ini kedengarannya seperti detail yang tidak penting tetapi ketika Anda mengacaukan konsep dalam desain Anda yang seharusnya berbeda maka akan lebih sulit untuk menemukan dan mendiskusikan pendekatan yang lebih baik.

Kylotan
sumber
1

Tentu saja ada lebih dari satu cara untuk melakukannya, tetapi secara pribadi, saya akan melewatkan utas terpisah sepenuhnya, dan hanya menggunakan loop acara. Cara Anda melakukan ini akan sedikit tergantung pada pustaka I / O yang Anda gunakan, tetapi pada dasarnya, loop server utama Anda akan terlihat seperti ini:

  1. Siapkan kumpulan koneksi dan soket mendengarkan untuk koneksi baru.
  2. Tunggu sesuatu terjadi.
  3. Jika ada sesuatu yang merupakan koneksi baru, tambahkan ke kolam.
  4. Jika sesuatu adalah permintaan dari klien, periksa apakah itu adalah sesuatu yang dapat Anda tangani segera. Jika ya, lakukanlah; jika tidak, letakkan dalam antrian dan (secara opsional) mengirim pemberitahuan kepada klien.
  5. Periksa juga apakah ada sesuatu dalam antrian yang dapat Anda tangani sekarang; jika demikian, lakukanlah.
  6. Kembali ke langkah 2.

Sebagai contoh, katakanlah Anda memiliki n klien yang terlibat dalam permainan. Ketika pertama kali n − 1 dari mereka mengirim gerakan mereka, Anda hanya memeriksa bahwa langkah tersebut terlihat valid dan mengirim kembali pesan yang mengatakan bahwa langkah tersebut telah diterima tetapi Anda masih menunggu pemain lain untuk bergerak. Setelah semua n pemain bergerak, Anda memproses semua gerakan yang telah Anda simpan dan mengirimkan hasilnya ke semua pemain.

Anda juga dapat memperbaiki skema ini untuk memasukkan batas waktu - sebagian besar perpustakaan I / O harus memiliki mekanisme untuk menunggu sampai data baru tiba atau jumlah waktu tertentu telah berlalu.

Tentu saja, Anda juga dapat mengimplementasikan sesuatu seperti ini dengan utas terpisah untuk setiap koneksi, dengan meminta utas tersebut meneruskan setiap permintaan yang tidak dapat mereka tangani langsung ke utas pusat (baik satu per game atau satu per server) yang menjalankan loop seperti ditunjukkan di atas, kecuali bahwa itu berbicara dengan utas penangan koneksi daripada langsung dengan klien. Apakah Anda menemukan ini lebih sederhana atau lebih rumit daripada pendekatan single-thread benar-benar terserah Anda.

Ilmari Karonen
sumber