调用socket的ReceiveAsync()调用后,接收的数据缓冲区总是为空?

时间:2014-03-07 02:37:38

标签: c# sockets asynchronous windows-phone-8

我有一个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

1 个答案:

答案 0 :(得分:1)

首先,我强烈建议您不要使用TCP / IP。如果可能的话,请改用WebAPI + HttpClient。如果您仍在学习async,则尤其如此。

那就是说,我的意见如下。

接收空数组是套接字的正常情况。它表明对方关闭了其发送渠道。

我不建议使用基于SocketAsyncEventArgs的API。它是possible, but awkward使其与async一起使用。相反,写TAP-over-APM wrappers(我更喜欢将它们写为扩展方法)。

代码示例中的*Async包装器都遵循一种模式,它们启动异步操作,然后排队线程池操作以等待它完成。这对我来说似乎没必要; Task<T>.Factory.FromAsyncTaskCompletionSource<T>会提高效率,减少错综复杂。

最后,您应确保不使用read-then-write循环,这是TCP / IP应用程序的常见错误。 read-then-write循环存在一些问题;其中一个是它无法从half-open problem中恢复,我在我的博客中描述了它。