Сетевое взаимодействие через сокеты является долгожданным дополнением к платформе 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 . В противном случае есть вероятность, что фактический терминатор не является завершающим символом.