درباره مدل شبکه در بازی های مبتدی

درباره مدل شبکه در بازی های مبتدی
در دو هفته گذشته من روی موتور شبکه برای بازی خود کار کرده ام. قبل از آن، من اصلاً در مورد شبکه در بازی ها چیزی نمی دانستم، بنابراین مقالات زیادی خواندم و آزمایش های زیادی انجام دادم تا همه مفاهیم را بفهمم و بتوانم موتور شبکه خودم را بنویسم.

در این راهنما، می‌خواهم مفاهیم مختلفی را که باید قبل از نوشتن موتور بازی خود یاد بگیرید، و همچنین بهترین منابع و مقالات برای یادگیری آنها را با شما به اشتراک بگذارم.

به طور کلی، دو نوع اصلی از معماری شبکه وجود دارد: همتا به همتا و مشتری-سرور. در معماری همتا به همتا (p2p)، داده ها بین هر جفت پخش کننده متصل منتقل می شود، در حالی که در معماری مشتری-سرور، داده ها فقط بین پخش کننده ها و سرور منتقل می شود.

اگرچه معماری همتا به همتا هنوز در برخی بازی ها استفاده می شود، معماری سرویس گیرنده-سرور استاندارد است: پیاده سازی آن آسان تر است، به عرض کانال کمتری نیاز دارد و محافظت در برابر تقلب را آسان تر می کند. بنابراین، در این راهنما، ما بر روی معماری مشتری-سرور تمرکز خواهیم کرد.

به ویژه، ما بیشتر به سرورهای مستبد علاقه مندیم: در چنین سیستم هایی، سرور همیشه حق دارد. به عنوان مثال، اگر بازیکن فکر می کند که در (10، 5) قرار دارد و سرور به او می گوید که در (5، 3) است، مشتری باید موقعیت خود را با موقعیتی که سرور گزارش می دهد جایگزین کند، نه برعکس. استفاده از سرورهای معتبر تشخیص کلاهبرداران را آسان تر می کند.

سه جزء اصلی در سیستم های شبکه بازی وجود دارد:

  • پروتکل حمل و نقل: نحوه انتقال داده بین کلاینت و سرور.
  • پروتکل برنامه: چه چیزی از کلاینت به سرور و از سرور به کلاینت منتقل می شود و در چه قالبی.
  • منطق برنامه: چگونه از داده های ارسال شده برای به روز رسانی وضعیت مشتریان و سرور استفاده می شود.

درک نقش هر بخش و مشکلات مربوط به آنها بسیار مهم است.

پروتکل حمل و نقل

اولین قدم انتخاب پروتکلی برای انتقال داده ها بین سرور و کلاینت است. دو پروتکل اینترنتی برای این کار وجود دارد: TCP и UDP. اما می توانید پروتکل حمل و نقل خود را بر اساس یکی از آنها ایجاد کنید یا از کتابخانه ای استفاده کنید که از آنها استفاده می کند.

مقایسه TCP و UDP

هر دو TCP و UDP بر اساس IP. IP به یک بسته اجازه می دهد تا از یک منبع به یک گیرنده منتقل شود، اما تضمین نمی کند که بسته ارسال شده دیر یا زود به گیرنده برسد، حداقل یک بار به آن برسد، و دنباله بسته ها به آن برسد. ترتیب صحیح علاوه بر این، یک بسته تنها می‌تواند حاوی یک اندازه داده محدود باشد که توسط مقدار داده می‌شود MTU.

UDP فقط یک لایه نازک در بالای IP است. از این رو همین محدودیت ها را دارد. در مقابل، TCP دارای ویژگی های بسیاری است. این یک اتصال منظم قابل اعتماد بین دو گره با بررسی خطا فراهم می کند. بنابراین، TCP بسیار راحت است و در بسیاری از پروتکل های دیگر، به عنوان مثال، در HTTP, FTP и SMTP. اما همه این ویژگی ها قیمت دارند: تاخیر انداختن.

