Về mô hình mạng trong trò chơi dành cho người mới bắt đầu

Về mô hình mạng trong trò chơi dành cho người mới bắt đầu
Trong hai tuần qua, tôi đã làm việc trên công cụ trực tuyến cho trò chơi của mình. Trước đó, tôi không biết gì về mạng trong trò chơi, vì vậy tôi đã đọc rất nhiều bài báo và làm rất nhiều thử nghiệm để hiểu tất cả các khái niệm và có thể viết công cụ mạng của riêng mình.

Trong hướng dẫn này, tôi muốn chia sẻ với bạn các khái niệm khác nhau mà bạn cần tìm hiểu trước khi viết công cụ trò chơi của riêng mình, cũng như các tài nguyên và bài viết hay nhất để tìm hiểu chúng.

Nhìn chung, có hai loại kiến ​​trúc mạng chính: ngang hàng và máy khách-máy chủ. Trong kiến ​​trúc ngang hàng (p2p), dữ liệu được truyền giữa bất kỳ cặp người chơi được kết nối nào, trong khi ở kiến ​​trúc máy khách-máy chủ, dữ liệu chỉ được truyền giữa người chơi và máy chủ.

Mặc dù kiến ​​trúc ngang hàng vẫn được sử dụng trong một số trò chơi, nhưng máy khách-máy chủ là tiêu chuẩn: nó dễ triển khai hơn, yêu cầu độ rộng kênh nhỏ hơn và giúp bảo vệ chống gian lận dễ dàng hơn. Vì vậy, trong hướng dẫn này chúng ta sẽ tập trung vào kiến ​​trúc client-server.

Đặc biệt, chúng tôi quan tâm nhất đến các máy chủ độc tài: trong những hệ thống như vậy, máy chủ luôn đúng. Ví dụ: nếu người chơi cho rằng anh ta đang ở tọa độ (10, 5) và máy chủ nói với anh ta rằng anh ta đang ở (5, 3), thì khách hàng nên thay thế vị trí của mình bằng vị trí được máy chủ báo cáo chứ không phải ngược lại. ngược lại. Sử dụng máy chủ có thẩm quyền giúp xác định kẻ gian lận dễ dàng hơn.

Hệ thống chơi game mạng có ba thành phần chính:

  • Giao thức vận chuyển: cách dữ liệu được truyền giữa máy khách và máy chủ.
  • Giao thức ứng dụng: những gì được truyền từ máy khách đến máy chủ và từ máy chủ đến máy khách và ở định dạng nào.
  • Logic ứng dụng: cách sử dụng dữ liệu được truyền để cập nhật trạng thái của máy khách và máy chủ.

Điều rất quan trọng là phải hiểu vai trò của từng bộ phận và những thách thức liên quan đến chúng.

Giao thức vận chuyển

Bước đầu tiên là chọn giao thức truyền dữ liệu giữa máy chủ và máy khách. Có hai giao thức Internet cho việc này: TCP и UDP. Nhưng bạn có thể tạo giao thức truyền tải của riêng mình dựa trên một trong số chúng hoặc sử dụng thư viện sử dụng chúng.

So sánh TCP và UDP

Cả TCP và UDP đều dựa trên IP. IP cho phép một gói được truyền từ nguồn đến người nhận, nhưng không đảm bảo rằng gói đã gửi sớm hay muộn sẽ đến được người nhận, rằng nó sẽ đến được ít nhất một lần và chuỗi các gói sẽ đến đúng địa chỉ. đặt hàng. Hơn nữa, một gói chỉ có thể chứa một lượng dữ liệu giới hạn, được cho bởi giá trị MTU.

UDP chỉ là một lớp mỏng trên IP. Vì vậy, nó có những hạn chế tương tự. Ngược lại, TCP có nhiều tính năng. Nó cung cấp một kết nối có trật tự, đáng tin cậy giữa hai nút với việc kiểm tra lỗi. Do đó, TCP rất thuận tiện và được sử dụng trong nhiều giao thức khác, ví dụ: HTTP, FTP и SMTP. Nhưng tất cả các tính năng này đều có giá: trì hoãn.

