C#异步UDP服务器多客户端 | 当客户端断开连接时发生Socket异常

3
我一直在用C#编写套接字服务器程序(灵感来源于这篇文章),我的问题是当客户端断开连接时,调用EndReceiveFrom()返回0并出现异常“An existing connection was forcibly closed by the remote host”,ref clientEP变成了通常关闭的客户端。我不明白为什么会在没有数据可读的情况下调用DoReceiveFrom()函数。我可能忘记了什么,请问出了什么问题?
int dataLen = this.serverSocket.EndReceiveFrom(iar, ref clientEP);

完整的源代码:
class UDPServer
{
    private Socket serverSocket = null;
    private List<EndPoint> clientList = new List<EndPoint>();
    private List<Tuple<EndPoint, byte[]>> dataList = new List<Tuple<EndPoint, byte[]>>();
    private byte[] byteData = new byte[1024];
    private int port = 4242;

    public List<Tuple<EndPoint, byte[]>> DataList
    {
        private set { this.dataList = value; }
        get { return (this.dataList); }
    }

    public UDPServer(int port)
    {
        this.port = port;
    }

    public void Start()
    {
        this.serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        this.serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        this.serverSocket.Bind(new IPEndPoint(IPAddress.Any, this.port));
        EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
        this.serverSocket.BeginReceiveFrom(this.byteData, 0, this.byteData.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, newClientEP);
    }

    private void DoReceiveFrom(IAsyncResult iar)
    {
        try
        {
            EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
            int dataLen = this.serverSocket.EndReceiveFrom(iar, ref clientEP);
            byte[] data = new byte[dataLen];
            Array.Copy(this.byteData, data, dataLen);

            if (!this.clientList.Any(client => client.Equals(clientEP)))
                this.clientList.Add(clientEP);

            EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
            this.serverSocket.BeginReceiveFrom(this.byteData, 0, this.byteData.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, newClientEP);

            DataList.Add(Tuple.Create(clientEP, data));
        }
        catch (ObjectDisposedException)
        {
        }
    }

    public void SendTo(byte[] data, EndPoint clientEP)
    {
        try
        {
            this.serverSocket.SendTo(data, clientEP);
        }
        catch (System.Net.Sockets.SocketException)
        {
            this.clientList.Remove(clientEP);
        }
    }

    public void SendToAll(byte[] data)
    {
        foreach (var client in this.clientList)
        {
            this.SendTo(data, client);
        }
    }

    public void Stop()
    {
        this.serverSocket.Close();
        this.serverSocket = null;

        this.dataList.Clear();
        this.clientList.Clear();
    }
}

异常:

An existing connection was forcibly closed by the remote host

更新: 我尝试在另一台电脑上运行我的客户端(netcat),异常不再出现,即使SendTo()也是删除我的clientList中客户端的问题。 我仍然不明白正在发生什么。


你遇到了同样的问题,你最终解决了吗? - Vincent
我认为,在关闭时调用DoReceiveFrom是回到用户代码的一种方式,错误代码(断开连接)可能已经存储,当调用EndReceiveFrom时,它将检查错误代码,如果错误代码<> 0,则会抛出异常。(但是,就像我说的那样,只是猜测)我宁愿不要出现异常,断开连接不应该是异常。 - Jeroen van Langen
1个回答

0

一切都应该是正常的。

这就是所有异步方法的工作方式:您调用BeginDo()并将AsyncCallback委托的实现传递给它(在您的示例中,即DoReceiveFrom)。 在此之后,您的实现立即开始执行-BeginDo()不是一个阻塞调用。

在您的实现内部,必须调用EndDo(),它将阻止直到发生以下两种情况之一:您调用BeginDo()的对象实际上执行了某些操作,或者在执行此操作时发生异常,就像您的客户端断开连接时发生的那样。

有关异步方法的源文件

要使整个事情正常工作,您需要做的是

  1. 确保正确处理客户端断开连接的异常
  2. 确保无论 EndReceiveFrom 如何结束,都要调用 BeginReceiveFrom。最好在调用 EndReceiveFrom 后立即调用 BeginReceiveFrom。这是必需的,因为在这些调用之间,服务器实际上并没有监听套接字。

我会在 EndReceiveFrom 周围再加一个 try-catch。

更新:

private void DoReceiveFrom(IAsyncResult iar)
{
    try
    {
        EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
        int dataLen = 0;
        byte[] data = null;
        try
        {
            dataLen = this.serverSocket.EndReceiveFrom(iar, ref clientEP);
            data = new byte[dataLen];
            Array.Copy(this.byteData, data, dataLen);
        }
        catch(Exception e)
        {
        }
        finally
        {
            EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
            this.serverSocket.BeginReceiveFrom(this.byteData, 0, this.byteData.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, newClientEP);
        }

        if (!this.clientList.Any(client => client.Equals(clientEP)))
            this.clientList.Add(clientEP);

        DataList.Add(Tuple.Create(clientEP, data));
    }
    catch (ObjectDisposedException)
    {
    }
}

谢谢您的回答,但问题仍然存在。 - user3626081
@user3626081,好的,但是你至少明白为什么即使没有可读内容,**DoReceiveFrom()**函数也会被调用吗?另外,我更新了我的答案,并附上了代码示例-看看是否有帮助。 - user270576

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