برای درک اینکه چرا این توابع می توانند باعث تأخیر شوند، باید بدانیم که TCP چگونه کار می کند. هنگامی که میزبان فرستنده بسته ای را به میزبان دریافت کننده ارسال می کند، انتظار دریافت یک تایید (ACK) را دارد. اگر بعد از مدتی آن را دریافت نکرد (به دلیل گم شدن بسته یا تأیید یا به دلایل دیگر)، دوباره بسته را ارسال می کند. علاوه بر این، TCP تضمین می‌کند که بسته‌ها به ترتیب صحیح دریافت می‌شوند، بنابراین تا زمانی که یک بسته گمشده دریافت نشود، تمام بسته‌های دیگر قابل پردازش نیستند، حتی اگر قبلاً توسط گره دریافت‌کننده دریافت شده باشند.

اما همانطور که احتمالا متوجه شده اید، تاخیر در بازی های چند نفره بسیار مهم است، به خصوص در ژانرهای فعال مانند FPS. به همین دلیل است که بسیاری از بازی ها از UDP با پروتکل خاص خود استفاده می کنند.

یک پروتکل بومی مبتنی بر UDP به دلایل مختلف می تواند کارآمدتر از TCP باشد. به عنوان مثال، می تواند برخی از بسته ها را به عنوان قابل اعتماد و برخی دیگر را به عنوان غیرقابل اعتماد علامت گذاری کند. بنابراین، او اهمیتی نمی دهد که بسته غیرقابل اعتماد به گیرنده رسیده باشد. یا می تواند چندین جریان داده را پردازش کند تا بسته ای که در یک جریان گم شده است باعث کندی جریان های دیگر نشود. به عنوان مثال، ممکن است یک رشته برای ورودی بازیکن و رشته دیگری برای پیام های چت وجود داشته باشد. اگر یک پیام چت که داده فوری نیست از بین برود، ورودی فوری را کاهش نخواهد داد. یا یک پروتکل اختصاصی ممکن است قابلیت اطمینان را متفاوت از TCP پیاده سازی کند تا در محیط بازی ویدیویی کارآمدتر باشد.

بنابراین، اگر TCP بد است، پس ما می خواهیم پروتکل حمل و نقل خود را بر اساس UDP بسازیم؟

همه چیز کمی پیچیده تر است. حتی اگر TCP برای سیستم‌های شبکه بازی تقریباً کمتر از حد مطلوب است، می‌تواند برای بازی خاص شما کاملاً خوب عمل کند و در وقت ارزشمند شما صرفه‌جویی کند. برای مثال، تأخیر ممکن است برای یک بازی نوبتی یا بازی‌هایی که فقط در شبکه‌های LAN قابل اجرا هستند، مشکلی ایجاد نکند، جایی که تأخیر و از دست دادن بسته‌ها بسیار کمتر از اینترنت است.

بسیاری از بازی های موفق از جمله World of Warcraft، Minecraft و Terraria از TCP استفاده می کنند. با این حال، اکثر FPS ها از پروتکل های مبتنی بر UDP خود استفاده می کنند، بنابراین در ادامه بیشتر در مورد آنها صحبت خواهیم کرد.

اگر می خواهید از TCP استفاده کنید، مطمئن شوید که غیرفعال است الگوریتم ناگل، زیرا بسته ها را قبل از ارسال بافر می کند، به این معنی که تاخیر را افزایش می دهد.

برای کسب اطلاعات بیشتر در مورد تفاوت بین UDP و TCP در زمینه بازی های چند نفره، به مقاله گلن فیدلر مراجعه کنید. UDP در مقابل TCP.

پروتکل اختصاصی

بنابراین می خواهید پروتکل حمل و نقل خود را ایجاد کنید اما نمی دانید از کجا شروع کنید؟ شما خوش شانس هستید، زیرا گلن فیدلر دو مقاله شگفت انگیز در مورد آن نوشت. شما ایده های هوشمندانه زیادی در آنها خواهید یافت.

مقاله اول شبکه سازی برای برنامه نویسان بازی 2008، راحت تر از دومی ساخت پروتکل شبکه بازی 2016. توصیه می کنم از نسخه قدیمی تر شروع کنید.

توجه داشته باشید که گلن فیدلر از طرفداران بزرگ استفاده از پروتکل خود مبتنی بر UDP است. و پس از خواندن مقالات او، احتمالاً نظر او را قبول خواهید کرد که TCP دارای اشکالات جدی در بازی های ویدیویی است و می خواهید پروتکل خود را پیاده سازی کنید.

اما اگر تازه وارد شبکه هستید، به خودتان لطف کنید و از TCP یا کتابخانه استفاده کنید. برای اجرای موفقیت آمیز پروتکل حمل و نقل خود، باید از قبل چیزهای زیادی یاد بگیرید.

کتابخانه های شبکه

اگر به چیزی کارآمدتر از TCP نیاز دارید، اما نمی‌خواهید برای پیاده‌سازی پروتکل خود و وارد شدن به جزئیات زیاد زحمت بکشید، می‌توانید از کتابخانه نت استفاده کنید. تعداد زیادی از آن ها وجود دارد:

من همه آنها را امتحان نکرده ام، اما ENet را ترجیح می دهم زیرا استفاده از آن آسان و قابل اعتماد است. علاوه بر این، دارای مستندات واضح و یک آموزش برای مبتدیان است.

نتیجه گیری پروتکل حمل و نقل

به طور خلاصه، دو پروتکل اصلی انتقال وجود دارد: TCP و UDP. TCP دارای بسیاری از ویژگی های مفید است: قابلیت اطمینان، حفظ سفارش بسته، تشخیص خطا. UDP همه اینها را ندارد، اما TCP، به دلیل ماهیت خود، تاخیر بالایی دارد که برای برخی از بازی ها غیرقابل قبول است. یعنی برای اطمینان از تاخیر کم، می توانید پروتکل خود را بر اساس UDP ایجاد کنید یا از کتابخانه ای استفاده کنید که پروتکل حمل و نقل را روی UDP پیاده سازی می کند و برای بازی های ویدئویی چند نفره سازگار است.

انتخاب بین TCP، UDP و کتابخانه به عوامل مختلفی بستگی دارد. اول، از نیازهای بازی: آیا به تاخیر کم نیاز دارد؟ دوم، از الزامات پروتکل برنامه: آیا به یک پروتکل قابل اعتماد نیاز دارد؟ همانطور که در قسمت بعدی خواهیم دید، امکان ایجاد یک پروتکل کاربردی وجود دارد که یک پروتکل غیرقابل اعتماد برای آن کاملا مناسب است. در نهایت، باید تجربه توسعه دهنده موتور شبکه را نیز در نظر بگیرید.

من دو نکته دارم:

  • پروتکل حمل و نقل را تا حد امکان از بقیه برنامه انتزاع کنید تا به راحتی بدون بازنویسی همه کدها جایگزین شود.
  • بیش از حد بهینه سازی نکنید. اگر متخصص شبکه نیستید و مطمئن نیستید که آیا به پروتکل حمل و نقل مبتنی بر UDP خود نیاز دارید، می توانید با TCP یا کتابخانه ای که قابلیت اطمینان را فراهم می کند شروع کنید و سپس عملکرد را آزمایش و اندازه گیری کنید. اگر مشکل دارید و مطمئن هستید که پروتکل حمل و نقل است، ممکن است وقت آن رسیده باشد که پروتکل حمل و نقل خود را ایجاد کنید.

در پایان این قسمت توصیه می کنم حتما بخوانید مقدمه ای بر برنامه نویسی بازی چند نفره برایان هوک، که بسیاری از موضوعات مورد بحث در اینجا را پوشش می دهد.

پروتکل برنامه

اکنون که می‌توانیم داده‌ها را بین کلاینت‌ها و سرور تبادل کنیم، باید تصمیم بگیریم که چه داده‌هایی و در چه قالبی انتقال دهیم.

طرح کلاسیک این است که کلاینت ها ورودی یا اقداماتی را به سرور ارسال می کنند و سرور وضعیت فعلی بازی را برای مشتریان ارسال می کند.

سرور نه کامل، بلکه حالت فیلتر شده را با موجودیت هایی که نزدیک پخش کننده هستند ارسال می کند. او این کار را به سه دلیل انجام می دهد. اول، حالت کل ممکن است برای ارسال در فرکانس بالا خیلی بزرگ باشد. ثانیاً، مشتریان عمدتاً به داده های دیداری و صوتی علاقه مند هستند، زیرا بیشتر منطق بازی بر روی سرور بازی شبیه سازی شده است. ثالثاً، در برخی از بازی ها، بازیکن نیازی به دانستن داده های خاصی مانند موقعیت دشمن در سمت دیگر نقشه ندارد، زیرا در غیر این صورت می تواند بسته ها را بو بکشد و دقیقاً بداند که برای کشتن او کجا حرکت کند.