Để hiểu tại sao các chức năng này có thể gây ra độ trễ, chúng ta cần hiểu cách thức hoạt động của TCP. Khi nút gửi truyền một gói đến nút nhận, nó sẽ nhận được xác nhận (ACK). Nếu sau một thời gian nhất định nó không nhận được (vì gói hoặc xác nhận bị mất hoặc vì lý do nào khác), thì nó sẽ gửi lại gói. Hơn nữa, TCP đảm bảo rằng các gói được nhận theo đúng thứ tự, vì vậy cho đến khi nhận được gói bị mất, tất cả các gói khác đều không thể được xử lý, ngay cả khi chúng đã được máy chủ nhận nhận.

Nhưng như bạn có thể tưởng tượng, độ trễ trong các trò chơi nhiều người chơi là rất quan trọng, đặc biệt là trong các thể loại hành động như FPS. Đây là lý do tại sao nhiều trò chơi sử dụng UDP với giao thức riêng.

Giao thức dựa trên UDP gốc có thể hiệu quả hơn TCP vì nhiều lý do. Ví dụ: nó có thể đánh dấu một số gói là đáng tin cậy và những gói khác là không đáng tin cậy. Do đó, nó không quan tâm liệu gói tin không đáng tin cậy có đến được người nhận hay không. Hoặc nó có thể xử lý nhiều luồng dữ liệu để gói tin bị mất trong một luồng không làm chậm các luồng còn lại. Ví dụ: có thể có một chuỗi dành cho thông tin nhập của người chơi và một chuỗi khác dành cho tin nhắn trò chuyện. Nếu một tin nhắn trò chuyện không khẩn cấp bị mất, nó sẽ không làm chậm quá trình nhập tin khẩn cấp. Hoặc một giao thức độc quyền có thể triển khai độ tin cậy khác với TCP để hiệu quả hơn trong môi trường trò chơi điện tử.

Vì vậy, nếu TCP quá tệ thì chúng ta sẽ tạo giao thức truyền tải của riêng mình dựa trên UDP?

Nó phức tạp hơn một chút. Mặc dù TCP gần như không tối ưu cho các hệ thống mạng chơi game nhưng nó có thể hoạt động khá tốt cho trò chơi cụ thể của bạn và giúp bạn tiết kiệm thời gian quý báu. Ví dụ: độ trễ có thể không phải là vấn đề đối với trò chơi theo lượt hoặc trò chơi chỉ có thể chơi trên mạng LAN, nơi độ trễ và mất gói thấp hơn nhiều so với trên Internet.

Nhiều trò chơi thành công, bao gồm World of Warcraft, Minecraft và Terraria, sử dụng TCP. Tuy nhiên, hầu hết FPS sử dụng giao thức dựa trên UDP của riêng chúng, vì vậy chúng ta sẽ nói nhiều hơn về chúng ở bên dưới.

Nếu bạn quyết định sử dụng TCP, hãy đảm bảo rằng nó bị tắt Thuật toán Nagle, vì nó đệm các gói trước khi gửi, điều đó có nghĩa là nó làm tăng độ trễ.

Để tìm hiểu thêm về sự khác biệt giữa UDP và TCP trong bối cảnh trò chơi nhiều người chơi, bạn có thể đọc bài viết của Glenn Fiedler UDP so với TCP.

Giao thức riêng

Vì vậy, bạn muốn tạo giao thức truyền tải của riêng mình nhưng không biết bắt đầu từ đâu? Bạn thật may mắn vì Glenn Fiedler đã viết hai bài báo tuyệt vời về vấn đề này. Bạn sẽ tìm thấy rất nhiều suy nghĩ thông minh trong đó.

Bài viết đầu tiên Mạng cho lập trình viên trò chơi Năm 2008, dễ hơn lần thứ hai, Xây dựng giao thức mạng trò chơi 2016. Tôi khuyên bạn nên bắt đầu với cái cũ hơn.

Lưu ý rằng Glenn Fiedler là người ủng hộ mạnh mẽ việc sử dụng giao thức tùy chỉnh dựa trên UDP. Và sau khi đọc các bài viết của anh ấy, bạn có thể sẽ chấp nhận quan điểm của anh ấy rằng TCP có những thiếu sót nghiêm trọng trong trò chơi điện tử và bạn sẽ muốn triển khai giao thức của riêng mình.

Nhưng nếu bạn là người mới làm quen với mạng, hãy tự mình sử dụng TCP hoặc thư viện. Để thực hiện thành công giao thức truyền tải của riêng mình, bạn cần phải tìm hiểu trước rất nhiều điều.

Thư viện mạng

Nếu bạn cần thứ gì đó hiệu quả hơn TCP, nhưng không muốn gặp rắc rối khi triển khai giao thức của riêng mình và đi sâu vào nhiều chi tiết, bạn có thể sử dụng thư viện mạng. Hiện có rất nhiều trong số họ:

Tôi chưa thử tất cả nhưng tôi thích ENet hơn vì nó dễ sử dụng và đáng tin cậy. Ngoài ra, nó có tài liệu rõ ràng và hướng dẫn cho người mới bắt đầu.

Giao thức vận chuyển: Kết luận

Tóm lại: có hai giao thức truyền tải chính: TCP và UDP. TCP có nhiều tính năng hữu ích: độ tin cậy, bảo toàn trật tự gói tin, phát hiện lỗi. UDP không có tất cả những điều này, nhưng về bản chất, TCP có độ trễ tăng lên, điều này không thể chấp nhận được đối với một số trò chơi. Nghĩa là, để đảm bảo độ trễ thấp, bạn có thể tạo giao thức của riêng mình dựa trên UDP hoặc sử dụng thư viện triển khai giao thức truyền tải trên UDP và được điều chỉnh cho các trò chơi điện tử nhiều người chơi.

Sự lựa chọn giữa TCP, UDP và thư viện phụ thuộc vào một số yếu tố. Thứ nhất, từ nhu cầu của game: có cần độ trễ thấp không? Thứ hai, từ các yêu cầu về giao thức ứng dụng: nó có cần một giao thức đáng tin cậy không? Như chúng ta sẽ thấy trong phần tiếp theo, có thể tạo một giao thức ứng dụng mà giao thức không tin cậy khá phù hợp. Cuối cùng, bạn cũng cần tính đến kinh nghiệm của nhà phát triển công cụ mạng.

Tôi có hai lời khuyên:

  • Tóm tắt giao thức truyền tải khỏi phần còn lại của ứng dụng càng nhiều càng tốt để có thể dễ dàng thay thế nó mà không cần viết lại toàn bộ mã.
  • Đừng tối ưu hóa quá mức. Nếu bạn không phải là chuyên gia về mạng và không chắc chắn liệu mình có cần giao thức truyền tải dựa trên UDP tùy chỉnh hay không, bạn có thể bắt đầu với TCP hoặc thư viện cung cấp độ tin cậy, sau đó kiểm tra và đo lường hiệu suất. Nếu có vấn đề phát sinh và bạn tin chắc rằng nguyên nhân là do giao thức truyền tải thì có lẽ đã đến lúc tạo giao thức truyền tải của riêng bạn.

Ở cuối phần này, tôi khuyên bạn nên đọc Giới thiệu về lập trình trò chơi nhiều người chơi của Brian Hook, đề cập đến nhiều chủ đề được thảo luận ở đây.

Giao thức ứng dụng

Bây giờ chúng ta có thể trao đổi dữ liệu giữa máy khách và máy chủ, chúng ta cần quyết định dữ liệu nào sẽ truyền và ở định dạng nào.

Sơ đồ cổ điển là máy khách gửi đầu vào hoặc hành động đến máy chủ và máy chủ gửi trạng thái trò chơi hiện tại cho máy khách.

