Опубликован: 15.05.2013 | Доступ: свободный | Студентов: 265 / 9 | Длительность: 24:25:00
Специальности: Системный архитектор
Лекция 9:

Синдикация

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >

Сокеты потока

В отличие от сокетов датаграмм, потоковая передача данных через сокеты Transmission Control Protocol(TCP). Отличительная черта TCP заключается в точной и надёжной доставке – он гарантирует, что принятые байты соответствуют отправленным: когда пакеты передаются по сети, TCP пытается повторять их передачу, если что-то пошло не так. Именно поэтому он является частью TCP/IP, что дает нам Всемирную паутину, электронную почту, передачу файлов и многое другое. HTTP, SMTP, и Session Initiation Protocol (SIP) так же построены на основе TCP. Во всех случаях, клиенты и сервера видят лишь надёжный поток данных, путешествующих от одного конца соединения к другому.

В отличие от сокетов датаграмм, для которых имеется лишь один класс WinRT, для обеих сторон соединения, сокеты потока различаются сильнее, для соответствия уникальным нуждам клиентской и серверной ролей. На клиентской стороне это Windows.Networking.Sockets.StreamSocket (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.streamsocket.aspx); на серверной – StreamSocketListener (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.streamsocketlistener.aspx).

Начнем с последнего, объект StreamSocketListener очень похож на DatagramSocket, который мы только что рассмотрели в предыдущем разделе, он имеет следующие методы, свойства и события:

Со стороны клиента, StreamSocket, опять же, выглядит как часть DatagramSocket. В дополнение к свойствам control (StreamSocketControl (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.streamsocketcontrol.aspx)) и information (StreamSocketInformation ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.streamsocketinformation.aspx)) и вездесущему методу close, мы обнаруживаем несколько вполне обычных и один необычный:

  • connectAsync Осуществляет подключение к HostName/имени сервиса или к EndpointPair. В каждом случае вы так же можете предоставить необязательный объект SocketProtectionLevel (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.socketprotectionlevel.aspx), который может принимать значения plainSocket, ssl, или sslAllowNullEncryption. Это, другими словами, четыре варианта данного метода.
  • inputStream Обект IInputStream который принимает данные через соединению.
  • outputStream Объект IOutputStream в который осуществляется запись данных.
  • upgradeToSslAsync (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.streamsocket.upgradetosslasync.aspx) Обновляет соединение типа plainSocket (созданное с помощью connectAsync) для использования SSL, что задаётся либо SocketProtectionLevel.ssl либо sslAllowNullEncryption. Эти методы так же требуют HostName, который подтверждает соединение.

