簡體   English   中英

調用套接字的 ReceiveAsync() 調用后接收到的數據緩沖區始終為空?

[英]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.FromAsyncTaskCompletionSource<T>會更高效且不那么復雜。

最后,您應該確保沒有使用先讀后寫循環,這是 TCP/IP 應用程序的常見錯誤。 先讀后寫循環有幾個問題; 其中之一是它無法從我在博客中描述的半開放問題中恢復。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM