[英]Received data buffer always empty after calling socket's ReceiveAsync() call?
我有一個 Windows Phone 8 應用程序,它通過套接字與服務器通信。 服務器很簡單。 它接受一個字符串,返回一個字符串,然后立即關閉連接。 我已經使用 Windows 窗體應用程序與服務器進行了交談,但從未遇到過問題。
我正在使用下面的代碼,我改編自此 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 類的一個實例,然后依次調用await ConnectAsync() 、 await 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
首先,我強烈建議您不要使用 TCP/IP。 如果可能,請改用 WebAPI + HttpClient。 如果您仍在學習async
則尤其如此。
也就是說,我的評論如下。
接收空數組是套接字的正常情況。 表示對方關閉了自己的發送通道。
我不建議使用基於SocketAsyncEventArgs
的 API。 這是可能的,但讓它與async
工作很尷尬。 相反,編寫TAP-over-APM 包裝器(我更喜歡將它們編寫為擴展方法)使用 TAP 方法(在SocketTaskExtensions
定義的方法)。
代碼示例中的*Async
包裝器都遵循一種模式,它們啟動異步操作,然后將線程池操作排隊等待它完成。 這對我來說似乎沒有必要; Task<T>.Factory.FromAsync
或TaskCompletionSource<T>
會更高效且不那么復雜。
最后,您應該確保沒有使用先讀后寫循環,這是 TCP/IP 應用程序的常見錯誤。 先讀后寫循環有幾個問題; 其中之一是它無法從我在博客中描述的半開放問題中恢復。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.