Это вторая часть серии руководств, в которой будет описана поддержка сокетов WP7 с точки зрения разработчика. Эта серия учебных пособий будет посвящена разработке приложений WP7, для которых требуется длительное TCP-соединение для отправки и получения текстовых данных. В этих постах мы рассмотрим создание полноценного IRC-клиента.
Основное внимание в этих постах уделяется разработчикам WP7. Если вы хотите лучше понять внутренние детали интернет-сокетов, в Википедии есть хорошее введение по этому вопросу.
Эта вторая часть будет продолжаться с того места, где остановилась первая часть : у нас открыто TCP-соединение, и теперь мы должны начать прослушивать сообщения, которые отправляет нам сервер.
основы
Как мы уже говорили ранее, Socket и SockectAsyncEventArgs являются двумя ключевыми классами при работе с сокетами на платформе Windows Phone. Они используются при создании соединения и используются снова, когда мы хотим получать сообщения с сервера. Мы продолжим работу с шаблоном, который мы создали в первом уроке: экземпляр SockectAsyncEventArgs всегда используется только один раз, а затем удаляется.
Для получения сообщения нам нужен буфер . Буфер — это просто массив байтов, который создается и присоединяется к экземпляру SocketAsyncEventArgs. Когда сервер отправляет сообщение нашему клиенту, оно может помещаться или не помещаться в один буфер. Если он не подходит, остальная часть сообщения (или следующая часть) может быть получена путем повторного создания нового экземпляра SocketAsyncEventArgs и буфера для него.
Создание нового буфера для каждого сообщения — не единственное доступное решение. Можно использовать кольцевой буфер для оптимизации производительности. Вы можете проверить «Круговой буфер для .NET» , проект от CodePlex, чтобы увидеть пример реализации.
Код
Как уже упоминалось ранее, в дополнение к байтовому массиву нам необходим экземпляр SocketAsyncEventArgs для получения сообщения от сервера. Это передается методу ReceiveAsync нашего экземпляра сокета.
private void ReceiveMessage() { var responseListener = new SocketAsyncEventArgs(); responseListener.Completed += OnMessageReceivedFromServer; var responseBuffer = new byte[bufferSize]; responseListener.SetBuffer(responseBuffer, 0, bufferSize); connection.ReceiveAsync(responseListener); }
Новый метод ReceiveMessage можно вызывать после открытия соединения с сервером. Теперь, когда сервер отправляет нам сообщение, выполняется наш метод OnMessageReceivedFromServer. В этом методе нам нужно сделать несколько вещей:
- Преобразуйте буфер (байтовый массив) в строку. Это сообщение (или сообщения), которое сервер отправил нам.
- Разберите сообщение и отреагируйте на него согласно IRC-спецификации.
- Вызовите метод ReceiveMessage, который создаст новый буфер и новый SocketAsyncEventArgs и начнет прослушивать следующее сообщение с сервера.
Один буфер может содержать несколько сообщений с сервера. Сервер может отправлять нам несколько сообщений, что означает, что, хотя OnMessageReceivedFromServer выполняется только один раз, мы можем иметь дело с несколькими сообщениями. В протоколе IRC каждое сообщение заканчивается новой строкой, и мы можем использовать это для нашего преимущества: превратив байтовый массив в строку, мы разбиваем его по символам новой строки в строковый массив. Каждый экземпляр в массиве — это одно отдельное сообщение от сервера.
Но как мы можем быть уверены, что сообщение сервера помещается в наш буфер? Если последний символ в буфере является новой строкой, все хорошо. Но если это не так, наш буфер заполнен, и нас ждет новое сообщение. И мы должны сделать несколько сшивки: мы не можем обработать текущее сообщение, потому что оно не завершено, поэтому мы сохраняем его в переменной, ждем следующего сообщения и объединяем эти два. Использование ранее упомянутого кольцевого буфера сделало бы это несколько проще.
private string trailingMessage; private void OnMessageReceivedFromServer(object sender, SocketAsyncEventArgs e) { // Convert the received message into a string var message = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred); var bufferWasPreviouslyFull = !string.IsNullOrWhiteSpace(trailingMessage); if (bufferWasPreviouslyFull) { message = trailingMessage + message; trailingMessage = null; } var isConnectionLost = string.IsNullOrWhiteSpace(message); if (isConnectionLost) { // We lost the connection for some reason // Handle the situation return; } // Convert the received string into a string array var lines = new List<string>(message.Split("\n\r".ToCharArray(), StringSplitOptions.None)); var lastLine = lines.LastOrDefault(); var isBufferFull = !string.IsNullOrWhiteSpace(lastLine); if (isBufferFull) { trailingMessage = lastLine; lines.Remove(lastLine); } foreach (var line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; ProcessIncomingMessage(line); } // Start listening for the next message ReceiveMessage(); }
Теперь нам просто нужно реализовать метод ProcessIncomingMessage. В этом методе мы будем анализировать сообщение и реагировать на него соответственно. Он может содержать личное сообщение другого пользователя, проверку сервера, чтобы убедиться, что мы все еще живы, или что-либо еще упомянутое в спецификациях IRC.
Как видите, мы предполагаем, что входящее сообщение имеет кодировку UTF8. К сожалению, мы не можем быть уверены в этом. Протокол IRC не определяет, какую кодировку следует использовать, и это может варьироваться между клиентами. UTF8 в настоящее время является наиболее используемой кодировкой, поэтому мы ее используем.
На данный момент мы собираемся просто вставить сообщение в наш отладочный вывод, но мы добавим некоторую базовую логику синтаксического анализа в будущих эпизодах этого учебного цикла.
private void ProcessIncomingMessage(string ircMessage) { Debug.WriteLine(ircMessage); // Future hook for handling the message in somewhere else. // It's most probably wise to put the parsing logic in some other class. if (IrcMessageReceivedFromServer != null) IrcMessageReceivedFromServer(this, new IrcMessageReceivedFromServer(ircMessage)); }
Текущая функциональность
Теперь мы почти на месте. Мы уже можем подключиться к серверу и получать от него сообщения. Это можно увидеть в действии, если вы запустите наше приложение с новым кодом, который мы добавили в этом руководстве.
Но если вы позволите приложению работать достаточно долго, вы также заметите, что мы получаем два дополнительных сообщения. Один утверждает, что произошла ошибка, а другой — просто пустая строка. Первое сообщение получено, потому что мы не следовали спецификации IRC: сервер хочет, чтобы мы идентифицировали себя перед тем, как продолжить. Поскольку мы этого не сделали, сервер отключает наше соединение, и из-за этого получается пустая строка. Если мы попытаемся вызвать метод ReceiveMessage после получения пустой строки, мы получим исключение, потому что соединение больше не открыто.
Следующий шаг
Следующий шаг — начать следовать спецификации IRC и фактически отправлять сообщения на сервер. Мы пройдем через это в следующей части завтра.
Исходный код
Весь исходный код для этого урока доступен на GitHub.