Máy chủ không gửi trạng thái đầy đủ mà gửi trạng thái được lọc với các thực thể nằm gần trình phát. Anh ấy làm điều này vì ba lý do. Đầu tiên, trạng thái hoàn chỉnh có thể quá lớn để có thể truyền ở tần số cao. Thứ hai, khách hàng chủ yếu quan tâm đến dữ liệu hình ảnh và âm thanh, vì hầu hết logic trò chơi được mô phỏng trên máy chủ trò chơi. Thứ ba, trong một số trò chơi, người chơi không cần biết một số dữ liệu nhất định, chẳng hạn như vị trí của kẻ thù ở phía bên kia bản đồ, nếu không, anh ta có thể đánh hơi các gói tin và biết chính xác nơi cần di chuyển để tiêu diệt hắn.

Tuần tự hóa

Bước đầu tiên là chuyển đổi dữ liệu chúng ta muốn gửi (trạng thái đầu vào hoặc trò chơi) sang định dạng phù hợp để truyền tải. Quá trình này được gọi là tuần tự hóa.

Ý nghĩ ngay lập tức xuất hiện trong đầu là sử dụng định dạng mà con người có thể đọc được, chẳng hạn như JSON hoặc XML. Nhưng điều này sẽ hoàn toàn không hiệu quả và sẽ lãng phí phần lớn kênh.

Thay vào đó, nên sử dụng định dạng nhị phân, nhỏ gọn hơn nhiều. Tức là các gói sẽ chỉ chứa một vài byte. Có một vấn đề cần cân nhắc ở đây thứ tự byte, có thể khác nhau trên các máy tính khác nhau.

Để tuần tự hóa dữ liệu, bạn có thể sử dụng thư viện, ví dụ:

Chỉ cần đảm bảo thư viện tạo các kho lưu trữ di động và quan tâm đến độ bền.

Một giải pháp thay thế là tự mình triển khai; nó không đặc biệt khó khăn, đặc biệt nếu bạn sử dụng cách tiếp cận lấy dữ liệu làm trung tâm cho mã của mình. Ngoài ra, nó sẽ cho phép bạn thực hiện các tối ưu hóa không phải lúc nào cũng có thể thực hiện được khi sử dụng thư viện.

Glenn Fiedler đã viết hai bài báo về việc xuất bản nhiều kỳ: Gói đọc và ghi и Chiến lược tuần tự hóa.

Nén

Lượng dữ liệu được truyền giữa máy khách và máy chủ bị giới hạn bởi băng thông của kênh. Nén dữ liệu sẽ cho phép bạn truyền nhiều dữ liệu hơn trong mỗi ảnh chụp nhanh, tăng tần suất cập nhật hoặc đơn giản là giảm yêu cầu về kênh.

Bao bì bit

Kỹ thuật đầu tiên là đóng gói bit. Nó bao gồm việc sử dụng chính xác số bit cần thiết để mô tả giá trị mong muốn. Ví dụ: nếu bạn có một enum có thể có 16 giá trị khác nhau thì thay vì toàn bộ byte (8 bit), bạn chỉ có thể sử dụng 4 bit.

Glenn Fiedler giải thích cách triển khai điều này trong phần thứ hai của bài viết Gói đọc và ghi.

Việc đóng gói bit đặc biệt hiệu quả với việc lấy mẫu, đây sẽ là chủ đề của phần tiếp theo.

Lấy mẫu

Lấy mẫu là một kỹ thuật nén mất dữ liệu chỉ sử dụng một tập hợp con các giá trị có thể để mã hóa một giá trị. Cách dễ nhất để thực hiện rời rạc hóa là làm tròn số dấu phẩy động.

Glenn Fiedler (một lần nữa!) chỉ ra cách áp dụng việc lấy mẫu vào thực tế trong bài viết của mình Nén ảnh chụp nhanh.

Thuật toán nén

Kỹ thuật tiếp theo sẽ là thuật toán nén không mất dữ liệu.

Theo tôi, đây là ba thuật toán thú vị nhất mà bạn cần biết:

  • Mã hóa Huffman với mã được tính toán trước, cực kỳ nhanh và có thể tạo ra kết quả tốt. Nó được sử dụng để nén các gói trong công cụ mạng Quake3.
  • zlib là một thuật toán nén có mục đích chung không bao giờ làm tăng lượng dữ liệu. Làm thế nào bạn có thể nhìn thấy đây, nó đã được sử dụng trong nhiều ứng dụng khác nhau. Nó có thể dư thừa cho việc cập nhật trạng thái. Nhưng nó có thể hữu ích nếu bạn cần gửi nội dung, văn bản dài hoặc địa hình cho khách hàng từ máy chủ.
  • Sao chép độ dài chạy - Đây có lẽ là thuật toán nén đơn giản nhất nhưng lại rất hiệu quả đối với một số loại dữ liệu nhất định và có thể được sử dụng làm bước tiền xử lý trước zlib. Nó đặc biệt thích hợp để nén địa hình bao gồm các ô hoặc voxels trong đó nhiều phần tử liền kề được lặp lại.

nén delta

Kỹ thuật nén cuối cùng là nén delta. Nó bao gồm thực tế là chỉ những khác biệt giữa trạng thái trò chơi hiện tại và trạng thái cuối cùng mà khách hàng nhận được mới được truyền đi.

Nó lần đầu tiên được sử dụng trong công cụ mạng Quake3. Dưới đây là hai bài viết giải thích cách sử dụng nó:

Glenn Fiedler cũng sử dụng nó trong phần thứ hai của bài viết của mình Nén ảnh chụp nhanh.

Mã hóa

Ngoài ra, bạn có thể cần mã hóa việc truyền thông tin giữa máy khách và máy chủ. Cái này có một vài nguyên nhân:

  • quyền riêng tư/bảo mật: chỉ người nhận mới có thể đọc tin nhắn và không người nào khác đang dò tìm mạng có thể đọc được chúng.
  • xác thực: người muốn đóng vai người chơi phải biết chìa khóa của mình.
  • Ngăn chặn gian lận: Những kẻ chơi độc hại sẽ khó khăn hơn nhiều khi tạo các gói gian lận của riêng mình, họ sẽ phải tạo lại sơ đồ mã hóa và tìm ra khóa (thay đổi theo mỗi kết nối).

Tôi thực sự khuyên bạn nên sử dụng thư viện cho việc này. Tôi đề nghị sử dụng libnatri, bởi vì nó đặc biệt đơn giản và có những hướng dẫn tuyệt vời. Đặc biệt thú vị là hướng dẫn về trao đổi chìa khóa, cho phép bạn tạo khóa mới với mỗi kết nối mới.

Giao thức ứng dụng: Kết luận

Điều này kết thúc giao thức ứng dụng của chúng tôi. Tôi tin rằng việc nén là hoàn toàn tùy chọn và quyết định sử dụng nó chỉ phụ thuộc vào trò chơi và băng thông được yêu cầu. Theo tôi, mã hóa là bắt buộc, nhưng trong nguyên mẫu đầu tiên, bạn có thể thực hiện mà không cần nó.

Logic ứng dụng

Hiện tại, chúng tôi có thể cập nhật trạng thái trong ứng dụng khách nhưng có thể gặp phải vấn đề về độ trễ. Người chơi sau khi hoàn thành việc nhập liệu cần đợi trạng thái trò chơi cập nhật từ máy chủ để xem nó có tác động gì đến thế giới.

Hơn nữa, giữa hai lần cập nhật trạng thái, thế giới hoàn toàn tĩnh. Nếu tốc độ cập nhật trạng thái thấp thì chuyển động sẽ rất giật.

Có một số kỹ thuật để giảm tác động của vấn đề này và tôi sẽ trình bày chúng trong phần tiếp theo.

Kỹ thuật làm mịn độ trễ

Tất cả các kỹ thuật được mô tả trong phần này sẽ được thảo luận chi tiết trong loạt bài này. Nhiều người chơi nhịp độ nhanh Gabriel Gambetta. Tôi thực sự khuyên bạn nên đọc loạt bài viết xuất sắc này. Nó cũng bao gồm một bản demo tương tác cho phép bạn xem các kỹ thuật này hoạt động như thế nào trong thực tế.

Kỹ thuật đầu tiên là áp dụng trực tiếp kết quả đầu vào mà không cần chờ phản hồi từ máy chủ. Nó được gọi là dự báo phía khách hàng. Tuy nhiên, khi máy khách nhận được bản cập nhật từ máy chủ, nó phải xác minh rằng dự đoán của nó là chính xác. Nếu không phải như vậy thì anh ta chỉ cần thay đổi trạng thái của mình theo những gì anh ta nhận được từ máy chủ, vì máy chủ là độc tài. Kỹ thuật này lần đầu tiên được sử dụng trong Quake. Bạn có thể đọc thêm về nó trong bài viết Đánh giá mã Quake Engine Fabien Sanglars [dịch trên Habré].

Nhóm kỹ thuật thứ hai được sử dụng để làm trơn tru chuyển động của các thực thể khác giữa hai lần cập nhật trạng thái. Có hai cách để giải quyết vấn đề này: nội suy và ngoại suy. Trong trường hợp nội suy, hai trạng thái cuối cùng được lấy và sự chuyển đổi từ trạng thái này sang trạng thái khác được hiển thị. Nhược điểm của nó là gây ra một chút chậm trễ vì khách hàng luôn nhìn thấy những gì đã xảy ra trong quá khứ. Phép ngoại suy là việc dự đoán vị trí hiện tại của các thực thể dựa trên trạng thái cuối cùng mà khách hàng nhận được. Nhược điểm của nó là nếu thực thể thay đổi hoàn toàn hướng chuyển động thì sẽ có sai số lớn giữa dự báo và vị trí thực tế.

Kỹ thuật mới nhất, tiên tiến nhất chỉ hữu ích trong FPS là bù lag. Khi sử dụng tính năng bù độ trễ, máy chủ sẽ tính đến độ trễ của máy khách khi bắn vào mục tiêu. Ví dụ: nếu một người chơi thực hiện một cú headshot trên màn hình của họ, nhưng trên thực tế, mục tiêu của họ đang ở một vị trí khác do bị trì hoãn, thì sẽ không công bằng nếu từ chối quyền giết của người chơi do bị trì hoãn. Do đó, máy chủ sẽ tua lại thời gian về thời điểm người chơi bắn để mô phỏng những gì người chơi nhìn thấy trên màn hình của họ và kiểm tra xem có va chạm giữa phát bắn của họ và mục tiêu hay không.

Glenn Fiedler (như mọi khi!) đã viết một bài báo vào năm 2004 Vật lý mạng (2004), trong đó ông đặt nền móng cho việc đồng bộ hóa các mô phỏng vật lý giữa máy chủ và máy khách. Năm 2014 ông viết một loạt bài mới Vật lý mạng, mô tả các kỹ thuật khác để đồng bộ hóa mô phỏng vật lý.

Ngoài ra còn có hai bài viết trên wiki Valve, Nguồn Mạng nhiều người chơi и Phương pháp bù độ trễ trong thiết kế và tối ưu hóa giao thức trong trò chơi của máy khách/máy chủ trong đó xem xét bồi thường cho sự chậm trễ.

Ngăn chặn gian lận

Có hai kỹ thuật chính để ngăn chặn gian lận.

Đầu tiên: khiến những kẻ gian lận khó gửi các gói độc hại hơn. Như đã đề cập ở trên, một cách tốt để thực hiện điều này là mã hóa.

Thứ hai: một máy chủ độc đoán chỉ nên nhận lệnh/đầu vào/hành động. Máy khách không thể thay đổi trạng thái trên máy chủ ngoài việc gửi đầu vào. Sau đó, mỗi lần máy chủ nhận được đầu vào, nó phải kiểm tra xem nó có hợp lệ hay không trước khi sử dụng.

Logic ứng dụng: kết luận

Tôi khuyên bạn nên triển khai cách mô phỏng độ trễ cao và tốc độ làm mới thấp để có thể kiểm tra hoạt động của trò chơi trong điều kiện kém, ngay cả khi máy khách và máy chủ đang chạy trên cùng một máy tính. Điều này sẽ đơn giản hóa rất nhiều việc thực hiện các kỹ thuật làm mịn độ trễ.

Các tài nguyên hữu ích khác

Nếu bạn muốn khám phá các tài nguyên khác về mô hình mạng, bạn có thể tìm thấy chúng tại đây:

Nguồn: www.habr.com

Thêm một lời nhận xét