Для того, чтобы узнать больше об использовании SSL, смотрите материал "Обеспечение безопасности подключений через сокеты с помощью протокола TLS/SSL" (http://msdn.microsoft.com/library/windows/apps/hh780595.aspx).

В любом случае, вы можете видеть, что для односторонней связи с использованием TCP приложение создает либо StreamSocket либо StreamSocketListener, в зависимости от его роли. Для двусторонней связи приложению нужно и то и другое.

Пример "StreamSocket" (http://code.msdn.microsoft.com/windowsapps/StreamSocket-Sample-8c573931), как и пример "DatagramSocket", содержит четыре сценария и подразумевает их последовательный запуск на локальном хосте: сначала для создания прослушивателя (для приема сообщения от клиента, Сценарий 1), затем – для создания StreamSocket (Сценарий 2) и отправки сообщения (Сценарий 3), и, в итоге, для закрытия сокета (Сценарий 4). При работе с потоковыми данными, приложение реализует пользовательский протокол для отображения данных, как мы увидим.

Начнем со Сценария 2 (js/startListener.js), здесь показано создание прослушивателя и обработчика событий. Обработка входящего потока данных сложнее, чем работа с датаграммой, так как нам нужно убедиться в том, что все нужные данные прибыли. Этот код показывает хороший шаблон организации ожидания завершения одной осинхронной операции прежде чем функция рекурсивно вызовет себя. Кроме того, обратите внимание на то, как здесь, для удобства, создан DataReader на основе входного потока:

socketsSample.listener = new Windows.Networking.Sockets.StreamSocketListener(serviceName);
// Дополните removeEventListener при необходимости socketsSample.listener.addEventListener
("connectionreceived", onServerAccept);

socketsSample.listener.bindServiceNameAsync(serviceName).done(function () {
// ...
}, onError);
}

// Эта функция должна быть реальной; она циклически вызывает сама себя с вызовом
// acceptAsync в самом конце.
function onServerAccept(eventArgument) {
socketsSample.serverSocket = eventArgument.socket;
socketsSample.serverReader =
new Windows.Storage.Streams.DataReader(socketsSample.serverSocket.inputStream);
startServerRead();
}

// Протокол, используемый здесь, прост: четырехбайтовое с 'сетевым порядком данных' (big-endian) целое число
// которое сообщает о длине строки, и затем строка указанной длины. Мы ожидаем 4 байта,
// читаем значение количества, и затем ожидаем указанное количество байтов, после чего отображаем их.
function startServerRead() {
socketsSample.serverReader.loadAsync(4).done(function (sizeBytesRead) {
// Убеждаемся, что прочитаны 4 байта.
if (sizeBytesRead !== 4) { /* [Show message] */ }

// Читаем 4-х байтовое число и затем читаем заданное количество байтов.
var count = socketsSample.serverReader.readInt32();
return socketsSample.serverReader.loadAsync(count).then(function (stringBytesRead) {
// Убеждаемся, что прочитана вся строка.
if (stringBytesRead !== count) { /* [Show message] */ }

// Читаем строку.
var string = socketsSample.serverReader.readString(count);
socketsSample.displayOutput("Server read: " + string);

// Начинаем чтение заново для большего количества байт. Мы можем просто вызвать startServerRead() но в
// случае синхронного завершения последовательных операций чтения мы начинаем заполнять стек
// что может привести к краху приложения. Мы использует WinJS.Promise.timeout() для вызова этой функции
// после разворачивания стека для текущей операции.
WinJS.Promise.timeout().done(function () { return startServerRead(); });
}); // Конец функции "чтения до конца строки" function.
}, onError);
}

Этот код приспособлен для ожидания входящих данных, которые пока не готовы, но вы можете столкнуться с ситуацией, в которой вы захотите узнать, если еще доступные данные, которые вы не прочли. Это значение можно получить с помощью свойства DataReader.unconsumedBufferLength (http://msdn.microsoft.com/library/windows/apps/windows.storage.streams.datareader.unconsumedbufferlength.aspx).

В Сценарии 2, в стороне взаимоотношений, отправляющих данные, всё устроено просто: создаётся StreamSocket и вызывается connectAsync (js/connectToListener.js; обратите внимание на то, что onError снова использует StreamSocketError.getStatus):

socketsSample.clientSocket = new Windows.Networking.Sockets.StreamSocket();
socketsSample.clientSocket.connectAsync(hostName, serviceName).done(function () {
// ...
}, onError);
Отправка данных в Сценарии 3 использует возможности DataWriter, 
]построенного на основе выходного потока сокета (js/sendData.js):

var writer = new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);
var string = "Hello World";
var len = writer.measureString(string); // Получает длину UTF-8-строки
writer.writeInt32(len);
writer.writeString(string);

writer.storeAsync().done(function () {
writer.detachStream();
}, onError);

Закрытие сокета в Сценарии 4, это, снова, обычный вызов StreamSocket.close.

Как и в случае с примером "DatagramSocket", установка точек останова внутри openClient (js/connectTo-Listener.js), onServerAccept (js/startListener.js), и sendHello (js/sendData.js) позволит вам увидеть, что происхиодит на каждом из шагов вышеописанного процесса.

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >