C # клиент-серверный протокол / модель вопроса - PullRequest
2 голосов
/ 20 мая 2009

Я пытаюсь концептуально проработать модель для приложения сокет клиент-сервер, которое я пишу на c # (как клиент, так и сервер). Моему серверу нужно будет обрабатывать много клиентов одновременно, и желательно несколько запросов от клиента одновременно. Я разработал контейнер для моего сообщения, в котором я буду отправлять заголовок фиксированной длины в начале каждого сообщения, который будет содержать (среди прочего) длину сообщения. У меня есть некоторый опыт программирования сокетов в c #, поэтому я спокойно использую асинхронные сокеты.

Главное, с чем я сталкиваюсь концептуально, это то, что мне нужны клиент и сервер, чтобы иметь возможность получать сообщения в любое время. Клиент установит соединение и останется «вошедшим в систему» ​​(как клиент IM), и ему нужно будет как получать данные в произвольное время, так и делать запросы в произвольное время. Я также хотел, как часть моего протокола, получать ответ на каждый сделанный запрос (будь то с сервера на клиент или с клиента на сервер).

Я хотел бы иметь возможность использовать один сокет, если это возможно. Я полагаю, что мог бы сделать эту работу, используя два сокета, один для выполнения запросов к серверу> клиент, а другой для запросов к клиенту> серверу, но я не хочу дополнительных сложностей при работе с двумя портами / соединениями. Однако, используя один сокет, я не уверен, как управлять отправкой запросов и получением ответов, когда они могут чередоваться.

Я не могу найти ни одного примера аналогичного сервера или клиента в моем поиске. Спасибо всем, кто предлагает любые иды.

Ответы [ 2 ]

3 голосов
/ 20 мая 2009

Розетки двунаправленные. Вам нужен только один сокет для отправки данных между двумя конечными точками. Но на клиенте вам, вероятно, понадобится один поток, постоянно читающий из сокета и уведомляющий некоторый компонент или помещающий в очередь данные, которые он получает, чтобы он обрабатывался в другом месте, в то время как другой поток отправляет данные через тот же сокет. Таким образом, вы получаете асинхронную связь.

Но на стороне сервера вам нужно открыть ServerSocket, который будет связываться с портом TCP и принимать соединения на этом порту; каждое соединение, которое вы принимаете, является новым сокетом. Таким образом, у вас есть сокет для каждого клиента, и вам, вероятно, понадобится один поток для каждого клиента.

Вам необходимо установить протокол для сообщений, которые вы будете передавать через сокет. Вы можете создать свой собственный протокол или посмотреть его в зависимости от того, что вы хотите сделать. Обычно вы сначала отправляете два или четыре байта с длиной для остальной части сообщения, поэтому другая сторона знает, что нужно прочитать столько байтов из потока; это сообщение Начало сообщения обычно является типом сообщения; в очень простом сценарии вы можете сказать, что «1» - это запрос клиента, «2» - ответ сервера, «3» - эхо клиента, «4» - ответ эха сервера, «5» - сервер echo, «6» - это эхо-ответ клиента и т. д. Таким образом, вы получаете сообщение, анализируете его, и затем вы знаете, что это запрос от клиента или ответ на сообщение, отправленное с сервера, например.

2 голосов
/ 20 мая 2009

Если вы используете TCP, то вам потребуется один сокет для каждого клиента на стороне сервера, а также сокет для прослушивания соединений. Для UDP вы можете сделать все это с одним сокетом. Независимо от этого, сделайте следующее для вашего одного сокета UDP или для каждого сокета клиента TCP:

Пусть BeginReceive происходит постоянно и BeginWrite, когда происходит необходимое событие (т. Е. Пользователь нажимает кнопку или приходит сообщение, с которым вам нужно что-то предпринять).

РЕДАКТИРОВАТЬ: В ответ на ваш комментарий, я бы обработал его, включив в заголовок идентификатор запроса. Всякий раз, когда отправляете запрос (с любого конца), укажите уникальное значение для этого сообщения. Используйте 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: Хм, хорошо ... вы действительно хотите заблокировать это? Что-то, что вы могли бы сделать, это иметь отдельный поток, обрабатывающий вызовы Send / Receive. Этот поток будет связываться с основным потоком через потокобезопасную очередь «отправить сообщение» и аналогичную «полученную сообщение» очередь псевдо-сообщений. Причина, по которой я называю последнюю очередь псевдо-очередью, заключается в том, что вы хотели бы иметь возможность принимать сообщения из очереди не по порядку. Основной поток помещает сообщение в очередь отправки, а затем блокирует очередь приема. Каждый раз, когда поток сокетов обновляет очередь приема, основной поток просыпается и проверяет, есть ли еще нужное сообщение. Если это так, он возьмет его (из строя) и завершит требуемую операцию. Если сообщение еще не пришло, оно снова заблокируется в очереди. Когда операция, наконец, будет завершена, она просто возобновит нормальную работу и получит сообщения по порядку из очереди приема, обработает их или сделает все остальное.

Имеет ли это какой-то смысл? Извините, если это немного сбивает с толку ...

...