【问题标题】:Confused about Sockets with UDP Protocol in C#对 C# 中带有 UDP 协议的套接字感到困惑
【发布时间】:2011-07-21 20:56:20
【问题描述】:

我刚刚开始通过各种 Google 搜索学习 Sockets,但在弄清楚如何在 C# 中正确使用 Sockets 时遇到了一些问题,我需要一些帮助。

我有一个测试应用程序(Windows 窗体)和一个不同的类(实际上在它自己的 .dll 中,但这无关紧要)我有我的套接字代码的所有服务器/客户端代码。

问题 1)

在我的测试应用程序中,在服务器部分,用户可以单击“开始监听”按钮,我的套接字应用程序的服务器部分应该开始监听指定地址和端口上的连接,到目前为止一切顺利。

但是,应用程序将被阻止,并且在有人连接到服务器之前我无法执行任何操作。如果没有人连接怎么办?我应该如何处理?我可以指定接收超时,但那又如何呢?它抛出一个异常,我该怎么办?我想要的是在主应用程序上进行某种活动,以便用户知道应用程序没有冻结并且正在等待连接。但是如果没有连接,它应该超时并关闭一切。

也许我应该使用异步调用来发送/接收方法,但它们看起来令人困惑,我无法让它工作,只能同步工作(我将在下面发布我当前的代码)。

问题 2)

当某些发送/接收呼叫超时时,我是否需要关闭任何东西。正如您将在我当前的代码中看到的那样,我在套接字上有一堆关闭,但这感觉不太对劲。但是当操作超时并且我没有关闭套接字时,它也感觉不对。

结束我的两个问题....我想要一个不会阻塞的应用程序,这样用户就知道服务器正在等待连接(例如,带有一点选框动画)。如果一段时间后从未建立连接,我想关闭所有应该关闭的东西。当建立连接或者一段时间后没有发生连接时,我想将结果通知主应用程序。

这是我的一些代码,其余的类似。 Packet 类是一个自定义类,代表我的自定义数据单元,目前它只是一堆基于enums 的属性,以及将它们转换为字节并返回属性的方法。

开始监听连接的函数是这样的:

public void StartListening(string address, int port) {
    try {
        byte[] bufferBytes = new byte[32];

        if(address.Equals("0.0.0.0")) {
            udpSocket.Bind(new IPEndPoint(IPAddress.Any, port));
        } else {
            udpSocket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
        }

        remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

        int numBytesReceived = udpSocket.ReceiveFrom(bufferBytes, ref remoteEndPoint);

        if(numBytesReceived == 0) {
            udpSocket.Close();
            return;
        }

        Packet syncPacket = new Packet(bufferBytes);

        if(syncPacket.PacketType != PacketType.Control) {
            udpSocket.Close();
            return;
        }
    } catch {
        if(udpSocket != null) {
            udpSocket.Close();
        }
    }
}

我确定我有一堆不必要的代码,但我是新手,我不确定该怎么做,非常感谢任何帮助修复我的代码以及如何解决上述问题。

编辑:

我可能应该说我的要求是使用 UDP 并在应用层自己实现这些东西。您可以将其视为家庭作业,但我没有这样标记,因为代码无关紧要,不会成为我成绩的一部分,而且我的问题(我的问题)在于“如何编码”,因为我的 Sockets 经验很少而且不是教过。

但是我必须说我现在解决了我的问题,我认为...我在演示应用程序上使用线程,这给我带来了一些问题,现在我在协议连接中使用它,更有意义,我可以轻松更改我的自定义协议类属性并从演示应用程序中读取这些属性。

我已经指定了一个超时时间,如果超时就抛出一个 SocketException。每当捕获到这样的异常时,套接字连接就会关闭。我只是在谈论连接握手,仅此而已。如果没有捕获到异常,则代码可能运行顺利并建立了连接。

请相应地调整您的答案。目前,我将其中任何一个标记为已接受的答案是没有意义的,希望您能理解。

【问题讨论】:

标签: c# sockets networking udp


【解决方案1】:

根据您的描述,我认为您应该使用 TCP 套接字而不是 UDP。区别是

TCP - 您在特定 IP:Port 上等待连接,一些用户可以连接到它,并且在套接字关闭之前可以通过发送和接收信息进行通信。这就像打电话给某人一样。

UDP - 您在某个 IP:Port 处等待消息。想要通信的用户只需通过 UDP 发送消息。您将通过 UDP 接收消息。不保证交货顺序。这更像是向某人发送一封蜗牛邮件。没有建立专用的通信通道。

现在来解决你的问题

服务器

  1. 使用 TCP 系列创建套接字。
  2. 要么创建一个线程并接受该线程中的连接,要么使用 Socket 的 BeginAccept api。
  3. 在主线程中,您仍然可以显示代码或任何您想做的事情。

客户

  1. 连接到服务器。
  2. 通过发送和接收数据进行通信。