سریال سازی

اولین قدم این است که داده هایی را که می خواهیم ارسال کنیم (ورودی یا وضعیت بازی) را به فرمتی مناسب برای انتقال تبدیل کنیم. این فرآیند نامیده می شود سریال سازی.

این ایده بلافاصله به ذهن خطور می کند که از یک قالب قابل خواندن برای انسان، مانند JSON یا XML استفاده کنید. اما این کاملاً ناکارآمد خواهد بود و بیشتر کانال را بیهوده اشغال می کند.

در عوض، توصیه می شود از فرمت باینری استفاده کنید که بسیار فشرده تر است. یعنی بسته ها فقط حاوی چند بایت خواهند بود. در اینجا یک مشکل قابل بررسی است ترتیب بایت، که ممکن است در رایانه های مختلف متفاوت باشد.

برای سریال سازی داده ها، می توانید از یک کتابخانه استفاده کنید، به عنوان مثال:

فقط مطمئن شوید که کتابخانه آرشیوهای قابل حمل ایجاد می کند و از endianness مراقبت می کند.

یک راه حل جایگزین این است که خودتان آن را پیاده سازی کنید، این کار چندان سخت نیست، به خصوص اگر از یک رویکرد داده محور در کد خود استفاده می کنید. علاوه بر این، به شما امکان می دهد بهینه سازی هایی را انجام دهید که همیشه در هنگام استفاده از کتابخانه امکان پذیر نیست.

گلن فیدلر دو مقاله در مورد سریال سازی نوشته است: بسته های خواندن و نوشتن и استراتژی های سریال سازی.

فشرده سازی

مقدار داده های منتقل شده بین کلاینت ها و سرور توسط پهنای باند کانال محدود می شود. فشرده سازی داده به شما این امکان را می دهد که داده های بیشتری را در هر عکس فوری منتقل کنید، نرخ تازه سازی را افزایش دهید یا به سادگی پهنای باند مورد نیاز را کاهش دهید.

بسته بندی بیت

اولین تکنیک بسته بندی بیت است. این شامل استفاده از تعداد بیت هایی است که برای توصیف مقدار مورد نظر لازم است. به عنوان مثال، اگر یک enum دارید که می تواند 16 مقدار مختلف داشته باشد، به جای یک بایت کامل (8 بیت)، می توانید فقط از 4 بیت استفاده کنید.

گلن فیدلر در قسمت دوم مقاله نحوه پیاده سازی آن را توضیح می دهد. بسته های خواندن و نوشتن.

بسته بندی بیت به ویژه با گسسته سازی خوب کار می کند، که موضوع بخش بعدی خواهد بود.

نمونه برداری

نمونه برداری یک تکنیک فشرده سازی با اتلاف است که تنها از زیر مجموعه ای از مقادیر ممکن برای رمزگذاری یک مقدار استفاده می کند. ساده ترین راه برای پیاده سازی گسسته، گرد کردن اعداد ممیز شناور است.

گلن فیدلر (دوباره!) نحوه اعمال گسسته سازی را در عمل در مقاله خود نشان می دهد فشرده سازی عکس فوری.

الگوریتم های فشرده سازی

تکنیک بعدی الگوریتم های فشرده سازی بدون تلفات خواهد بود.

در اینجا، به نظر من، سه الگوریتم جالب است که باید بدانید:

  • کد نویسی هافمن با کد از پیش محاسبه شده، که بسیار سریع است و می تواند نتایج خوبی ایجاد کند. برای فشرده سازی بسته ها در موتور شبکه Quake3 استفاده شد.
  • زلیب یک الگوریتم فشرده سازی با هدف کلی است که هرگز حجم داده ها را افزایش نمی دهد. چگونه می توانید ببینید اینجا، در کاربردهای مختلفی مورد استفاده قرار گرفته است. برای به‌روزرسانی وضعیت‌ها، ممکن است اضافی باشد. اما اگر نیاز به ارسال دارایی ها، متن های طولانی یا زمین به مشتریان از سرور دارید، می تواند مفید باشد.
  • کپی کردن طول اجرا احتمالا ساده ترین الگوریتم فشرده سازی است، اما برای انواع خاصی از داده ها بسیار کارآمد است و می تواند به عنوان یک مرحله پیش پردازش قبل از zlib استفاده شود. به ویژه برای فشرده سازی زمین های متشکل از کاشی ها یا وکسل ها که در آن عناصر همسایه زیادی تکرار شده اند، مناسب است.

فشرده سازی دلتا

تکنیک فشرده سازی نهایی، فشرده سازی دلتا است. این در این واقعیت نهفته است که فقط تفاوت بین حالت فعلی بازی و آخرین حالت دریافت شده توسط مشتری منتقل می شود.

اولین بار در موتور شبکه Quake3 استفاده شد. در اینجا دو مقاله وجود دارد که نحوه استفاده از آن را توضیح می دهد:

گلن فیدلر نیز در قسمت دوم مقاله خود از آن استفاده کرد. فشرده سازی عکس فوری.

رمزگذاری

علاوه بر این، ممکن است لازم باشد انتقال اطلاعات بین کلاینت و سرور را رمزگذاری کنید. چندین دلیل برای این وجود دارد:

  • حریم خصوصی/محرمانه بودن: پیام ها فقط توسط گیرنده قابل خواندن است و هیچ ردگیر شبکه دیگری قادر به خواندن آنها نخواهد بود.
  • احراز هویت: شخصی که می خواهد نقش یک بازیکن را بازی کند باید کلید خود را بداند.
  • جلوگیری از تقلب: برای بازیکنان مخرب ایجاد بسته های تقلب بسیار دشوارتر خواهد بود، آنها باید طرح رمزگذاری را تکرار کنند و کلید را پیدا کنند (که در هر اتصال تغییر می کند).

من به شدت توصیه می کنم از یک کتابخانه برای این کار استفاده کنید. پیشنهاد میکنم استفاده کنید لیب سدیم، زیرا به خصوص ساده است و آموزش های عالی دارد. به خصوص جالب است آموزش در تعویض کلید، که به شما امکان می دهد در هر اتصال جدید کلیدهای جدیدی ایجاد کنید.

پروتکل کاربردی: نتیجه گیری

این پروتکل برنامه را به پایان می رساند. من معتقدم فشرده سازی کاملا اختیاری است و تصمیم برای استفاده از آن فقط به بازی و پهنای باند مورد نیاز بستگی دارد. رمزگذاری، به نظر من، اجباری است، اما در نمونه اولیه می توانید بدون آن انجام دهید.

منطق برنامه

اکنون می‌توانیم وضعیت را در کلاینت به‌روزرسانی کنیم، اما ممکن است با مشکلات تأخیر مواجه شویم. بازیکن، پس از ایجاد یک ورودی، باید منتظر بروزرسانی وضعیت بازی از سرور باشد تا ببیند چه تأثیری بر جهان داشته است.

علاوه بر این، بین دو به روز رسانی حالت، جهان کاملاً ثابت است. اگر نرخ به‌روزرسانی وضعیت پایین باشد، حرکات بسیار تند و ناگهانی خواهند بود.

چندین تکنیک برای کاهش تاثیر این مشکل وجود دارد که در بخش بعدی به آنها خواهم پرداخت.

تکنیک های تاخیری صاف کردن

تمام تکنیک های شرح داده شده در این بخش به تفصیل در مجموعه مورد بحث قرار گرفته است. چند نفره سریع گابریل گامبتا خواندن این سری مقالات عالی را به شدت توصیه می کنم. همچنین شامل یک نسخه نمایشی تعاملی برای مشاهده نحوه عملکرد این تکنیک ها در عمل است.

اولین تکنیک این است که نتیجه ورودی را مستقیماً بدون انتظار برای پاسخ از سرور اعمال کنید. نامیده می شود پیش بینی سمت مشتری. با این حال، هنگامی که مشتری یک به روز رسانی از سرور دریافت می کند، باید تأیید کند که پیش بینی آن درست بوده است. اگر اینطور نیست، او فقط باید وضعیت خود را مطابق آنچه از سرور دریافت کرده تغییر دهد، زیرا سرور مستبد است. این تکنیک برای اولین بار در Quake استفاده شد. شما می توانید در مورد آن در مقاله بیشتر بخوانید. بررسی کد Quake Engine فابین سانگلار [ترجمه در Habré].

