Я пытаюсь концептуально работать через модель для приложения-сокета клиент-сервер, которое я пишу в С# (как клиент, так и сервер). Мой сервер должен будет обрабатывать сразу несколько клиентов, и желательно сразу несколько запросов от клиента. Я разработал контейнер для моего сообщения, где я отправлю заголовок с фиксированной длиной в начале каждого сообщения, которое будет содержать (помимо прочего) длину сообщения. У меня есть некоторый опыт программирования сокетов в С#, поэтому мне удобно использовать асинхронные сокеты.
Главное, с чем я столкнулся, концептуально, мне нужно, чтобы клиент и сервер могли получать сообщения в любое время. Клиент установит соединение и останется "вошедшим в систему" (например, IM-клиент), и ему нужно будет как получать данные в произвольные моменты времени, так и делать запросы в произвольные моменты времени. Я также хотел, чтобы часть моего протокола получала ответ на каждый сделанный запрос (будь то от сервера к клиенту или от клиента к серверу).
Я хотел бы иметь возможность использовать один сокет, если это возможно. Я считаю, что я мог бы сделать эту работу с использованием двух сокетов, один для создания запросов server- > client и один для клиент- > серверных запросов, но я не хочу лишних сложностей с двумя портами/соединениями. Однако, используя один сокет, я не уверен, как управлять отправкой запросов и получать ответы, когда они могут чередоваться.
Я не могу найти примеры похожих серверов или клиентов в моем поиске. Спасибо всем, кто предлагает любые ides.
Если вы используете TCP, то вам обязательно придется иметь один сокет для каждого клиента на стороне сервера, а также сокет для прослушивания соединений. Для UDP вы можете сделать это все с одним сокетом. Независимо от того, сделайте следующее для вашего одного сокета UDP или для каждого клиентского сокета TCP:
У вас есть BeginReceive
в любое время и BeginWrite
, когда происходит необходимое событие (т.е. пользователь нажимает кнопку или появляется сообщение о том, что вам нужно что-то делать).
EDIT: в ответ на ваш комментарий способ, которым я бы справился с этим, состоял бы в том, чтобы включить идентификатор запроса в ваш заголовок. Всякий раз, когда отправка запроса (с любого конца) включает уникальное значение для этого сообщения. Используйте Dictionary<RequestId, RequestState>
(с соответствующими подстановками для типов) и посмотрите, связано ли входящее сообщение с ранее существующим запросом. Кроме того, вы можете указать, что все идентификаторы с высоким набором бит - это запросы, исходящие от клиента, и идентификаторы с высоким битом, очищенным от сервера, чтобы избежать конфликтов:
Server Client
Request 0x00 -------------> Starts processing
Starts processing <------- (unrelated) Request 0x80
Request 0x00 complete <---- Sends response to 0x00
Sends response to 0x80 ---> Request 0x80 complete
Это система, в которой используется протокол AOL OSCAR для (возможно медленных) запросов состояния.
EDIT2: Hm, хорошо... вы действительно хотите заблокировать его? Что-то, что вы могли бы сделать, это иметь отдельный поток, обрабатывающий вызовы Send
/Receive
. Этот поток будет связываться с основным потоком через потокобезопасную очередь "отправить сообщение" и аналогичную "полученную сообщение" psuedo-queue. Причина, по которой я называю последнюю очередь psuedo, заключается в том, что вы хотели бы, чтобы возможность выводить сообщения из очереди из очереди. Основной поток помещает сообщение в очередь отправки, а затем блокирует очередь приема. Каждый раз, когда поток сокета обновляет очередь приема, основной поток просыпается и проверяет, есть ли сообщение, которое оно хочет. Если это так, то потребуется (не по порядку) и закончить желаемую операцию. Если сообщение еще не существует, оно просто будет заблокировано в очереди. Когда операция завершена, она только возобновит нормальную работу и получит сообщения в очереди от очереди приема и обработает их или сделает что-то еще.
В этом смысл? Прошу прощения, если это смущает...
Сокеты двунаправленные. Вам нужен только один сокет для отправки данных между двумя конечными точками. Но на клиенте вам, вероятно, потребуется один поток, постоянно читающий сокет, и уведомление о каком-то компоненте или очередность получаемых данных, поэтому он обрабатывается в другом месте, а другой поток отправляет данные через один и тот же сокет. Таким образом, у вас асинхронная связь.
Но на стороне сервера вам нужно открыть ServerSocket, который будет привязываться к TCP-порту и принимать соединения на этом порту; каждое соединение, которое вы принимаете, является новым Socket. Таким образом, у вас есть сокет для каждого клиента, и вам, вероятно, понадобится один поток для каждого клиента.
Вам необходимо установить протокол для сообщений, которые вы будете передавать через сокет. Вы можете либо brew создать собственный протокол, либо просмотреть его в зависимости от того, что вы хотите сделать. Обычно вы сначала отправляете два или четыре байта с длиной для остальной части сообщения, поэтому другой стороне известно, чтобы прочитать, что много байтов из потока; это сообщение. Начало сообщения обычно является типом сообщения; в очень простом сценарии вы можете сказать, что "1" - это клиентский запрос, "2" - ответ сервера, "3" - это клиентское эхо, "4" - ответ сервера на сервер, "5" - сервер echo, "6" - это клиентский эхо-ответ и т.д. Таким образом вы получаете сообщение, анализируете его, а затем вы знаете его запрос от клиента или ответ на сообщение, отправленное с сервера, например.