(翻译 gafferongames)Virtual Connection over UDP

https://gafferongames.com/post/virtual_connection_over_udp/

No Direct Connections

Instead your data is sent over Internet Protocol (IP) via packets that hop from computer to computer.

A packet may pass through several computers before it reaches its destination. You cannot know the exact set of computers in advance, as it changes dynamically depending on how the network decides to route packets. You could even send two packets A and B to the same address, and they may take different routes.

On unix-like systems can inspect the route that packets take by calling “traceroute” and passing in a destination hostname or IP address.

On windows, replace “traceroute” with “tracert” to get it to work.

Try it with a few websites like this:

    traceroute slashdot.org
    traceroute amazon.com
    traceroute google.com
    traceroute bbc.co.uk
    traceroute news.com.au

Take a look and you should be able to convince yourself pretty quickly that there is no direct connection.

数据是通过 IP(Internet Protocol)数据包(packets) 的形式在计算机之间跳跃式传输的。

一个数据包在到达目的地之前,可能会经过多个计算机的转发。你无法提前知道它会经过哪些具体的计算机,因为这取决于网络如何动态路由数据包。同样,你可能会向同一地址发送两个数据包 A 和 B,但它们可能会走不同的路径。

类 Unix 系统(如 macOS 和 Linux)上,你可以使用 traceroute 来查看数据包的实际路径:

traceroute slashdot.org
traceroute amazon.com
traceroute google.com
traceroute bbc.co.uk
traceroute news.com.au

Windows 系统上,命令是 tracert 而不是 traceroute

尝试在终端中运行这些命令,你很快就会发现:互联网并没有直接连接

How Packets Get Delivered

In the first article, I presented a simple analogy for packet delivery, describing it as somewhat like a note being passed from person to person across a crowded room.

While this analogy gets the basic idea across, it is much too simple. The Internet is not a flat network of computers, it is a network of networks. And of course, we don’t just need to pass letters around a small room, we need to be able to send them anywhere in the world.

It should be pretty clear then that the best analogy is the postal service!

When you want to send a letter to somebody you put your letter in the mailbox and you trust that it will be delivered correctly. It’s not really relevant to you how it gets there, as long as it does. Somebody has to physically deliver your letter to its destination of course, so how is this done?

Well first off, the postman sure as hell doesn’t take your letter and deliver it personally! It seems that the postal service is not a series of tubes either. Instead, the postman takes your letter to the local post office for processing.

If the letter is addressed locally then the post office just sends it back out, and another postman delivers it directly. But, if the address is is non-local then it gets interesting! The local post office is not able to deliver the letter directly, so it passes it “up” to the next level of hierarchy, perhaps to a regional post office which services cities nearby, or maybe to a mail center at an airport, if the address is far away. Ideally, the actual transport of the letter would be done using a big truck.

Lets be complicated and assume the letter is sent from Los Angeles to Sydney, Australia. The local post office receives the letter and given that it is addressed internationally, sends it directly to a mail center at LAX. The letter is processed again according to address, and gets routed on the next flight to Sydney.

The plane lands at Sydney airport where an entirely different postal system takes over. Now the whole process starts operating in reverse. The letter travels “down” the hierarchy, from the general, to the specific. From the mail hub at Sydney Airport it gets sent out to a regional center, the regional center delivers it to the local post office, and eventually the letter is hand delivered by a mailman with a funny accent. Crikey! :)

Just like post offices determine how to deliver letters via their address, networks deliver packets according to their IP address. The low-level details of this delivery and the actual routing of packets from network to network is actually quite complex, but the basic idea is that each router is just another computer, with a routing table describing where packets matching sets of addresses should go, as well as a default gateway address describing where to pass packets for which there is no matching entry in the table. It is routing tables, and the physical connections they represent that define the network of networks that is the Internet.

The job of configuring these routing tables is up to network administrators, not programmers like us. But if you want to read more about it, then this article from ars technica provides some fascinating insight into how networks exchange packets between each other via peering and transit relationships. You can also read more details about routing tables in this linux faq, and about the border gateway protocol on wikipedia, which automatically discovers how to route packets between networks, making the internet a truly distributed system capable of dynamically routing around broken connectivity.

数据包是如何传输的?

