Хорошо, во-первых, если у вас есть что-то, и оно работает, обычно хорошая идея оставить это так. Зачем исправлять то, что не сломано?
Но если у вас возникли проблемы, и вы действительно хотите переписать свой сетевой код, я думаю, у вас есть четыре основных варианта:
- Многопоточный код блокировки (что вы сейчас делаете)
- Неблокирующие розетки с уведомлением о срабатывании уровня
- Неблокирующие розетки с уведомлением об изменении готовности
- Асинхронные розетки
Написав множество многопользовательских (от одноранговых до массовых многопользовательских) клиентов и серверов, мне нравится думать, что вариант 2 ведет к наименьшей сложности и довольно хорошей производительности как для серверной, так и для клиентской частей игры. В качестве второй секунды я бы выбрал вариант 4, но для этого обычно требуется переосмыслить всю программу, и я считаю ее полезной для серверов, а не для клиентов.
В частности, я бы посоветовал не блокировать сокеты в многопоточной среде, поскольку это обычно приводит к блокировке и другим функциям синхронизации, которые не только значительно увеличивают сложность кода, но также могут ухудшать его производительность, поскольку некоторые потоки ожидают другие.
Но, кроме всего этого, большинство реализаций сокетов (и большинство реализаций ввода / вывода при этом) неблокируют на самом низком уровне. Операции блокирования просто предусмотрены для упрощения разработки тривиальных программ. Когда сокет блокируется, процессор в этом потоке полностью простаивает, так зачем создавать неблокирующую абстракцию над блокирующей абстракцией над уже неблокирующей задачей?
Неблокирующее программирование сокетов немного утомительно, если вы еще не пробовали его, но оказывается, что это довольно просто, и если вы уже проводите опрос ввода, у вас уже есть способ делать неблокирующие сокеты.
Первое, что вы хотите сделать, это установить сокет в неблокирующее состояние. Вы делаете это с fcntl()
.
После этого, прежде чем делать send()
, recv()
, sendto()
, recvfrom()
, accept()
( connect()
немного отличается) или другие вызовы , которые могут блокировать поток, вы звоните select()
на сокете. select()
сообщает вам, может ли последующая операция чтения или записи быть выполнена на сокете без его блокировки. Если это так, вы можете безопасно выполнить нужную операцию, и сокет не заблокируется.
Включить это в игру довольно просто. Если у вас уже есть игровой цикл, например, вот так:
while game_is_running do
poll_input()
update_world()
do_sounds()
draw_world()
end
Вы можете изменить это, чтобы выглядеть так:
while game_is_running do
poll_input()
read_network()
update_world()
do_sounds()
write_network()
draw_world()
end
где
function read_network()
while select(socket, READ) do
game.net_input.enqueue(recv(socket))
end
end
а также
function write_network()
while not game.net_output.empty and select(socket, WRITE) do
send(socket, game.net_output.dequeue())
end
end
С точки зрения ресурсов, единственная книга, которую я думаю, что каждый должен иметь на своих книжных полках, даже если это единственная книга, которую они имеют, это " Сетевое программирование Unix, Том 1 " покойного Ричарда Стивенса. Неважно, занимаетесь ли вы Windows или другой ОС или программированием языковых сокетов. Не думайте, что понимаете сокеты, пока не прочитаете эту книгу.
Другой ресурс, где вы можете найти общий обзор доступных решений с точки зрения программирования нескольких сокетов (в основном это касается программирования на сервере), - эта страница .