Я программирую онлайн-игру по двум причинам: первая для ознакомления с запросами сервера / клиента в среде реального времени (в отличие от чего-то вроде обычного веб-браузера, который работает не в реальном времени) и для того, чтобы на самом деле намочить руки в этой области , так что я могу приступить к собственно правильному проектированию.
Anywho, я делаю это на C++, и я использую winsock для обработки моих основных, базовых сетевых тестов. Я, очевидно, хочу использовать ограничитель кадров и в какой-то момент запустить 3D и все такое, и моя основная проблема в том, что когда я выполняю send () или receive (), программа любезно бездействует и ждет ответа. Это может привести к скорости 8 кадров в секунду даже при лучшем подключении к Интернету.
Поэтому для меня очевидное решение - вынуть сетевой код из основного процесса и запустить его в отдельном потоке. В идеале я бы назвал "send" в моем основном процессе, который передал бы сетевому потоку указатель на сообщение, а затем периодически (каждый кадр) проверял, получил ли сетевой поток ответ, или истекло ли время ожидания, или что-то еще. есть ты. В идеальном мире у меня на самом деле было бы 2 или более сетевых потока, работающих одновременно, так что я мог бы сказать, запустить окно чата и выполнить фоновую загрузку элемента брони, и при этом позволить игроку бегать сразу.
Основная моя проблема в том, что для меня это в новинку. Я понимаю концепция потоковой передачи, но вижу некоторые серьезные проблемы, например, что произойдет, если два потока попытаются прочитать / записать один и тот же адрес памяти одновременно и т. д. Я знаю, что уже существуют методы для обработки такого рода из вещей, поэтому я ищу предложения о том, как лучше всего реализовать что-то подобное. По сути, мне нужен поток A, чтобы иметь возможность запускать процесс в потоке B, отправив фрагмент данных, опросив статус потока B, а затем получив ответ, также как фрагмент данных, в идеале без каких-либо серьезных сбоев. ^ _ ^ Я буду беспокоиться о том, что на самом деле содержат эти данные, и как обрабатывать отброшенные пакеты и т. д. Позже, мне просто нужно сначала сделать это.
Спасибо за любую помощь / совет.
PS: Просто подумал об этом, может упростить вопрос. Есть ли способ использовать систему обработки событий Windows в моих интересах? Например, возможно ли, чтобы поток A где-то инициализировал данные, а затем запускал событие в потоке B, чтобы он собирал данные, и наоборот для потока B, чтобы сообщить потоку A, что это было сделано? Это, вероятно, решило бы многие мои проблемы, поскольку мне действительно не нужны оба потока, чтобы иметь возможность работать с данными одновременно, это скорее передача эстафеты. Я просто не знаю, возможно ли это между двумя разными потоками. (Я знаю, что один поток может создавать свои собственные сообщения для обработчика событий.)