在上一篇文章中,我用 人群传递纸条 的比喻来形容数据包的传输方式。但这个比喻过于简单,互联网并不是一个单层的计算机网络,而是一个由多个网络组成的网络

一个更准确的比喻是:邮政系统

当你想寄一封信时,你把它投进邮筒,并相信它会被正确投递。至于它是如何被送达的,你并不关心,只要最终能送到收件人手中就行。

但邮递员不会直接送信,而是先送到 本地邮局 处理。如果是同城信件,邮局直接派人送达;但如果是外地信件,则需要**“逐层转发”**,直到送到目标城市的邮局,最终由邮递员派送。

比如,一封信从 洛杉矶(Los Angeles) 发送到 悉尼(Sydney)

  1. 本地邮局处理信件并发现它是 国际邮件,然后将其送到 洛杉矶国际机场的邮件中心
  2. 信件被分类并装上飞往 悉尼 的航班。
  3. 到达悉尼机场后,当地的邮政系统接管,信件被逐步送往 地区邮局 → 本地邮局 → 收件人

网络中的数据包传输方式与邮政系统类似

  • 路由器(Router) 就像 邮局,它决定数据包应该转发到哪里。
  • IP 地址 类似于 邮寄地址,决定了数据包的目标。
  • 路由表(Routing Table) 决定了如何转发数据包,当找不到对应的路径时,数据包会被发送到 默认网关(Default Gateway)

关于网络路由和 BGP(Border Gateway Protocol) 你可以在 Wikipedia 或者 Ars Technica 找到更详细的资料。

 

Virtual Connections

Now back to connections.

If you have used TCP sockets then you know that they sure look like a connection, but since TCP is implemented on top of IP, and IP is just packets hopping from computer to computer, it follows that TCP’s concept of connection must be a virtual connection.

If TCP can create a virtual connection over IP, it follows that we can do the same over UDP.

Lets define our virtual connection as two computers exchanging UDP packets at some fixed rate like 10 packets per-second. As long as the packets are flowing, we consider the two computers to be virtually connected.

Our connection has two sides:

  • One computer sits there and listens for another computer to connect to it. We'll call this computer the server.
  • Another computer connects to a server by specifying an IP address and port. We'll call this computer the client.

In our case, we only allow one client to connect to the server at any time. We’ll generalize our connection system to support multiple simultaneous connections in a later article. Also, we assume that the IP address of the server is on a fixed IP address that the client may directly connect to.

虚拟连接

回到“连接”的话题。

如果你使用过 TCP 套接字,你会发现它看起来就像是一个真正的“连接”。但实际上,TCP 是建立在 IP 之上的,而 IP 只是数据包的跳跃式传输,因此,TCP 连接只是“虚拟连接”

如果 TCP 可以在 IP 之上创建虚拟连接,那么我们也可以在 UDP 之上创建类似的连接!

如何定义虚拟连接?

我们可以定义一个虚拟连接,只要两台计算机持续以固定的速率(如 10 个数据包/秒)交换 UDP 数据包,我们就认为它们是连接的

我们的连接包含 两端

  • 服务器(Server):等待客户端连接
  • 客户端(Client):通过指定的 IP 地址和端口去链接Server

为了简化,我们暂时只允许一个客户端连接到服务器。(未来会扩展到支持多个客户端)

Protocol ID

Since UDP is connectionless our UDP socket can receive packets sent from any computer.

We’d like to narrow this down so that the server only receives packets sent from the client, and the client only receives packets sent from the server. We can’t just filter out packets by address, because the server doesn’t know the address of the client in advance. So instead, we prefix each UDP packet with small header containing a 32 bit protocol id as follows:

    [uint protocol id]
    (packet data...)

The protocol id is just some unique number representing our game protocol. Any packet that arrives from our UDP socket first has its first four bytes inspected. If they don’t match our protocol id, then the packet is ignored. If the protocol id does match, we strip out the first four bytes of the packet and deliver the rest as payload.

You just choose some number that is reasonably unique, perhaps a hash of the name of your game and the protocol version number. But really you can use anything. The whole point is that from the point of view of our connection based protocol, packets with different protocol ids are ignored.

协议 ID(Protocol ID)

因为 UDP 是无连接的,它的套接字可以接收来自任何计算机的数据包。
为了确保服务器只接受来自客户端的正确数据包,我们在 每个 UDP 数据包的前 4 个字节 加上一个 唯一的协议 ID

