我有一个Windows Phone 8应用程序通过套接字与服务器通信。服务器很简单。它接受一个字符串,返回一个字符串,并立即关闭连接。我使用Windows Forms应用程序与服务器进行了交谈,从未遇到过问题。
我正在使用下面的代码,我从这个MSDN页面改编而来,该代码显示了如何在Windows Phone 8应用程序中使用套接字:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202858(v=vs.105).aspx
我使用async / await将代码修改为非阻塞。所有方法现在都在等待,并且在每个套接字异步操作之后的WaitOne()调用被分离到新任务。我没有收到任何套接字错误或异常。但是,当ReceiveAsync()调用的匿名Completed事件处理程序触发时,传输的字节值始终为0.
奇怪的说明。如果我在ReceiveAsync()的已完成事件处理程序中的某些行上设置断点,则调用超时。如果我没有设置断点并且它只发生在某些行上,则不会发生这种情况。我不知道为什么。如果我没有设置断点,则不会发生超时
我做错了什么?这是我正在使用的代码。调用代码(未显示)只是创建一个SocketDetails类的实例,然后按顺序调用,等待ConnectAsync(),等待SendAsync(“somestring”),以及最后等待SocketDetails实例上的ReceiveAsync()。:
/// <summary>
/// Details object that holds a socket.
/// </summary>
public class SocketDetails
{
/// <summary>
/// Creates a new socket details object.
/// </summary>
/// <param name="hostName">The host name for the connection.</param>
/// <param name="portNumber">The port name for the connection.</param>
/// <param name="timeOutMS">The maximum number of milliseconds to wait for a connection before <br />
/// timing out.</param>
/// <param name="defaultBufferSize">The maximum number of bytes for the buffer that receives the <br />
/// connection result string.</param>
public SocketDetails(
string hostName,
int portNumber,
int timeOutMS,
int defaultBufferSize)
{
if (String.IsNullOrWhiteSpace(hostName))
throw new ArgumentNullException("The host name is empty.");
if (portNumber <= 0)
throw new ArgumentOutOfRangeException("The port number is less than or equal to 0.");
if (timeOutMS < 0)
throw new ArgumentOutOfRangeException("The time-out value is negative.");
this.HostName = hostName;
this.PortNumber = portNumber;
this.TimeOutMS = timeOutMS;
// Create DnsEndPoint. The hostName and port are passed in to this method.
this.HostEntry = new DnsEndPoint(this.HostName, this.PortNumber);
// Create a stream-based, TCP socket using the InterNetwork Address Family.
this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Create the manual reset event.
this.ClientDone = new ManualResetEvent(false);
}
/// <summary>
/// The string returned by the last connection attempt.
/// </summary>
public string ConnectionResult { get; private set; }
public DnsEndPoint HostEntry
{ get; private set; }
/// <summary>
/// The host name to open the socket on.
/// </summary>
public string HostName { get; private set; }
/// <summary>
/// The port number to use when opening the socket.
/// </summary>
public int PortNumber { get; private set; }
/// <summary>
/// Cached Socket object that will be used by each call for the lifetime of this class
/// </summary>
public Socket Socket { get; private set; }
/// <summary>
/// Signaling object used to notify when an asynchronous operation is completed. Exposing it <br />
/// so other threads/code can reset it if necessary, unblocking any threads waiting on this socket.
/// </summary>
public ManualResetEvent ClientDone { get; private set; }
/// <summary>
/// Define a timeout in milliseconds for each asynchronous call. If a response is not received within this <br />
// timeout period, the call is aborted.
/// </summary>
public int TimeOutMS { get; set; }
// The maximum size of the data buffer to use with the asynchronous socket methods
public int BufferSize { get; set; }
/// <summary>
/// Waits until a socket operation completes or the time-out period is reached.
/// </summary>
/// <returns>TRUE if the semaphore wait did not TIME-OUT, FALSE if a time-out did occur.</returns>
private bool BlockUntilSocketOperationCompletes(string caller)
{
// Sets the state of the event to nonsignaled, causing this task's thread to block.
// The completed handler of the socket method that called this method should unblock it.
this.ClientDone.Reset();
bool bRet = this.ClientDone.WaitOne(this.TimeOutMS);
if (bRet)
Debug.WriteLine("WaitOne() completed successfully for caller: " + caller);
else
Debug.WriteLine("WaitOne() timed-out for caller: " + caller);
return bRet;
}
/// <summary>
/// (awaitable) Connects to the socket using the details given in the constructor.
/// </summary>
/// <returns>Returns the banner or error message returned from the sockete during the <br />
/// connection attempt.</returns>
/// <remarks>This call BLOCKS until the connection succeeds or the time-out limit is reached!</remarks>
async public Task<string> ConnectAsync()
{
// Create a SocketAsyncEventArgs object to be used in the connection request
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = this.HostEntry;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
// Retrieve the result of this request
this.ConnectionResult = e.SocketError.ToString();
Debug.WriteLine("CONNECT completed, Connection result string received: " + this.ConnectionResult);
// Signal that the request is complete, unblocking the UI thread
this.ClientDone.Set();
});
// Make an asynchronous Connect request over the socket
this.Socket.ConnectAsync(socketEventArg);
// Wait for the return operation to complete or until it times out.
bool bIsTimeOut = !(await Task.Run(() => BlockUntilSocketOperationCompletes("ConnectAsync")));
return this.ConnectionResult;
}
/// <summary>
/// (awaitable) Send the given data to the server using the established connection
/// </summary>
/// <param name="data">The data to send to the server</param>
/// <returns>The result of the Send request</returns>
/// <remarks>This call BLOCKS until the data is received or the attempt times out!</remarks>
async public Task<string> SendAsync(string data)
{
string response = "Operation Timeout";
// We are re-using the _socket object initialized in the Connect method
if (this.Socket != null)
{
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
// Set properties on context object
socketEventArg.RemoteEndPoint = this.Socket.RemoteEndPoint;
socketEventArg.UserToken = null;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order
// to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
response = e.SocketError.ToString();
Debug.WriteLine("SEND completed, Response received: " + response);
// Unblock the UI thread
this.ClientDone.Set();
});
// Add the data to be sent into the buffer
byte[] payload = Encoding.UTF8.GetBytes(data);
socketEventArg.SetBuffer(payload, 0, payload.Length);
// Make an asynchronous Send request over the socket
this.Socket.SendAsync(socketEventArg);
// Wait for the return operation to complete or until it times out.
bool bIsTimeOut = !(await Task.Run(() => BlockUntilSocketOperationCompletes("SendAsync")));
}
else
{
response = "Socket is not initialized";
}
return response;
}
/// <summary>
/// (awaitable) Receive data from the server using the established socket connection
/// </summary>
/// <returns>The data received from the server</returns>
async public Task<string> ReceiveAsync()
{
string response = "Operation Timeout";
// We are receiving over an established socket connection
if (this.Socket != null)
{
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = this.Socket.RemoteEndPoint;
// Setup the buffer to receive the data
socketEventArg.SetBuffer(new Byte[this.BufferSize], 0, this.BufferSize);
// Inline event handler for the Completed event.
// Note: This even handler was implemented inline in order to make
// this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
Debug.WriteLine("RECEIVE completed.");
if (e.SocketError == SocketError.Success)
{
// Retrieve the data from the buffer
response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
response = response.Trim('\0');
Debug.WriteLine("RECEIVE completed, response received: " + response);
}
else
{
response = e.SocketError.ToString();
Debug.WriteLine("RECEIVE failed: socket error: " + response);
}
this.ClientDone.Set();
});
// Make an asynchronous Receive request over the socket
this.Socket.ReceiveAsync(socketEventArg);
bool bIsTimeOut = await Task.Run(() => BlockUntilSocketOperationCompletes("ReceiveAsync"));
}
else
{
response = "Socket is not initialized";
}
return response;
}
/// <summary>
/// Closes the Socket connection and releases all associated resources
/// </summary>
public void Close()
{
if (this.Socket != null)
{
this.Socket.Close();
}
}
} // public class SocketDetails
答案 0 :(得分:1)
首先,我强烈建议您不要使用TCP / IP。如果可能的话,请改用WebAPI + HttpClient。如果您仍在学习async
,则尤其如此。
那就是说,我的意见如下。
接收空数组是套接字的正常情况。它表明对方关闭了其发送渠道。
我不建议使用基于SocketAsyncEventArgs
的API。它是possible, but awkward使其与async
一起使用。相反,写TAP-over-APM wrappers(我更喜欢将它们写为扩展方法)。
代码示例中的*Async
包装器都遵循一种模式,它们启动异步操作,然后排队线程池操作以等待它完成。这对我来说似乎没必要; Task<T>.Factory.FromAsync
或TaskCompletionSource<T>
会提高效率,减少错综复杂。
最后,您应确保不使用read-then-write循环,这是TCP / IP应用程序的常见错误。 read-then-write循环存在一些问题;其中一个是它无法从half-open problem中恢复,我在我的博客中描述了它。