مجموعه دوم از تکنیک ها برای هموارسازی حرکت سایر موجودیت ها بین دو به روز رسانی حالت استفاده می شود. دو راه برای حل این مشکل وجود دارد: درون یابی و برون یابی. در حالت درون یابی، دو حالت آخر گرفته شده و انتقال از یکی به دیگری نشان داده می شود. عیب آن این است که باعث کسری کمی از تاخیر می شود، زیرا مشتری همیشه آنچه را که در گذشته اتفاق افتاده است می بیند. برون یابی در مورد پیش بینی محل قرار گرفتن موجودیت ها بر اساس آخرین وضعیت دریافت شده توسط مشتری است. عیب آن این است که اگر موجودیت به طور کامل جهت حرکت را تغییر دهد، خطای زیادی بین پیش بینی و موقعیت واقعی وجود خواهد داشت.

آخرین، پیشرفته ترین تکنیک، که فقط در FPS مفید است، این است جبران تاخیر. هنگام استفاده از جبران تاخیر، سرور تاخیرهای مشتری را در هنگام شلیک به هدف در نظر می گیرد. به عنوان مثال، اگر بازیکنی یک ضربه سر را روی صفحه نمایش خود انجام دهد، اما در واقع هدف او به دلیل تاخیر در مکان دیگری قرار داشته باشد، در این صورت محروم کردن بازیکن از حق کشتن به دلیل تاخیر، ناعادلانه خواهد بود. بنابراین سرور زمان را به زمانی برمی‌گرداند که بازیکن شلیک می‌کند تا چیزی را که بازیکن روی صفحه خود دیده است شبیه‌سازی کند و برخورد بین شلیک و هدف را بررسی کند.

گلن فیدلر (مثل همیشه!) در سال 2004 مقاله ای نوشت فیزیک شبکه (2004)، که در آن او پایه و اساس همگام سازی شبیه سازی های فیزیک بین سرور و مشتری را بنا نهاد. در سال 2014 او مجموعه جدیدی از مقالات را نوشت فیزیک شبکه، که در آن تکنیک های دیگری را برای همگام سازی شبیه سازی های فیزیک توضیح داد.

همچنین دو مقاله در ویکی Valve وجود دارد، منبع شبکه چند نفره и روش‌های جبران تاخیر در طراحی و بهینه‌سازی پروتکل درون‌بازی کلاینت/سرور پرداختن به جبران تاخیر

پیشگیری از تقلب

دو روش اصلی پیشگیری از تقلب وجود دارد.

اول، ارسال بسته های مخرب برای متقلبان سخت تر می شود. همانطور که در بالا ذکر شد، یک راه خوب برای پیاده سازی آن رمزگذاری است.

دوم، سرور معتبر فقط باید دستورات/ورودی/عملیات را دریافت کند. کلاینت نباید بتواند وضعیت سرور را به جز با ارسال ورودی تغییر دهد. سپس سرور، هر بار که ورودی دریافت می کند، باید قبل از اعمال آن، اعتبار آن را بررسی کند.

منطق کاربردی: نتیجه گیری

توصیه می‌کنم روشی را برای شبیه‌سازی تأخیر بالا و نرخ تازه‌سازی پایین پیاده‌سازی کنید تا بتوانید رفتار بازی خود را در شرایط بد آزمایش کنید، حتی زمانی که کلاینت و سرور روی یک دستگاه در حال اجرا هستند. این امر اجرای تکنیک های هموارسازی تاخیر را بسیار ساده می کند.

سایر منابع مفید

اگر می خواهید سایر منابع مدل شبکه را کاوش کنید، می توانید آنها را در اینجا بیابید:

  • وبلاگ گلن فیدلر - ارزش خواندن کل وبلاگ او را دارد، مقالات بسیار عالی در آن وجود دارد. اینجا تمام مقالات در مورد فن آوری های شبکه جمع آوری شده است.
  • شبکه بازی عالی توسط M. Fatih MAR فهرستی جامع از مقالات و ویدیوها در مورد موتورهای شبکه بازی های ویدیویی است.
  • В wiki subreddit r/gamedev همچنین لینک های مفید زیادی وجود دارد.

منبع: www.habr.com

اضافه کردن نظر