มีรูปแบบการเขียนเซิร์ฟเวอร์แบบเทิร์นเบสที่สื่อสารกับไคลเอนต์ n ผ่านซ็อกเก็ตหรือไม่?


14

ฉันกำลังทำงานกับเซิร์ฟเวอร์เกมทั่วไปที่จัดการเกมให้กับไคลเอนต์เครือข่ายซ็อกเก็ต TCP จำนวนหนึ่งที่เล่นเกม ฉันมี 'การออกแบบ' ที่แฮ็คพร้อมกับเทปพันสายไฟที่ใช้งานได้ แต่ดูเหมือนว่าทั้งบอบบางและไม่ยืดหยุ่น มีรูปแบบที่ดีขึ้นสำหรับวิธีการเขียนการสื่อสารลูกค้า / เซิร์ฟเวอร์ที่แข็งแกร่งและยืดหยุ่นหรือไม่? (ถ้าไม่คุณจะปรับปรุงสิ่งที่ฉันมีด้านล่างได้อย่างไร)

ประมาณฉันมีสิ่งนี้:

  • ในขณะที่ตั้งค่าเกมเซิร์ฟเวอร์จะมีหนึ่งเธรดสำหรับแต่ละซ็อกเก็ตผู้เล่นที่จัดการคำขอที่ซิงโครนัสจากไคลเอนต์และการตอบสนองจากเซิร์ฟเวอร์
  • อย่างไรก็ตามเมื่อเกมดำเนินไปเธรดทั้งหมดยกเว้นหนึ่งสลีปและเธรดจะวนไปตามผู้เล่นทุกคนทีละครั้งเพื่อสื่อสารเกี่ยวกับการเคลื่อนไหวของพวกเขา (ในการตอบกลับคำร้องขอ)

นี่คือแผนภาพของสิ่งที่ฉันมีอยู่ในปัจจุบัน; คลิกเพื่อดู / รุ่นที่สามารถอ่านได้ที่มีขนาดใหญ่หรือ66kB รูปแบบไฟล์ PDF

แผนภาพลำดับการไหล

ปัญหา:

  • มันต้องการให้ผู้เล่นตอบกลับอย่างแม่นยำด้วยข้อความที่ถูกต้อง (ฉันคิดว่าฉันสามารถให้ผู้เล่นแต่ละคนตอบสนองด้วยอึแบบสุ่มและย้ายต่อเมื่อพวกเขาให้การเคลื่อนไหวที่ถูกต้องแก่ฉัน)
  • ไม่อนุญาตให้ผู้เล่นคุยกับเซิร์ฟเวอร์เว้นแต่จะเป็นตาของพวกเขา (ฉันอาจให้เซิร์ฟเวอร์ส่งการอัปเดตเกี่ยวกับผู้เล่นอื่น แต่ไม่สามารถดำเนินการตามคำขอแบบอะซิงโครนัสได้)

ข้อกำหนดขั้นสุดท้าย:

  • ประสิทธิภาพไม่สำคัญยิ่ง ส่วนใหญ่จะใช้สำหรับเกมที่ไม่ใช่เรียลไทม์และส่วนใหญ่สำหรับการเจาะ AIs ต่อกันไม่ใช่มนุษย์ที่กระตุก

  • การเล่นเกมจะเป็นแบบเทิร์นเบสเสมอ (แม้ว่าจะมีความละเอียดสูงมาก) ผู้เล่นแต่ละคนจะได้รับการดำเนินการหนึ่งครั้งก่อนที่ผู้เล่นอื่นจะได้รับการเปิด

การนำเซิร์ฟเวอร์ไปใช้จะเกิดขึ้นใน Ruby หากสร้างความแตกต่าง


หากคุณใช้ซ็อกเก็ต TCP ต่อไคลเอนต์จะไม่ครอบคลุมไคลเอนต์นี้ (65535-1024)?
o0 '

1
ทำไมเป็นปัญหาโลฮาริส? คนส่วนใหญ่จะต่อสู้เพื่อให้ได้ 6000 ผู้ใช้พร้อมกันไม่เคยคิด 60000.
Kylotan

@Kylotan แน่นอน หากฉันมีมากกว่า 10 AIs พร้อมกันฉันจะประหลาดใจ :)
Phrogz

จากความอยากรู้คุณใช้เครื่องมือใดในการสร้างไดอะแกรมนั้น
matthias

@matthias น่าเศร้าที่นั่นเป็นงานที่กำหนดเองมากเกินไปใน Adobe Illustrator
Phrogz

คำตอบ:


10

ฉันไม่แน่ใจว่าคุณต้องการบรรลุสิ่งใด แต่มีรูปแบบหนึ่งที่ใช้อย่างต่อเนื่องในเซิร์ฟเวอร์เกมและอาจช่วยคุณได้ ใช้คิวข้อความ

เพื่อให้มีความเฉพาะเจาะจงมากขึ้น: เมื่อลูกค้าส่งข้อความไปยังเซิร์ฟเวอร์ห้ามประมวลผลทันที ค่อนข้างแยกวิเคราะห์พวกเขาและใส่ลงในคิวสำหรับลูกค้าที่เฉพาะเจาะจงนี้ จากนั้นในลูปหลักบางอัน (อาจอยู่ในเธรดอื่น) ไปยังไคลเอนต์ทั้งหมดในทางกลับกันดึงข้อความจากคิวและประมวลผลพวกเขา เมื่อการประมวลผลบ่งชี้ว่าการเทิร์นของลูกค้านี้เสร็จสิ้นแล้วให้ย้ายไปยังถัดไป

วิธีนี้ลูกค้าไม่จำเป็นต้องทำงานแบบเลี้ยวต่อเลี้ยวอย่างเคร่งครัด เร็วพอเพื่อให้คุณมีบางสิ่งในคิวตามเวลาที่ลูกค้าได้รับการประมวลผล (แน่นอนคุณสามารถรอลูกค้าหรือข้ามการเปิดถ้ามันล่าช้า) และคุณสามารถเพิ่มการสนับสนุนสำหรับคำขอแบบอะซิงโครนัสได้โดยเพิ่มคิว "async": เมื่อลูกค้าส่งคำขอพิเศษมันจะถูกเพิ่มลงในคิวพิเศษนี้ คิวนี้ได้รับการตรวจสอบและประมวลผลบ่อยครั้งกว่าของลูกค้า


1

เธรดฮาร์ดแวร์ไม่ได้ปรับขนาดได้ดีพอที่จะทำให้ "หนึ่งต่อผู้เล่น" เป็นความคิดที่สมเหตุสมผลสำหรับจำนวนผู้เล่น 3 หลักและตรรกะของการรู้ว่าเมื่อใดที่จะทำให้พวกเขาตื่นขึ้นมาก็คือความซับซ้อนที่จะเพิ่มขึ้น ความคิดที่ดีกว่าคือการค้นหาแพ็คเกจ I / O แบบอะซิงโครนัสสำหรับ Ruby ที่จะช่วยให้คุณสามารถส่งและรับข้อมูลโดยไม่ต้องหยุดเธรดโปรแกรมทั้งหมดในขณะที่การดำเนินการอ่านหรือเขียนจะเกิดขึ้น วิธีนี้จะช่วยแก้ปัญหาในการรอให้ผู้เล่นตอบกลับเนื่องจากคุณจะไม่มีเธรดใด ๆ ที่แขวนอยู่บนการอ่าน แต่เซิร์ฟเวอร์ของคุณสามารถตรวจสอบว่าการ จำกัด เวลาหมดอายุแล้วแจ้งให้ผู้เล่นคนอื่นทราบตามนั้น

โดยทั่วไป 'อะซิงโครนัส I / O' คือ 'รูปแบบ' ที่คุณกำลังมองหาถึงแม้ว่ามันจะไม่ใช่รูปแบบ แต่เป็นวิธีการที่มากกว่า แทนที่จะเรียก 'อ่าน' บนซ็อกเก็ตอย่างชัดเจนและหยุดโปรแกรมไว้ชั่วคราวจนกว่าข้อมูลจะมาถึงคุณตั้งค่าระบบให้เรียก 'onRead' ตัวจัดการของคุณเมื่อใดก็ตามที่มีข้อมูลพร้อมและคุณยังคงดำเนินการจนกว่าจะถึงเวลานั้น

