Гаразд, по-перше, якщо у вас щось є, і це працює, зазвичай корисно залишити це так. Чому виправляти те, що не порушено?
Але якщо у вас виникають проблеми, і ви хотіли б переписати свій мережевий код, я думаю, у вас є чотири основні варіанти:
- Багатопотоковий код блокування (що ви зараз робите)
- Неблокуючі розетки з тривожним сповіщенням
- Розблокування розеток із повідомленням про готовність змінити
- Асинхронні розетки
Написавши безліч мультиплеера (від однорангових до масово багатокористувацьких) клієнтів і серверів, мені подобається вважати, що варіант 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 чи іншою ОС або програмуванням мовних розеток. Не думайте, що ви розумієте розетки, поки не прочитаєте цю книгу.
Інший ресурс, на якому можна знайти загальний огляд доступних рішень з точки зору програмування декількох сокетів (в основному стосується серверного програмування), - це ця сторінка .