Статьи

TCP-сокеты на Windows Phone с поддержкой больших объемов данных

Сетевое взаимодействие через сокеты является долгожданным дополнением к платформе Windows Phone Лично мне нравится использовать их из-за огромного прироста производительности по сравнению со службами WCF. Однако одна тема, по-видимому, редко освещается, когда речь идет о связи по протоколу TCP между сервером (предположительно работающим на настольном компьютере) и клиентом (Windows Phone) — передачей больших объемов данных.

Многие существующие образцы предполагают, что связь, хотя и двунаправленная, осуществляется с небольшими блоками данных. Например, вот довольно хороший пример, который я использовал для построения асинхронного сервера. Обратный вызов чтения выглядит так:

public static void readCallback(IAsyncResult ar) {
    StateObject state = (StateObject) ar.AsyncState;
    Socket handler = state.WorkSocket;

    // Read data from the client socket.
    int read = handler.EndReceive(ar);

    // Data was read from the client socket.
    if (read > 0) {
        state.sb.Append(
             Encoding.ASCII.GetString(state.buffer,0,read));
        handler.BeginReceive(state.buffer,0,
             StateObject.BufferSize, 0,
            new AsyncCallback(readCallback), state);
    } else {
        if (state.sb.Length > 1) {
            // All the data has been read from the client;
            // display it on the console.
            string content = state.sb.ToString();
            Console.WriteLine(content);
        }
        handler.Close();
    }
}

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

public void ReadCallback(IAsyncResult ar)
{
    StateObject state = (StateObject)ar.AsyncState;
    Socket handler = state.WorkSocket;

    int bytesRead = handler.EndReceive(ar);

    if (bytesRead > 0)
    {
        string contentString =

Encoding.UTF8.GetString(state.buffer, 0, bytesRead);

        if (state.buffer[bytesRead - 1] != terminator[0])
        {
            state.ContentString.Append(contentString);
            handler.BeginReceive(state.buffer, 0, StateObject.BUFFER_SIZE,

0, new AsyncCallback(ReadCallback), state);
        }
        else
        {
            contentString = contentString.Replace(terminator, "");
            state.ContentString.Append(contentString);
        }
    }

}

Несмотря на кажущуюся простоту, процесс получения данных на Windows Phone обрабатывается по-другому. Прямой реализации Socket.BeginReceive нет . Вместо этого разработчики работают с Socket.ReceiveAsync . Многие примеры дают вам этот кусок кода:

public string Receive()
{
    string response = "Operation Timeout";

    if (_socket != null)
    {
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
        socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint;
        socketEventArg.SetBuffer(new Byte[MAX_BUFFER_SIZE],

0, MAX_BUFFER_SIZE);

        socketEventArg.Completed +=
            new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = Encoding.UTF8.GetString(
                    e.Buffer, e.Offset, e.BytesTransferred);
                response = response.Trim('\0');
            }
            else
            {
                response = e.SocketError.ToString();
            }

             clientDone.Set();
        });

        clientDone.Reset();
        socket.ReceiveAsync(socketEventArg);
        clientDone.WaitOne(TIMEOUT_MILLISECONDS);
    }
    else
    {
        response = "Socket is not initialized";
    }

    return response;
}

Обратите внимание на проблему с этим фрагментом. Как только строка получена, это будет она. Длина входящих данных будет ограничена размером буфера и максимальным размером чанка. Размер буфера не всегда может быть фиксированным, и можно с уверенностью предположить, что разработчик не хочет выделять слишком много места для этого за одну итерацию. Размер патрона по умолчанию не всегда может быть адаптирован.

Решение приходит в непрерывном вызове  Socket.ReceiveAsync :

private void ProcessReceive(SocketAsyncEventArgs e)
{
    if (e.SocketError == SocketError.Success)
    {
        Socket sock = e.UserToken as Socket;
        string dataFromServer =

Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);

        if (dataFromServer.EndsWith(terminator))
        {
            dataFromServer = dataFromServer.Replace(terminator, "");
            builder.Append(dataFromServer);

            sock.Shutdown(SocketShutdown.Send);
            sock.Close();
            clientDone.Set();


        }
        else
        {
            builder.Append(dataFromServer);
            sock.ReceiveAsync(e);
        }

    }
    else
    {
        clientDone.Set();
        throw new SocketException((int)e.SocketError);
    }
}

Вот где персонаж-терминатор играет главную роль. Посмотрите на if (dataFromServer.EndsWith (terminator)) — это флаг, который показывает, поступает ли больше данных или нет. Принимая структуру, как я показал выше, можно продолжать получать данные до тех пор, пока соединение не будет закрыто (происходит постоянно) или пока не будет обнаружен символ-терминатор.

ПРИМЕЧАНИЕ. Если вам интересно, какой символ-терминатор использовать, попробуйте использовать нестандартные символы. Например, \ u00BE . В противном случае есть вероятность, что фактический терминатор не является завершающим символом.