แต่ละซ็อกเก็ตมีการเปิด

ผู้เล่นแต่ละคนมีเทิร์นและผู้เล่นแต่ละคนมีซ็อกเก็ตที่ส่งข้อมูลซึ่งแตกต่างกันเล็กน้อย วันหนึ่งคุณอาจไม่ต้องการซ็อกเก็ตหนึ่งอันต่อผู้เล่นหนึ่งคน คุณอาจไม่ใช้ซ็อกเก็ตเลย แยกความรับผิดชอบออกจากกัน ขออภัยถ้าฟังดูเหมือนรายละเอียดที่ไม่สำคัญ แต่เมื่อคุณสร้างแนวคิดในการออกแบบของคุณที่แตกต่างออกไปก็จะทำให้ยากต่อการค้นหาและอภิปรายแนวทางที่ดีกว่า


1

มีมากกว่าหนึ่งวิธีที่จะทำ แต่โดยส่วนตัวแล้วฉันจะข้ามเธรดแยกทั้งหมดและใช้เหตุการณ์วนรอบ วิธีที่คุณทำจะขึ้นอยู่กับไลบรารี I / O ที่คุณใช้ แต่โดยทั่วไปแล้วลูปเซิร์ฟเวอร์หลักของคุณจะมีลักษณะดังนี้:

  1. ตั้งค่าพูลการเชื่อมต่อและซ็อกเก็ตการฟังสำหรับการเชื่อมต่อใหม่
  2. รอสิ่งที่จะเกิดขึ้น
  3. หากบางสิ่งเป็นการเชื่อมต่อใหม่ให้เพิ่มลงในพูล
  4. หากสิ่งนั้นเป็นคำขอจากลูกค้าตรวจสอบว่าเป็นสิ่งที่คุณสามารถจัดการได้ทันที ถ้าใช่ทำเช่นนั้น ถ้าไม่มีให้ใส่ไว้ในคิวและ (เป็นทางเลือก) ส่งการตอบรับไปยังลูกค้า
  5. ตรวจสอบด้วยว่ามีบางอย่างในคิวที่คุณสามารถจัดการได้หรือไม่ ถ้าเป็นเช่นนั้นทำ
  6. กลับไปที่ขั้นตอนที่ 2

ตัวอย่างเช่นสมมติว่าคุณมีลูกค้าn คนที่เกี่ยวข้องกับเกม เมื่อแรกที่n − 1ของพวกเขาส่งในการเคลื่อนไหวของพวกเขาคุณเพียงตรวจสอบว่าการเคลื่อนไหวดูถูกต้องและส่งข้อความกลับบอกว่าได้รับการย้าย แต่คุณยังคงรอให้ผู้เล่นอื่นที่จะย้าย หลังจากผู้เล่นทั้งหมดnคนย้ายคุณดำเนินการทุกการเคลื่อนไหวที่คุณบันทึกไว้และส่งผลลัพธ์ไปยังผู้เล่นทุกคน

คุณสามารถปรับแต่งรูปแบบนี้เพื่อรวมการหมดเวลา - ห้องสมุด I / O ส่วนใหญ่ควรมีกลไกรอจนกว่าข้อมูลใหม่มาถึงหรือเวลาที่กำหนดได้ผ่านไปแล้ว

แน่นอนคุณสามารถนำบางสิ่งเช่นนี้ไปใช้กับแต่ละเธรดสำหรับแต่ละการเชื่อมต่อโดยให้เธรดเหล่านั้นส่งผ่านคำร้องขอใด ๆ ที่พวกเขาไม่สามารถจัดการกับเธรดกลางได้โดยตรง (อย่างใดอย่างหนึ่งต่อเกมหรือหนึ่งต่อเซิร์ฟเวอร์หนึ่ง) ที่แสดงด้านบนยกเว้นว่าจะมีการพูดคุยกับเธรดตัวจัดการการเชื่อมต่อแทนที่จะติดต่อกับลูกค้าโดยตรง ไม่ว่าคุณจะพบว่าสิ่งนี้ง่ายกว่าหรือซับซ้อนกว่าแนวทางแบบเธรดเดียวก็ขึ้นอยู่กับคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.