Архитектура зависит от того, что вы хотите сделать с входящими данными клиентов. Я предполагаю, что для каждого входящего сообщения вы выполняете некоторые вычисления и, вероятно, также возвращаете ответ.
В этом случае я бы создал 1 основной поток прослушивателя, который получает все входящие сообщения (на самом деле, если ваше оборудование имеет более 1 физического сетевого устройства, я бы использовал поток прослушивателя на устройство и удостоверился, что каждый из них прослушивает конкретное устройство).
Получите количество процессоров, которые у вас есть на вашем компьютере, и создайте рабочие потоки для каждого процессора и свяжите их каждый поток с одним процессором (возможно, количество рабочих потоков должно быть num_of_cpu-1, чтобы оставить доступный процессор для слушателя и диспетчера).
У каждого потока есть очередь и семафор, основной поток слушателя просто помещает входящие данные в эти очереди. Есть много способов выполнить балансировку нагрузки (об этом поговорим позже).
Каждый рабочий поток работает только с данными запросами и помещает ответ в другую очередь, считываемую диспетчером.
Диспетчер - здесь есть 2 варианта: использовать поток для диспетчера (или поток для каждого сетевого устройства, как для слушателей), или же диспетчер должен быть тем же потоком, что и слушатель.
Некоторое преимущество заключается в том, что они оба размещаются в одном потоке, поскольку это облегчает обнаружение потерянного соединения с сокетом и использование одних и тех же fds для чтения и записи без синхронизации потока. Однако может случиться так, что использование 2-х разных потоков даст лучшую производительность, его нужно протестировать.
Примечание о балансировке нагрузки:
Это отдельная тема.
Самое простое - использовать одну очередь для всех рабочих потоков, но проблема в том, что они должны блокироваться, чтобы извлекать элементы, и блокировка может ухудшить производительность. (Но вы получаете максимально сбалансированную нагрузку).
Другим довольно простым подходом было бы иметь личную очередь для каждого работника и выполнять циклический перебор при вставке. После каждого цикла X проверяйте размер всех очередей. Если некоторые очереди намного больше, чем другие, оставьте их для следующих циклов X, а затем снова проверьте их. Это не лучший подход, но он прост в реализации и дает некоторую балансировку нагрузки, когда блокировка не требуется.
Кстати, есть способ реализовать очередь между двумя потоками без блокировки - но это тоже другая тема.
Надеюсь, это поможет,
Парень