【讨论】:

    【解决方案2】:

    你的东西有点不对劲。

    首先,UDP 是无连接的。您不连接或断开连接。您所做的只是发送和接收(每次都必须指定目的地)。您还应该知道,UDP 唯一承诺的是每次读取都会收到一条完整的消息。 UDP 不保证您的消息以正确的顺序到达或完全到达。

    另一方面,TCP 是基于连接的。您连接、发送/接收并最终断开连接。 TCP 是基于流的(而 UDP 是基于消息的),这意味着您可以在第一次读取时获得一半消息,而在第二次读取时获得另一半消息。 TCP 向您保证一切都会以正确的顺序到达(或者会死去尝试;)。因此,使用 TCP 意味着您应该有某种逻辑来了解完整消息何时到达,以及用于构建完整消息的缓冲区。

    下一个大问题是关于阻塞的。由于您是新手,我建议您使用线程来处理套接字。将侦听器套接字放在一个线程中,将每个连接套接字放在一个单独的线程中(5 个连接的客户端 = 5 个线程)。

    我还建议您使用 TCP,因为构建完整消息比排序消息和构建事务系统更容易(如果您想确保所有消息都到达/来自客户端,则需要此系统)。

    更新

    你还是把 UDP 弄错了。除了清理系统资源之外,关闭不会做任何事情。你应该这样做:

    public void MySimpleServer(string address, int port) 
    {
        try 
        {
            byte[] bufferBytes = new byte[32];
    
            if(address.Equals("0.0.0.0")) {
                udpSocket.Bind(new IPEndPoint(IPAddress.Any, port));
            } else {
                udpSocket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
            }
    
            remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
            while (serverCanRun)
            {
                int numBytesReceived = udpSocket.ReceiveFrom(bufferBytes, ref remoteEndPoint);
    
                // just means that one of the clients closed the connection using Shutdown.
                // doesnt mean that we cant continue to receive.
                if(numBytesReceived == 0)
                    continue; 
    
                // same here, loop to receive from another client.
                Packet syncPacket = new Packet(bufferBytes);
                if (syncPacket.PacketType != PacketType.Control)
                    continue; 
    
                HandlePacket(packet, endPoint);
            }
        } catch {
            if(udpSocket != null) {
                udpSocket.Close();
            }
        }
    }
    

    看到了吗?由于没有连接,因此关闭 UDP 套接字以开始从另一个套接字开始监听只是浪费时间。同一个套接字可以从所有知道正确端口和地址的 udp 客户端接收。这就是remoteEndPoint 的用途。它告诉哪个客户端发送消息。

    更新 2

    小更新,总结一下我所有的cmets。

    UDP 是无连接的。您永远无法检测到连接是否已建立或断开。 UDP 套接字上的 Close 方法只会释放系统资源。对client.Close() 的调用不会通知服务器套接字(就像使用 TCP 一样)。

    检查连接是否打开的最佳方法是创建一个 ping/pong 样式的数据包。即客户端发送一个 PING 消息,服务器用一个 PONG 响应。请记住,如果消息没有到达,UDP 将不会尝试重新发送您的消息。因此,在假设服务器已关闭(如果您没有收到 PONG)之前,您需要重新发送 PING 几次。

    对于关闭的客户端,您需要向服务器发送自己的消息,告诉它客户端将停止与服务器通信。为了可靠性,这里也是一样,请继续重新发送 BYE 消息,直到收到回复。

    恕我直言,如果您想要可靠性,则必须为 UDP 实现事务系统。 SIP (google rfc3261) 是使用 UDP 交易的协议示例。

    【讨论】:

    • 是的,我想我明白了。如果我需要在服务器/客户端之间进行通信以终止连接,现在该怎么办?假设我在两者之间交换了几个数据包以确认应该终止连接,如何真正关闭它?只需在客户端调用socket.Shutdown() 并在服务器中设置serverCanRun = False 还是需要做其他事情?除了调用Close 来释放系统资源。
    • 没有连接(严重)。服务器永远无法检测到客户端是否断开连接,因为 UDP 中客户端/服务器之间没有连接。您可以检测到它的唯一方法是发送自己的 BYE 消息(根据您自己的协议格式)
    • 你不能。没有联系。关闭服务器套接字仅意味着释放系统资源并且您无法从任何客户端接收任何内容。关闭客户端套接字仅意味着清理系统资源。如果您想自动检测连接是活动的还是关闭的,您必须切换到 TCP。
    • 很好的答案,唯一的问题是,如果您打算将任意(可能)大量客户端“连接”到服务器,则永远不应该在 .net 中使用每个客户端线程。它从来没有很好地扩展。拥有一个(或两个)线程来为所有套接字排队任务,并且每个使用 .net 异步模式的套接字可以更有效地扩展。更不用说异步函数将在可用时使用 IOCP!
    • 我知道套接字,谢谢。我已经和他们一起工作了很多年。我将“已连接”放在引号中,因为我知道 UDP 是无连接的。我也从未说过任何关于每个连接的线程,我说的是每个客户端的线程。无论多么多余,仍然可以编写。但是我指的是您的评论:“将侦听器套接字放在一个线程中,并将每个连接套接字放在一个单独的线程中(5 个连接的客户端 = 5 个线程)。”以及当有很多客户时它的表现如何。仅此而已。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 2021-12-15
    相关资源
    最近更新 更多