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