C#套接字ReceiveAsync

7

我习惯于使用同步套接字,并且在现在这个节点上,特别是在Socket.Receive(..)并非总是接收到所有字节的情况下,我遇到了一些头疼的问题。

这里是我曾经使用的代码:

    public byte[] Receive(int size)
    {
        var buffer = new byte[size];
        var r = 0;
        do
        {
            // ReSharper disable once InconsistentlySynchronizedField
            var c = _clientSocket.Receive(buffer, r, size - r, SocketFlags.None);
            if (c == 0)
            {
                throw new SocketExtendedException();
            }
            r += c;
        } while (r != buffer.Length);
        return buffer;
    }

我现在开始在Windows Phone中使用sockets,但是.Receive(..)不可用。我设法让Socket.ReceiveAsync(..)工作,但我担心(到目前为止没有出现问题)。这是我的新代码,我还没有实现检查是否已经接收到所有字节,也不知道是否需要使用以下代码。

    private byte[] ReadBySize(int size = 4)
    {
        var readEvent = new AutoResetEvent(false);
        var buffer = new byte[size];
        var recieveArgs = new SocketAsyncEventArgs()
        {
            UserToken = readEvent
        };
        recieveArgs.SetBuffer(buffer, 0, size);
        recieveArgs.Completed += recieveArgs_Completed;
        _connecter.ReceiveAsync(recieveArgs);
        readEvent.WaitOne();

        if (recieveArgs.BytesTransferred == 0)
        {
            if (recieveArgs.SocketError != SocketError.Success)
                throw new SocketException((int)recieveArgs.SocketError);
            throw new CommunicationException();
        }
        return buffer;
    }

    void recieveArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        var are = (AutoResetEvent)e.UserToken;
        are.Set();
    }

这是我第一次使用ReceiveAsync,请问是否有什么我做错或需要更改的地方。


我建议使用BeginReceive() - Bauss
在Windows Phone上,“BeginRecieve”和“Recieve”不可用,我已经使用了“BeginReceive”,但它也不可用,我只能使用StreamSocket或Socket(带有“RecieveAsync”功能)。 - Donald Jansen
对不起,我的错,我没有看到它是Windows手机。 - Bauss
你是否在使用TCP?如果是这种情况,你是否考虑过使用TcpClient/TcpListenerNetworkStream更容易操作。 - Luaan
不幸的是,TcpClient在Windows Phone中也不可用。 - Donald Jansen
看我的回答,如果你感兴趣的话,我已经强制服务器不向客户端发送所有字节。 - Donald Jansen
2个回答

3

好的,我使用了一个大缓冲区,并按照间隔睡眠时间分批发送数据,以模拟“未接收到所有字节”的情况。因此,上面的代码不能接收到所有字节。对于那些使用ReceiveAsync(..)的人,这里是我的代码,可以正常工作。

    private byte[] ReadBySize(int size = 4)
    {
        var readEvent = new AutoResetEvent(false);
        var buffer = new byte[size]; //Receive buffer
        var totalRecieved = 0;
        do
        {
            var recieveArgs = new SocketAsyncEventArgs()
            {
                UserToken = readEvent
            };
            recieveArgs.SetBuffer(buffer, totalRecieved, size - totalRecieved);//Receive bytes from x to total - x, x is the number of bytes already recieved
            recieveArgs.Completed += recieveArgs_Completed;
            _connecter.ReceiveAsync(recieveArgs);
            readEvent.WaitOne();//Wait for recieve

            if (recieveArgs.BytesTransferred == 0)//If now bytes are recieved then there is an error
            {
                if (recieveArgs.SocketError != SocketError.Success)
                    throw new ReadException(ReadExceptionCode.UnexpectedDisconnect,"Unexpected Disconnect");
                throw new ReadException(ReadExceptionCode.DisconnectGracefully);
            }
            totalRecieved += recieveArgs.BytesTransferred;

        } while (totalRecieved != size);//Check if all bytes has been received
        return buffer;
    }

    void recieveArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        var are = (AutoResetEvent)e.UserToken;
        are.Set();
    }

我在处理 Socket 应用程序时,通常会发送一个包含一些变量的缓冲区。

[0] -> 0,1,2 0 is keep alive, 1 means there are data, 2 means a type off error occured
[1,2,3,4] size of the actual buffer I am sending
[x(size of 1,2,3,4)] the actual 'Serialized' data buffer

1
如果您在同一线程中调用ReceiveAsync然后再调用WaitOne,从而有效地使它们变成同步的,那么为什么要使用异步API呢?您可以直接调用同步阻塞的Receive() - Jonathon Reinhart
因为这是2年前的Windows Phone,Windows Phone没有Recieve(),如果你已经阅读了问题,你就会知道。当时我需要同步。 今天代码看起来很不同。 - Donald Jansen
谢谢,我确实错过了那个部分。我永远也想不到同步 API 会不可用。 - Jonathon Reinhart
1
我的代码现在包含 TaskCompletionSource<byte[]>(),摆脱了丑陋的 AutoResetEvent,我现在使用 async/awaitbyte[] 变成了 Task<byte[]>return buffer 变成了 return tcs.Task;recieveArgs_Completed 也改变以适应 TaskCompletionSource。 - Donald Jansen
5
@DonaldJansen,你能否更新你的答案以反映你最新的评论?看到一个async/await的实现会很棒! - Dominic Jonas

1
你可以创建一个套接字扩展,例如:
public static Task<int> ReceiveAsync(this Socket socket,
    byte[] buffer, int offset, int size, SocketFlags socketFlags)
{
    if (socket == null) throw new ArgumentNullException(nameof(socket));

    var tcs = new TaskCompletionSource<int>();
    socket.BeginReceive(buffer, offset, size, socketFlags, ar =>
    {
        try { tcs.TrySetResult(socket.EndReceive(ar)); }
        catch (Exception e) { tcs.TrySetException(e); }
    }, state: null);
    return tcs.Task;
}

然后有一个读取所需大小的方法,如下:

public static async Task<byte[]> ReadFixed(Socket socket, int bufferSize)
{
    byte[] ret = new byte[bufferSize];
    for (int read = 0; read < bufferSize; read += await socket.ReceiveAsync(ret, read, ret.Length - read, SocketFlags.None)) ;
    return ret;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接