[uint32 协议 ID] + (数据包内容)

服务器只会处理 协议 ID 正确的数据包,其他一律丢弃。

您只需选择一些合理唯一的数字,例如游戏名称和协议版本号的散列。但实际上你可以用任何东西。整个要点是,从我们基于连接的协议的角度来看,具有不同协议id的数据包将被忽略。

Detecting Connection

Now we need a way to detect connection.

Sure we could do some complex handshaking involving multiple UDP packets sent back and forth. Perhaps a client “request connection” packet is sent to the server, to which the server responds with a “connection accepted” sent back to the client, or maybe an “i’m busy” packet if a client tries to connect to server which already has a connected client.

Or… we could just setup our server to take the first packet it receives with the correct protocol id, and consider a connection to be established.

The client just starts sending packets to the server assuming connection, when the server receives the first packet from the client, it takes note of the IP address and port of the client, and starts sending packets back.

The client already knows the address and port of the server, since it was specified on connect. So when the client receives packets, it filters out any that don’t come from the server address. Similarly, once the server receives the first packet from the client, it gets the address and port of the client from “recvfrom”, so it is able to ignore any packets that don’t come from the client address.

We can get away with this shortcut because we only have two computers involved in the connection. In later articles, we’ll extend our connection system to support more than two computers in a client/server or peer-to-peer topology, and at this point we’ll upgrade our connection negotiation to something more robust.

But for now, why make things more complicated than they need to be?

如何检测连接?

检测连接

现在,我们需要一种方法来检测连接。

当然,我们可以使用复杂的握手机制,让客户端发送一个“请求连接”数据包到服务器,然后服务器回复一个“连接接受”数据包给客户端,或者如果服务器已经有一个客户端连接了,则发送一个“忙碌”数据包拒绝新的连接请求。

或者……我们可以让服务器接收第一个符合协议 ID 的数据包,并将其视为连接已建立。

客户端直接开始向服务器发送数据包,并假设连接已经建立。当服务器接收到客户端发送的第一个数据包后,它会记录客户端的 IP 地址和端口,并开始向客户端发送数据包。

客户端在连接时已经知道服务器的 IP 地址和端口,因此它只接收来自该服务器地址的数据包,并忽略其他来源的数据包。同样地,一旦服务器收到来自客户端的第一个数据包,它可以从 recvfrom 获取客户端的 IP 地址和端口,这样服务器也能忽略来自其他地址的数据包。

我们可以使用这个简化的方式,因为当前连接只有两台计算机。在后续文章中,我们会扩展连接系统,以支持多个计算机之间的通信,包括客户端/服务器或点对点(P2P)拓扑结构,并在那时引入更健壮的连接协商机制。

但就目前而言,为什么要让事情变得更复杂呢?

如何检测断开连接?

那么,如何检测连接断开呢?

如果我们将“连接”定义为“持续接收数据包”,那么我们可以将“断开连接”定义为“不再接收数据包”。

为了检测是否没有收到数据包,我们需要记录自上次接收数据包以来经过的时间,并在客户端和服务器两端都进行此操作。

每次收到数据包时,我们将计时器重置为 0.0。在每次更新时,我们会根据经过的时间增加计时器的值。

如果计时器的值超过设定的阈值(例如 10 秒),则连接“超时”并被视为断开连接。

这种方式还可以优雅地处理多个客户端尝试连接到已被占用的服务器的情况。由于服务器已经有一个连接的客户端,它会忽略来自其他地址的数据包,因此第二个客户端在发送数据包后不会收到任何响应。由于该客户端在 10 秒内未收到任何数据包,它会超时并断开连接。

 

结论

建立虚拟连接所需的一切就是:

  • 一种建立连接的方法
  • 过滤掉与连接无关的数据包
  • 通过超时机制检测连接断开

这样,我们的虚拟连接和 TCP 连接一样可靠,并且稳定的 UDP 数据包流足以作为多人动作游戏的基础。

现在,你可以基于 UDP 轻松地建立一个客户端/服务器架构,实现双人在线游戏,而无需依赖 TCP。

 

 

posted @ 2025-03-21 16:08  sun_dust_shadow  阅读(24)  评论(0)    收藏  举报