c# - 调用套接字的ReceiveAsync()调用后,接收到的数据缓冲区始终为空吗?

标签 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类的实例,然后按顺序调用,等待SocketDetails实例上的ConnectAsync(),等待SendAsync(“somestring”),最后等待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,则尤其如此。
就是说,我的评论如下。
对于套接字,接收空数组是正常情况。它指示另一端已关闭其发送 channel 。
我不建议使用基于SocketAsyncEventArgs的API。使它与async一起使用的是possible, but awkward。相反,使用TAP方法(在SocketTaskExtensions上定义的方法)来编写TAP-over-APM wrappers(我更喜欢将它们作为扩展方法来编写)。
代码示例中的*Async包装器均遵循一种模式,在该模式下,它们将启动异步操作,然后将线程池操作排队,以等待其完成。对我来说,这似乎是不必要的。 Task<T>.Factory.FromAsyncTaskCompletionSource<T>会更高效且更容易混淆。
最后,您应该确保没有使用读写循环,这是TCP/IP应用程序的常见错误。读写循环存在两个问题。其中之一是它无法从我在博客中描述的half-open problem恢复。

关于c# - 调用套接字的ReceiveAsync()调用后,接收到的数据缓冲区始终为空吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22240387/

相关文章:

c# - UWP Combobox SelectedItem 忽略其绑定(bind)值

c# - MyDataContext.dbml 下的 .cs 文件是做什么用的?

java - 套接字而不是 ServerSocket

asynchronous - SIGNAL vs Esterel vs Lustre

c# - 性能计数器平均计时器如何与其基数相关联?

c# - 接口(interface)成员的属性不起作用

linux - 在 Linux 上使用 AF_LOCAL 或 AF_UNIX 套接字进行多播?

c# - 通过 TCP 套接字丢失一个字节

Node.js:如何使异步数据全局可用

asynchronous - F#- AsyncSeq - 如何返回列表中的值