Я предлагаю изучить неблокирующие сокеты для быстрого исправления. Используя неблокирующие сокеты, send () и recv () не блокируются, а с помощью функции select () вы можете получать любые ожидающие данные в каждом кадре.
Рассматривайте это как проблема производитель-потребитель: при получении ваш сетевой коммуникационный поток является производителем, а поток пользовательского интерфейса - потребителем. При отправке все как раз наоборот. Реализуйте простой буферный класс, который предоставляет вам такие методы, как push и pop (pop должен блокировать сетевой поток и неблокировать поток пользовательского интерфейса).
Вместо использования системы событий Windows я бы предпочел что-то более портативное, например Переменные условия повышения.
Я не кодирую игры, но я использовал систему, похожую на ту, что предлагал pukku. Он отлично подходит для таких вещей, как буфер, приоритизирующий ваши сообщения для обработки, если у вас есть такая необходимость.
Я думаю о них как о почтовых ящиках на поток. Вы хотите отправить пакет? Попросите ProcessThread создать «сообщение потока» с полезной нагрузкой для передачи по сети и «отправить» его в NetworkThread (т.е. протолкнуть его в очередь / почтовый ящик NetworkThread и сигнализировать переменной условия NetworkThread, чтобы он проснулся и сними это). Когда NetworkThread получает ответ, упакуйте его в сообщение потока и отправьте обратно в ProcessThread таким же образом. Разница в том, что ProcessThread не будет заблокирован для переменной условия, а просто будет опрашивать mailbox.empty (), когда вы хотите проверить ответ.
Возможно, вы захотите отправлять и извлекать файлы напрямую, но для более крупных проектов более удобным способом является реализация схемы toThreadName, fromThreadName в базовом классе ThreadMsg и почтового отделения, в котором потоки регистрируют свой почтовый ящик. Затем PostOffice отправляет сообщение (ThreadMsg *); функция, которая получает / отправляет сообщения в соответствующий почтовый ящик на основе получателя и отправителя. Почтовый ящик (класс буфера / очереди) содержит ThreadMsg * = receiveMessage (), в основном выталкивая его из базовой очереди.
В зависимости от ваших потребностей, у вас может быть ThreadMsg, содержащий процесс виртуальной функции (..), который может быть соответствующим образом переопределен в производных классах, или просто обычный класс ThreadMessage с элементами to, from и функцией getPayload () для возврата сырые данные и обрабатывать их непосредственно в ProcessThread.
Надеюсь это поможет.
Предложение BlodBath о неблокирующих сокетах - потенциально правильный подход.
Если вы пытаетесь избежать использования многопоточного подхода, вы можете исследовать использование настройки перекрывающегося ввода-вывода в ваших сокетах. Они не будут блокироваться, когда вы выполняете передачу или прием, но имеют дополнительный бонус в виде возможности ожидания нескольких событий в рамках одного цикла событий. Когда ваша передача закончится, вы получите сообщение о событии. (подробности см. в это)
Это не несовместимо с многопоточным подходом, поэтому есть возможность передумать позже. ;-)
О дизайне вашего многопоточного приложения. Лучше всего проработать все внешние действия, о которых вы хотите получать предупреждения. Например, до сих пор в вашем вопросе вы указали сетевые передачи, сетевые приемы и активность пользователей. В зависимости от количества одновременных подключений, с которыми вы собираетесь иметь дело, вы, вероятно, обнаружите, что концептуально проще иметь поток на сокет (при условии небольшого количества сокетов), где каждый поток отвечает за всю обработку этого сокета. .
Затем вы можете реализовать некоторую форму системы обмена сообщениями между вашими потоками, как было предложено RC. Организуйте свою систему так, чтобы при отправке сообщения в конкретный поток отправлялось и событие. Затем ваши потоки могут быть отправлены в спящий режим в ожидании одного из этих событий. (а также любые другие стимулы, такие как события сокета, пользовательские события и т. д.)
Вы совершенно правы в том, что вам нужно быть осторожным в ситуациях, когда несколько потоков пытаются получить доступ к одному и тому же фрагменту памяти. Здесь можно использовать мьютексы и семафоры.
Также помните об ограничениях вашего графического интерфейса, когда дело доходит до многопоточности. Некоторое обсуждение этой темы можно найти в в этом вопросе. Но сокращенная версия состоит в том, что большинство (и Windows является одним из таких) графических интерфейсов не позволяют нескольким потокам выполнять операции графического интерфейса одновременно. Чтобы обойти эту проблему, вы можете использовать насос сообщений в своем приложении, отправив пользовательские сообщения в свой поток графического интерфейса, чтобы заставить Это выполнять операции графического интерфейса.
Самое легкое
для вас будет просто вызвать Windows API QueueUserWorkItem. Все, что вам нужно указать, - это функцию, которую будет выполнять поток, и переданный ему ввод. Пул потоков будет автоматически создан для вас и выполняемых в нем заданий. Новые потоки будут создаваться по мере необходимости.
http://msdn.microsoft.com/en-us/library/ms684957(VS.85).aspx
Больше контроля
У вас может быть более подробный контроль, используя другой набор API, который снова может управлять пулом потоков за вас -
http://msdn.microsoft.com/en-us/library/ms686980(VS.85).aspx
Сделай сам
Если вы хотите контролировать все аспекты создания потока и управления пулом, вам придется создавать потоки самостоятельно, решать, как они должны заканчиваться, сколько создавать и т. д. (Beginthreadex - это api, который вы должны использовать для создания потоков. Если вы используйте MFC, вы должны использовать функцию AfxBeginThread).
Отправка заданий в рабочие потоки - Порты завершения Io
В этом случае вам также придется беспокоиться о том, как сообщать о своих вакансиях - я бы порекомендовал IoCOmpletionPorts для этого. Это самый масштабируемый механизм уведомлений, который я в настоящее время знаю, созданный для этой цели. У него есть дополнительное преимущество, заключающееся в том, что он реализован в ядре, так что вы избегаете всевозможных ситуаций мертвой блокировки, с которыми вы можете столкнуться, если решите что-то обработать самостоятельно.
Эта статья покажет вам, как с примерами кода -
http://blogs.msdn.com/larryosterman/archive/2004/03/29/101329.aspx
Обратное общение - сообщения Windows
Вы можете использовать сообщения Windows, чтобы передать статус своему родительскому потоку, поскольку он все равно ожидает сообщения. используйте для этого функцию PostMessage. (и проверьте на наличие ошибок)
ps: вы также можете выделить данные, которые необходимо отправить, на выделенный указатель, а затем рабочий поток может позаботиться об их удалении после отправки. Таким образом, вы также избежите трафика указателя возврата.
Некоторые темы, которые могут вас заинтересовать:
мьютекс: мьютекс позволяет заблокировать доступ к определенным ресурсам только для одного потока
семафор: способ определить, сколько пользователей у определенного ресурса все еще есть (= сколько потоков обращаются к нему), и способ доступа потоков к ресурсу. Мьютекс - это особый случай семафора.
критическая секция: фрагмент кода, защищенный мьютексом (улица только с одной полосой движения), по которому может перемещаться только один поток за раз.
очередь сообщений: способ распределения сообщений в централизованной очереди
межпроцессное взаимодействие (IPC) - способ взаимодействия потоков и процессов друг с другом через именованные каналы, разделяемую память и многие другие способы (это скорее концепция, чем особый метод)
Все темы, выделенные жирным шрифтом, можно легко найти в поисковой системе.
Есть ли решение для машин, отличных от Windows?