Это вторая часть серии руководств, в которой будет описана поддержка сокетов 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.