<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://www.anwsome.com//api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Xlong</id>
	<title>程序员技术小站 - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="http://www.anwsome.com//api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Xlong"/>
	<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php/%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/Xlong"/>
	<updated>2026-04-15T01:51:59Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=57</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=57"/>
		<updated>2025-12-25T15:01:37Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[编写一个TCP/IP栈3-TCP基础与握手]]&lt;br /&gt;
&lt;br /&gt;
* [[编写一个TCP/IP栈2-IPv4和ICMPv4]]&lt;br /&gt;
* [[编写一个TCP/IP栈1-以太网和ARP]]&lt;br /&gt;
* [[构建自己的区块链|构建自己的区块链（python教程）]]&lt;br /&gt;
* [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]]&lt;br /&gt;
* [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=56</id>
		<title>编写一个TCP/IP栈3-TCP基础与握手</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=56"/>
		<updated>2025-12-25T14:58:06Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人棘手的传输控制协议(TCP)了。&lt;br /&gt;
&lt;br /&gt;
TCP运行在OSI网络的第4层负责端对端传输操作, 负责恢复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。&lt;br /&gt;
&lt;br /&gt;
TCP 并非全新的协议——第一个规范于1974年问世。从那以后,很多事情都发生了变化,也获得了许多扩展和修正。&lt;br /&gt;
&lt;br /&gt;
本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。&lt;br /&gt;
&lt;br /&gt;
= 可靠性机制 =&lt;br /&gt;
可靠地发送数据的问题看似肤浅,但其实际实现却涉及问题。主要涉及数据报式网络中的错误修复问题:&lt;br /&gt;
&lt;br /&gt;
* 发送方应等待接收方确认多长时间?&lt;br /&gt;
* 如果接收方无法按发送速度处理数据怎么办?&lt;br /&gt;
* 如果中间的网络(例如路由器)无法像发送数据时那样快速处理数据,该怎么办?&lt;br /&gt;
&lt;br /&gt;
在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。&lt;br /&gt;
&lt;br /&gt;
为解决这些问题,可以使用多种机制。最常见的就是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 222529.png|居中|缩略图|630x630像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
使用这种滑动窗口的便利之处在于,它能缓解流量控制。当接收端无法以发送速度快速处理数据时,需要进行流量控制。在这种情况下,滑动窗口的大小将被协商以降低,从而限制发送方的字节流输出。&lt;br /&gt;
&lt;br /&gt;
拥塞控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,相关研究至今仍在进行。&lt;br /&gt;
&lt;br /&gt;
= TCP基础 =&lt;br /&gt;
TCP 中的实现机制比其他协议(如 UDP 和 IP)要多得多。&#039;&#039;即TCP是一种面向连接的协议&#039;&#039;,这意味着作为第一步,在通信双方之间建立统一通信通道。双方积极维护这一联系:建立联系(握手),向对方告知数据状况等可能的问题。&lt;br /&gt;
&lt;br /&gt;
TCP的另一个重要特性是它是一种流式协议。与UDP不同,TCP 无法保证应用程序在发送和接收时能够稳定地“记录”数据。TCP 实现必须缓冲数据,当数据包丢失、重新排序或损坏时,TCP 必须等待并组织缓冲区中的数据。只有当数据被认定为完整时,TCP 才能将数据交给应用程序的套接字。&lt;br /&gt;
&lt;br /&gt;
当TCP以数据流的形式运行时,来自该流的“块”必须转换为IP可以承载的数据包。&#039;&#039;这称为包化&#039;&#039;,其中TCP头块包含流中当前索引的序列号。这还具有方便的属性,即流可以分成许多可变大小的段,而TCP则知道如何重新集成它们。&lt;br /&gt;
&lt;br /&gt;
与IP类似,TCP也会检查消息是否具有完整性。这是通过与IP相同的校验和算法实现的,但增加了更多细节。主要情况下,校验和是端到端的,这意味着报文头和数据都包含在校验中。此外,还包含一个由IP头编造的伪头。&lt;br /&gt;
&lt;br /&gt;
如果TCP实现接收损坏的片段,就会丢弃它们,并且不会通知发送方。此错误由发送方设置的定时器进行纠正,如果接收方从未确认该片段,则可用于重新传输片段。&lt;br /&gt;
&lt;br /&gt;
TCP &#039;&#039;full-duplex&#039;&#039;是一个全双工系统,意味着流量可以双向同时流动。通信端必须将两个方向数据顺序保存在内存中。更具体地说，TCP 通过在发送的数据段中包含对反向流量的确认信息来减少流量占用。&lt;br /&gt;
&lt;br /&gt;
从本质上讲，数据流的排序是 TCP 的主要原则。然而，保持数据流同步并非易事。&lt;br /&gt;
&lt;br /&gt;
= TCP 报头格式 =&lt;br /&gt;
接下来，我们将定义消息头并描述其字段。TCP 消息头看似简单，但实际上包含大量关于通信状态的信息。&lt;br /&gt;
&lt;br /&gt;
TCP 报头大小为 20 字节：&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 223342.png|居中|缩略图|558x558像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;源端口&#039;&#039;和&#039;&#039;目标端口&#039;&#039;字段用于建立与主机之间的多个连接。具体来说，伯克利套接字是应用程序绑定到 TCP 网络协议栈的常用接口。网络协议栈通过端口来确定流量的路由。由于这些字段为 16 位，因此端口值的范围为 0 到 65535。&lt;br /&gt;
&lt;br /&gt;
由于数据流中的每个字节都已编号，因此&#039;&#039;序列号&#039;&#039;代表 TCP 段的窗口索引。在握手过程中，该序列号包含&#039;&#039;初始序列号&#039;&#039; (ISN)。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;确认号&#039;&#039;包含发送方预期接收的下一个字节的窗口索引。握手完成后，ACK 字段必须始终填充。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;头部长度&#039;&#039; (HL) 字段以 32 位表示头部的长度。&lt;br /&gt;
&lt;br /&gt;
接下来，会显示几个标志位。前 4 位（ &#039;&#039;rsvd&#039;&#039; ）未使用。&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;拥塞窗口减少&#039;&#039; (C) 用于通知发送方降低发送速率。&lt;br /&gt;
# &#039;&#039;ECN 回显&#039;&#039; (E) 表示发送方已收到拥塞通知。&lt;br /&gt;
# &#039;&#039;紧急指针&#039;&#039; (U) 表示该段包含优先数据。&lt;br /&gt;
# &#039;&#039;ACK&#039;&#039; （A）字段用于传递 TCP 握手的状态。在连接的剩余时间内，它将一直保持开启状态。&lt;br /&gt;
# &#039;&#039;PSH&#039;&#039; （P）用于指示接收方应尽快将数据“推送”到应用程序。&lt;br /&gt;
# &#039;&#039;RST&#039;&#039; （R）重置 TCP 连接。&lt;br /&gt;
# &#039;&#039;SYN&#039;&#039; (S) 用于在初始握手中同步序列号。&lt;br /&gt;
# &#039;&#039;FIN&#039;&#039; （F）表示发送方已完成数据发送。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;窗口大小&#039;&#039;字段用于指定窗口大小。换句话说，它表示接收方愿意接收的字节数。由于这是一个 16 位字段，因此最大窗口大小为 65,535 字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;TCP 校验和&#039;&#039;字段用于验证 TCP 数据段的完整性。其算法与互联网协议相同，但输入数据段除了包含 TCP 数据外，还包含来自 IP 数据报的伪头部。&lt;br /&gt;
&lt;br /&gt;
当设置了 U 标志时，会使用&#039;&#039;紧急指针&#039;&#039; 。该指针指示数据流中紧急数据的位置。&lt;br /&gt;
&lt;br /&gt;
在邮件头之后，可以提供多个选项。例如， &#039;&#039;最大段大小&#039;&#039; (MSS) 就是其中之一，发送方可以通过它告知接收方数据段的最大长度。&lt;br /&gt;
&lt;br /&gt;
在列出所有可选方案之后，才是实际数据。然而，这些数据并非必需。例如，握手过程仅需 TCP 头部即可完成。&lt;br /&gt;
&lt;br /&gt;
= TCP握手 =&lt;br /&gt;
TCP 连接通常经历以下几个阶段：连接建立（握手）、数据传输和连接关闭。下图展示了 TCP 的典型握手过程：&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 224135.png|居中|缩略图|551x551像素]]&lt;br /&gt;
&lt;br /&gt;
# 主机 A 的套接字处于关闭状态，这意味着它不接受连接。相反，主机 B 绑定到特定端口的套接字正在监听新的连接。&lt;br /&gt;
# 主机 A 打算与主机 B 建立连接。因此，A 构造了一个 TCP 段，该段的 SYN 标志已设置，并且序列字段的值也已填充为 (100)。&lt;br /&gt;
# 主机 B 以一个设置了 SYN 和 ACK 字段的 TCP 段进行响应，并通过将 A 的序列号加 1 来确认（ACK=101）。同样，B 也生成一个序列号（300）。&lt;br /&gt;
# 三次握手以连接请求发起方 (A) 发送的确认 (ACK) 结束。确认字段反映了主机接下来期望从另一方收到的序列号。&lt;br /&gt;
# 数据开始流动，主要是因为双方都确认了对方的数据序号。&lt;br /&gt;
&lt;br /&gt;
这是建立 TCP 连接的常见场景。然而，由此会产生几个问题：&lt;br /&gt;
&lt;br /&gt;
# 初始序列号是如何选择的？&lt;br /&gt;
# 如果双方同时向对方提出连接请求呢？&lt;br /&gt;
# 如果某些环节延迟一段时间甚至无限期延迟怎么办？&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;初始序列号&#039;&#039; (ISN) 由通信双方在首次连接时独立选择。由于它是识别连接的关键部分，因此必须选择尽可能唯一且不易猜测的序列号。事实上， &#039;&#039;TCP 序列号攻击&#039;&#039;  指的是攻击者可以复制 TCP 连接并有效地向目标发送数据，从而冒充可信主机的情况。&lt;br /&gt;
&lt;br /&gt;
The original specification suggests that the ISN is chosen by a counter that increments every 4 microseconds. This, however, can be guessed by an attacker. In reality, modern networking stacks generate the ISN by more complicated methods.&lt;br /&gt;
&lt;br /&gt;
原始规范表明，ISN 由一个每 4 微秒递增一次的计数器生成。然而，攻击者可以猜到这一点。实际上，现代网络协议栈采用更复杂的方法生成 ISN。&lt;br /&gt;
&lt;br /&gt;
当两端都收到对方的连接请求（SYN）时，这种情况称为&#039;&#039;同时打开&#039;&#039; 。TCP 握手过程中会通过额外的消息交换来解决这个问题：双方都发送一个 ACK（彼此并不知道对方是否也发送了 ACK），然后双方都发送 SYN-ACK 来确认请求。之后，数据传输开始。&lt;br /&gt;
&lt;br /&gt;
最后，TCP 实现必须包含一个计时器，用于判断何时放弃建立连接。系统会尝试重新建立连接，通常采用指数退避算法，但一旦达到最大重试次数或时间阈值，连接就会被视为已断开。&lt;br /&gt;
&lt;br /&gt;
= TCP 选项 =&lt;br /&gt;
TCP 报头段的最后一个字段保留用于存储 TCP 选项。最初的规范提供了三个选项，但后来的规范又增加了更多选项。接下来，我们将介绍最常用的选项。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;最大段大小&#039;&#039; (MSS) 选项指定 TCP 实现能够接收的最大 TCP 段大小。在 IPv4 中，该值的典型值为 1460 字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;选择性确认&#039;&#039; (SACK) 选项优化了传输过程中大量数据包丢失、接收端数据窗口出现“空洞”的情况。为了弥补由此导致的吞吐量下降，TCP 实现可以通过 SACK 通知发送方哪些数据包未收到。因此，与累积确认机制相比，发送方能够以更直接的方式获取数据状态信息。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;窗口缩放&#039;&#039;选项可以增大原本受限的 16 位窗口大小。具体来说，如果双方都在握手段中包含此选项，则窗口大小将乘以该缩放比例。更大的窗口大小对于大批量数据传输尤为重要。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;时间戳&#039;&#039;选项允许发送方在 TCP 数据段中插入时间戳，该时间戳可用于计算每个 ACK​​ 数据段的往返时间 (RTT)。此信息随后可用于计算 TCP 重传超时时间。&lt;br /&gt;
&lt;br /&gt;
= 测试TCP握手 =&lt;br /&gt;
现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 225216.png|居中|缩略图|627x627像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
由于nmap进行SYN扫描(它只等待 SYN-ACK 来判断目标端口是否打开),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。&lt;br /&gt;
&lt;br /&gt;
= 关键实现 =&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct net_ops tcp_ops = {&lt;br /&gt;
    .alloc_sock = &amp;amp;tcp_alloc_sock,&lt;br /&gt;
    .init = &amp;amp;tcp_v4_init_sock,&lt;br /&gt;
    .connect = &amp;amp;tcp_v4_connect,&lt;br /&gt;
    .disconnect = &amp;amp;tcp_disconnect,&lt;br /&gt;
    .write = &amp;amp;tcp_write,&lt;br /&gt;
    .read = &amp;amp;tcp_read,&lt;br /&gt;
    .recv_notify = &amp;amp;tcp_recv_notify,&lt;br /&gt;
    .close = &amp;amp;tcp_close,&lt;br /&gt;
    .abort = &amp;amp;tcp_abort,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
void tcp_init()&lt;br /&gt;
{&lt;br /&gt;
}&lt;br /&gt;
static void tcp_init_segment(struct tcphdr *th, struct iphdr *ih, struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    th-&amp;gt;sport = ntohs(th-&amp;gt;sport);&lt;br /&gt;
    th-&amp;gt;dport = ntohs(th-&amp;gt;dport);&lt;br /&gt;
    th-&amp;gt;seq = ntohl(th-&amp;gt;seq);&lt;br /&gt;
    th-&amp;gt;ack_seq = ntohl(th-&amp;gt;ack_seq);&lt;br /&gt;
    th-&amp;gt;win = ntohs(th-&amp;gt;win);&lt;br /&gt;
    th-&amp;gt;csum = ntohs(th-&amp;gt;csum);&lt;br /&gt;
    th-&amp;gt;urp = ntohs(th-&amp;gt;urp);&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;seq = th-&amp;gt;seq;&lt;br /&gt;
    skb-&amp;gt;dlen = ip_len(ih) - tcp_hlen(th);&lt;br /&gt;
    skb-&amp;gt;len = skb-&amp;gt;dlen + th-&amp;gt;syn + th-&amp;gt;fin;&lt;br /&gt;
    skb-&amp;gt;end_seq = skb-&amp;gt;seq + skb-&amp;gt;dlen;&lt;br /&gt;
    skb-&amp;gt;payload = th-&amp;gt;data;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void tcp_clear_queues(struct tcp_sock *tsk) {&lt;br /&gt;
    skb_queue_free(&amp;amp;tsk-&amp;gt;ofo_queue);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_in(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk;&lt;br /&gt;
    struct iphdr *iph;&lt;br /&gt;
    struct tcphdr *th;&lt;br /&gt;
&lt;br /&gt;
    iph = ip_hdr(skb);&lt;br /&gt;
    th = (struct tcphdr*) iph-&amp;gt;data;&lt;br /&gt;
&lt;br /&gt;
    tcp_init_segment(th, iph, skb);&lt;br /&gt;
    &lt;br /&gt;
    sk = inet_lookup(skb, th-&amp;gt;sport, th-&amp;gt;dport);&lt;br /&gt;
&lt;br /&gt;
    if (sk == NULL) {&lt;br /&gt;
        print_err(&amp;quot;No TCP socket for sport %d dport %d\n&amp;quot;,&lt;br /&gt;
                  th-&amp;gt;sport, th-&amp;gt;dport);&lt;br /&gt;
        free_skb(skb);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
&lt;br /&gt;
    tcp_in_dbg(th, sk, skb);&lt;br /&gt;
    /* if (tcp_checksum(iph, th) != 0) { */&lt;br /&gt;
    /*     goto discard; */&lt;br /&gt;
    /* } */&lt;br /&gt;
    tcp_input_state(sk, th, skb);&lt;br /&gt;
&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_udp_checksum(uint32_t saddr, uint32_t daddr, uint8_t proto,&lt;br /&gt;
                     uint8_t *data, uint16_t len)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t sum = 0;&lt;br /&gt;
&lt;br /&gt;
    sum += saddr;&lt;br /&gt;
    sum += daddr;&lt;br /&gt;
    sum += htons(proto);&lt;br /&gt;
    sum += htons(len);&lt;br /&gt;
    &lt;br /&gt;
    return checksum(data, len, sum);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_checksum(struct sk_buff *skb, uint32_t saddr, uint32_t daddr)&lt;br /&gt;
{&lt;br /&gt;
    return tcp_udp_checksum(saddr, daddr, IP_TCP, skb-&amp;gt;data, skb-&amp;gt;len);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
struct sock *tcp_alloc_sock()&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = malloc(sizeof(struct tcp_sock));&lt;br /&gt;
&lt;br /&gt;
    memset(tsk, 0, sizeof(struct tcp_sock));&lt;br /&gt;
    tsk-&amp;gt;sk.state = TCP_CLOSE;&lt;br /&gt;
    tsk-&amp;gt;sackok = 1;&lt;br /&gt;
    &lt;br /&gt;
    tsk-&amp;gt;rmss = 1460;&lt;br /&gt;
    // Default to 536 as per spec&lt;br /&gt;
    tsk-&amp;gt;smss = 536;&lt;br /&gt;
&lt;br /&gt;
    skb_queue_init(&amp;amp;tsk-&amp;gt;ofo_queue);&lt;br /&gt;
    &lt;br /&gt;
    return (struct sock *)tsk;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_init_sock(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    tcp_init_sock(sk);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_init_sock(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void __tcp_set_state(struct sock *sk, uint32_t state)&lt;br /&gt;
{&lt;br /&gt;
    sk-&amp;gt;state = state;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static uint16_t generate_port()&lt;br /&gt;
{&lt;br /&gt;
    /* TODO: Generate a proper port */&lt;br /&gt;
    static int port = 40000;&lt;br /&gt;
&lt;br /&gt;
    pthread_rwlock_wrlock(&amp;amp;tcplock);&lt;br /&gt;
    int copy =  ++port + (timer_get_tick() % 10000);&lt;br /&gt;
    pthread_rwlock_unlock(&amp;amp;tcplock);&lt;br /&gt;
&lt;br /&gt;
    return copy;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int generate_iss()&lt;br /&gt;
{&lt;br /&gt;
    /* TODO: Generate a proper ISS */&lt;br /&gt;
    return (int)time(NULL) * rand();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_connect(struct sock *sk, const struct sockaddr *addr, int addrlen, int flags)&lt;br /&gt;
{&lt;br /&gt;
    uint16_t dport = ((struct sockaddr_in *)addr)-&amp;gt;sin_port;&lt;br /&gt;
    uint32_t daddr = ((struct sockaddr_in *)addr)-&amp;gt;sin_addr.s_addr;&lt;br /&gt;
&lt;br /&gt;
    sk-&amp;gt;dport = ntohs(dport);&lt;br /&gt;
    sk-&amp;gt;sport = generate_port();&lt;br /&gt;
    sk-&amp;gt;daddr = ntohl(daddr);&lt;br /&gt;
    /* TODO: Do not hardcode lvl-ip local interface */&lt;br /&gt;
    sk-&amp;gt;saddr = parse_ipv4_string(&amp;quot;10.0.0.4&amp;quot;); &lt;br /&gt;
&lt;br /&gt;
    return tcp_connect(sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_disconnect(struct sock *sk, int flags)&lt;br /&gt;
{&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_write(struct sock *sk, const void *buf, int len)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    int ret = sk-&amp;gt;err;&lt;br /&gt;
&lt;br /&gt;
    if (ret != 0) goto out;&lt;br /&gt;
&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        break;&lt;br /&gt;
    default:&lt;br /&gt;
        ret = -EBADF;&lt;br /&gt;
        goto out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return tcp_send(tsk, buf, len);    &lt;br /&gt;
&lt;br /&gt;
out: &lt;br /&gt;
    return ret;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_read(struct sock *sk, void *buf, int len)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    int ret = -1;&lt;br /&gt;
&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE:&lt;br /&gt;
        ret = -EBADF;&lt;br /&gt;
        goto out;&lt;br /&gt;
    case TCP_LISTEN:&lt;br /&gt;
    case TCP_SYN_SENT:&lt;br /&gt;
    case TCP_SYN_RECEIVED:&lt;br /&gt;
        /* Queue for processing after entering ESTABLISHED state.  If there&lt;br /&gt;
           is no room to queue this request, respond with &amp;quot;error:&lt;br /&gt;
           insufficient resources&amp;quot;. */&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
    case TCP_FIN_WAIT_1:&lt;br /&gt;
    case TCP_FIN_WAIT_2:&lt;br /&gt;
        /* If insufficient incoming segments are queued to satisfy the&lt;br /&gt;
           request, queue the request. */&lt;br /&gt;
        &lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        /* If no text is awaiting delivery, the RECEIVE will get a&lt;br /&gt;
           &amp;quot;error:  connection closing&amp;quot; response.  Otherwise, any remaining&lt;br /&gt;
           text can be used to satisfy the RECEIVE. */&lt;br /&gt;
        if (!skb_queue_empty(&amp;amp;tsk-&amp;gt;sk.receive_queue)) break;&lt;br /&gt;
        if (tsk-&amp;gt;flags &amp;amp; TCP_FIN) {&lt;br /&gt;
            tsk-&amp;gt;flags &amp;amp;= ~TCP_FIN;&lt;br /&gt;
            return 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSING:&lt;br /&gt;
    case TCP_LAST_ACK:&lt;br /&gt;
    case TCP_TIME_WAIT:&lt;br /&gt;
        ret = sk-&amp;gt;err;&lt;br /&gt;
        goto out;&lt;br /&gt;
    default:&lt;br /&gt;
        goto out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return tcp_receive(tsk, buf, len);    &lt;br /&gt;
&lt;br /&gt;
out: &lt;br /&gt;
    return ret;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_recv_notify(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    if (&amp;amp;(sk-&amp;gt;recv_wait)) {&lt;br /&gt;
        return wait_wakeup(&amp;amp;sk-&amp;gt;recv_wait);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // No recv wait lock&lt;br /&gt;
    return -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_close(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE:&lt;br /&gt;
    case TCP_CLOSING:&lt;br /&gt;
    case TCP_LAST_ACK:&lt;br /&gt;
    case TCP_TIME_WAIT:&lt;br /&gt;
    case TCP_FIN_WAIT_1:&lt;br /&gt;
    case TCP_FIN_WAIT_2:&lt;br /&gt;
        /* Respond with &amp;quot;error:  connection closing&amp;quot;. */&lt;br /&gt;
        sk-&amp;gt;err = -EBADF;&lt;br /&gt;
        return -1;&lt;br /&gt;
    case TCP_LISTEN:&lt;br /&gt;
    case TCP_SYN_SENT:&lt;br /&gt;
    case TCP_SYN_RECEIVED:&lt;br /&gt;
        return tcp_done(sk);&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
        /* Queue this until all preceding SENDs have been segmentized, then&lt;br /&gt;
           form a FIN segment and send it.  In any case, enter FIN-WAIT-1&lt;br /&gt;
           state. */&lt;br /&gt;
        tcp_set_state(sk, TCP_FIN_WAIT_1);&lt;br /&gt;
        tcp_queue_fin(sk);&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        /* Queue this request until all preceding SENDs have been&lt;br /&gt;
           segmentized; then send a FIN segment, enter LAST_ACK state. */&lt;br /&gt;
        tcp_queue_fin(sk);&lt;br /&gt;
        break;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;Unknown TCP state for close\n&amp;quot;);&lt;br /&gt;
        return -1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_abort(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcp_send_reset(tsk);&lt;br /&gt;
    return tcp_done(sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static int tcp_free(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    tcp_clear_timers(sk);&lt;br /&gt;
    tcp_clear_queues(tsk);&lt;br /&gt;
&lt;br /&gt;
    wait_wakeup(&amp;amp;sk-&amp;gt;sock-&amp;gt;sleep);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_done(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    tcp_set_state(sk, TCP_CLOSING);&lt;br /&gt;
    tcp_free(sk);&lt;br /&gt;
    return socket_delete(sk-&amp;gt;sock);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_clear_timers(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcp_stop_rto_timer(tsk);&lt;br /&gt;
    tcp_stop_delack_timer(tsk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;keepalive);&lt;br /&gt;
    tsk-&amp;gt;keepalive = NULL;&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_stop_rto_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk) {&lt;br /&gt;
        timer_cancel(tsk-&amp;gt;retransmit);&lt;br /&gt;
        tsk-&amp;gt;retransmit = NULL;&lt;br /&gt;
        tsk-&amp;gt;backoff = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_release_rto_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk) {&lt;br /&gt;
        timer_release(tsk-&amp;gt;retransmit);&lt;br /&gt;
        tsk-&amp;gt;retransmit = NULL;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_stop_delack_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;delack);&lt;br /&gt;
    tsk-&amp;gt;delack = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_release_delack_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    timer_release(tsk-&amp;gt;delack);&lt;br /&gt;
    tsk-&amp;gt;delack = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_handle_fin_state(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        tcp_set_state(sk, TCP_LAST_ACK);&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
        tcp_set_state(sk, TCP_FIN_WAIT_1);&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void *tcp_linger(void *arg)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk = (struct sock *) arg;&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcpsock_dbg(&amp;quot;TCP time-wait timeout, freeing TCB&amp;quot;, sk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
&lt;br /&gt;
    tcp_done(sk);&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
&lt;br /&gt;
    return NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void *tcp_user_timeout(void *arg)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk = (struct sock *) arg;&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcpsock_dbg(&amp;quot;TCP user timeout, freeing TCB and aborting conn&amp;quot;, sk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
&lt;br /&gt;
    tcp_abort(sk);&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
    &lt;br /&gt;
    return NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_enter_time_wait(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    tcp_set_state(sk, TCP_TIME_WAIT);&lt;br /&gt;
&lt;br /&gt;
    tcp_clear_timers(sk);&lt;br /&gt;
    /* RFC793 arbitrarily defines MSL to be 2 minutes */&lt;br /&gt;
    tsk-&amp;gt;linger = timer_add(TCP_2MSL, &amp;amp;tcp_linger, sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_rearm_user_timeout(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    if (sk-&amp;gt;state == TCP_TIME_WAIT) return;&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    /* RFC793 set user timeout */&lt;br /&gt;
    tsk-&amp;gt;linger = timer_add(TCP_USER_TIMEOUT, &amp;amp;tcp_user_timeout, sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_rtt(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk-&amp;gt;backoff &amp;gt; 0 || !tsk-&amp;gt;retransmit) {&lt;br /&gt;
        // Karn&#039;s Algorithm: Don&#039;t measure retransmissions&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int r = timer_get_tick() - (tsk-&amp;gt;retransmit-&amp;gt;expires - tsk-&amp;gt;rto);&lt;br /&gt;
    if (r &amp;lt; 0) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tsk-&amp;gt;srtt) {&lt;br /&gt;
        /* RFC6298 2.2 first measurement is made */&lt;br /&gt;
        tsk-&amp;gt;srtt = r;&lt;br /&gt;
        tsk-&amp;gt;rttvar = r / 2;&lt;br /&gt;
    } else {&lt;br /&gt;
        /* RFC6298 2.3 a subsequent measurement is made */&lt;br /&gt;
        double beta = 0.25;&lt;br /&gt;
        double alpha = 0.125;&lt;br /&gt;
        tsk-&amp;gt;rttvar = (1 - beta) * tsk-&amp;gt;rttvar + beta * abs(tsk-&amp;gt;srtt - r);&lt;br /&gt;
        tsk-&amp;gt;srtt = (1 - alpha) * tsk-&amp;gt;srtt + alpha * r;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int k = 4 * tsk-&amp;gt;rttvar;&lt;br /&gt;
&lt;br /&gt;
    /* RFC6298 says RTO should be at least 1 second. Linux uses 200ms */&lt;br /&gt;
    if (k &amp;lt; 200) k = 200;&lt;br /&gt;
&lt;br /&gt;
    tsk-&amp;gt;rto = tsk-&amp;gt;srtt + k;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_calculate_sacks(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sack_block *sb = &amp;amp;tsk-&amp;gt;sacks[tsk-&amp;gt;sacklen];&lt;br /&gt;
&lt;br /&gt;
    sb-&amp;gt;left = 0;&lt;br /&gt;
    sb-&amp;gt;right = 0;&lt;br /&gt;
&lt;br /&gt;
    struct sk_buff *next;&lt;br /&gt;
    struct list_head *item, *tmp;&lt;br /&gt;
&lt;br /&gt;
    list_for_each_safe(item, tmp, &amp;amp;tsk-&amp;gt;ofo_queue.head) {&lt;br /&gt;
        next = list_entry(item, struct sk_buff, list);&lt;br /&gt;
&lt;br /&gt;
        if (sb-&amp;gt;left == 0) {&lt;br /&gt;
            sb-&amp;gt;left = next-&amp;gt;seq;&lt;br /&gt;
            tsk-&amp;gt;sacklen++;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        if (sb-&amp;gt;right == 0) sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
        else if (sb-&amp;gt;right == next-&amp;gt;seq) sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
        else {&lt;br /&gt;
            if (tsk-&amp;gt;sacklen &amp;gt;= tsk-&amp;gt;sacks_allowed) break;&lt;br /&gt;
            &lt;br /&gt;
            sb = &amp;amp;tsk-&amp;gt;sacks[tsk-&amp;gt;sacklen];&lt;br /&gt;
            sb-&amp;gt;left = next-&amp;gt;seq;&lt;br /&gt;
            sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
            tsk-&amp;gt;sacklen++;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=55</id>
		<title>编写一个TCP/IP栈3-TCP基础与握手</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=55"/>
		<updated>2025-12-25T14:57:01Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人棘手的传输控制协议(TCP)了。&lt;br /&gt;
&lt;br /&gt;
TCP运行在OSI网络的第4层负责端对端传输操作, 负责恢复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。&lt;br /&gt;
&lt;br /&gt;
TCP 并非全新的协议——第一个规范于1974年问世。从那以后,很多事情都发生了变化,也获得了许多扩展和修正。&lt;br /&gt;
&lt;br /&gt;
本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。&lt;br /&gt;
&lt;br /&gt;
= 可靠性机制 =&lt;br /&gt;
可靠地发送数据的问题看似肤浅,但其实际实现却涉及问题。主要涉及数据报式网络中的错误修复问题:&lt;br /&gt;
&lt;br /&gt;
* 发送方应等待接收方确认多长时间?&lt;br /&gt;
* 如果接收方无法按发送速度处理数据怎么办?&lt;br /&gt;
* 如果中间的网络(例如路由器)无法像发送数据时那样快速处理数据,该怎么办?&lt;br /&gt;
&lt;br /&gt;
在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。&lt;br /&gt;
&lt;br /&gt;
为解决这些问题,可以使用多种机制。最常见的就是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 222529.png|居中|缩略图|630x630像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
使用这种滑动窗口的便利之处在于,它能缓解流量控制。当接收端无法以发送速度快速处理数据时,需要进行流量控制。在这种情况下,滑动窗口的大小将被协商以降低,从而限制发送方的字节流输出。&lt;br /&gt;
&lt;br /&gt;
拥塞控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,相关研究至今仍在进行。&lt;br /&gt;
&lt;br /&gt;
= TCP基础 =&lt;br /&gt;
TCP 中的实现机制比其他协议(如 UDP 和 IP)要多得多。&#039;&#039;即TCP是一种面向连接的协议&#039;&#039;,这意味着作为第一步,在通信双方之间建立统一通信通道。双方积极维护这一联系:建立联系(握手),向对方告知数据状况等可能的问题。&lt;br /&gt;
&lt;br /&gt;
TCP的另一个重要特性是它是一种流式协议。与UDP不同,TCP 无法保证应用程序在发送和接收时能够稳定地“记录”数据。TCP 实现必须缓冲数据,当数据包丢失、重新排序或损坏时,TCP 必须等待并组织缓冲区中的数据。只有当数据被认定为完整时,TCP 才能将数据交给应用程序的套接字。&lt;br /&gt;
&lt;br /&gt;
当TCP以数据流的形式运行时,来自该流的“块”必须转换为IP可以承载的数据包。&#039;&#039;这称为包化&#039;&#039;,其中TCP头块包含流中当前索引的序列号。这还具有方便的属性,即流可以分成许多可变大小的段,而TCP则知道如何重新集成它们。&lt;br /&gt;
&lt;br /&gt;
与IP类似,TCP也会检查消息是否具有完整性。这是通过与IP相同的校验和算法实现的,但增加了更多细节。主要情况下,校验和是端到端的,这意味着报文头和数据都包含在校验中。此外,还包含一个由IP头编造的伪头。&lt;br /&gt;
&lt;br /&gt;
如果TCP实现接收损坏的片段,就会丢弃它们,并且不会通知发送方。此错误由发送方设置的定时器进行纠正,如果接收方从未确认该片段,则可用于重新传输片段。&lt;br /&gt;
&lt;br /&gt;
TCP &#039;&#039;full-duplex&#039;&#039;是一个全双工系统,意味着流量可以双向同时流动。通信端必须将两个方向数据顺序保存在内存中。更具体地说，TCP 通过在发送的数据段中包含对反向流量的确认信息来减少流量占用。&lt;br /&gt;
&lt;br /&gt;
从本质上讲，数据流的排序是 TCP 的主要原则。然而，保持数据流同步并非易事。&lt;br /&gt;
&lt;br /&gt;
= TCP 报头格式 =&lt;br /&gt;
接下来，我们将定义消息头并描述其字段。TCP 消息头看似简单，但实际上包含大量关于通信状态的信息。&lt;br /&gt;
&lt;br /&gt;
TCP 报头大小为 20 字节：&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 223342.png|居中|缩略图|558x558像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;源端口&#039;&#039;和&#039;&#039;目标端口&#039;&#039;字段用于建立与主机之间的多个连接。具体来说，伯克利套接字是应用程序绑定到 TCP 网络协议栈的常用接口。网络协议栈通过端口来确定流量的路由。由于这些字段为 16 位，因此端口值的范围为 0 到 65535。&lt;br /&gt;
&lt;br /&gt;
由于数据流中的每个字节都已编号，因此&#039;&#039;序列号&#039;&#039;代表 TCP 段的窗口索引。在握手过程中，该序列号包含&#039;&#039;初始序列号&#039;&#039; (ISN)。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;确认号&#039;&#039;包含发送方预期接收的下一个字节的窗口索引。握手完成后，ACK 字段必须始终填充。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;头部长度&#039;&#039; (HL) 字段以 32 位表示头部的长度。&lt;br /&gt;
&lt;br /&gt;
接下来，会显示几个标志位。前 4 位（ &#039;&#039;rsvd&#039;&#039; ）未使用。&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;拥塞窗口减少&#039;&#039; (C) 用于通知发送方降低发送速率。&lt;br /&gt;
# &#039;&#039;ECN 回显&#039;&#039; (E) 表示发送方已收到拥塞通知。&lt;br /&gt;
# &#039;&#039;紧急指针&#039;&#039; (U) 表示该段包含优先数据。&lt;br /&gt;
# &#039;&#039;ACK&#039;&#039; （A）字段用于传递 TCP 握手的状态。在连接的剩余时间内，它将一直保持开启状态。&lt;br /&gt;
# &#039;&#039;PSH&#039;&#039; （P）用于指示接收方应尽快将数据“推送”到应用程序。&lt;br /&gt;
# &#039;&#039;RST&#039;&#039; （R）重置 TCP 连接。&lt;br /&gt;
# &#039;&#039;SYN&#039;&#039; (S) 用于在初始握手中同步序列号。&lt;br /&gt;
# &#039;&#039;FIN&#039;&#039; （F）表示发送方已完成数据发送。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;窗口大小&#039;&#039;字段用于指定窗口大小。换句话说，它表示接收方愿意接收的字节数。由于这是一个 16 位字段，因此最大窗口大小为 65,535 字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;TCP 校验和&#039;&#039;字段用于验证 TCP 数据段的完整性。其算法与互联网协议相同，但输入数据段除了包含 TCP 数据外，还包含来自 IP 数据报的伪头部。&lt;br /&gt;
&lt;br /&gt;
当设置了 U 标志时，会使用&#039;&#039;紧急指针&#039;&#039; 。该指针指示数据流中紧急数据的位置。&lt;br /&gt;
&lt;br /&gt;
在邮件头之后，可以提供多个选项。例如， &#039;&#039;最大段大小&#039;&#039; (MSS) 就是其中之一，发送方可以通过它告知接收方数据段的最大长度。&lt;br /&gt;
&lt;br /&gt;
在列出所有可选方案之后，才是实际数据。然而，这些数据并非必需。例如，握手过程仅需 TCP 头部即可完成。&lt;br /&gt;
&lt;br /&gt;
= TCP握手 =&lt;br /&gt;
TCP 连接通常经历以下几个阶段：连接建立（握手）、数据传输和连接关闭。下图展示了 TCP 的典型握手过程：&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 224135.png|居中|缩略图|551x551像素]]&lt;br /&gt;
&lt;br /&gt;
# 主机 A 的套接字处于关闭状态，这意味着它不接受连接。相反，主机 B 绑定到特定端口的套接字正在监听新的连接。&lt;br /&gt;
# 主机 A 打算与主机 B 建立连接。因此，A 构造了一个 TCP 段，该段的 SYN 标志已设置，并且序列字段的值也已填充为 (100)。&lt;br /&gt;
# 主机 B 以一个设置了 SYN 和 ACK 字段的 TCP 段进行响应，并通过将 A 的序列号加 1 来确认（ACK=101）。同样，B 也生成一个序列号（300）。&lt;br /&gt;
# 三次握手以连接请求发起方 (A) 发送的确认 (ACK) 结束。确认字段反映了主机接下来期望从另一方收到的序列号。&lt;br /&gt;
# 数据开始流动，主要是因为双方都确认了对方的数据序号。&lt;br /&gt;
&lt;br /&gt;
这是建立 TCP 连接的常见场景。然而，由此会产生几个问题：&lt;br /&gt;
&lt;br /&gt;
# 初始序列号是如何选择的？&lt;br /&gt;
# 如果双方同时向对方提出连接请求呢？&lt;br /&gt;
# 如果某些环节延迟一段时间甚至无限期延迟怎么办？&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;初始序列号&#039;&#039; (ISN) 由通信双方在首次连接时独立选择。由于它是识别连接的关键部分，因此必须选择尽可能唯一且不易猜测的序列号。事实上， &#039;&#039;TCP 序列号攻击&#039;&#039;  指的是攻击者可以复制 TCP 连接并有效地向目标发送数据，从而冒充可信主机的情况。&lt;br /&gt;
&lt;br /&gt;
The original specification suggests that the ISN is chosen by a counter that increments every 4 microseconds. This, however, can be guessed by an attacker. In reality, modern networking stacks generate the ISN by more complicated methods.&lt;br /&gt;
&lt;br /&gt;
原始规范表明，ISN 由一个每 4 微秒递增一次的计数器生成。然而，攻击者可以猜到这一点。实际上，现代网络协议栈采用更复杂的方法生成 ISN。&lt;br /&gt;
&lt;br /&gt;
当两端都收到对方的连接请求（SYN）时，这种情况称为&#039;&#039;同时打开&#039;&#039; 。TCP 握手过程中会通过额外的消息交换来解决这个问题：双方都发送一个 ACK（彼此并不知道对方是否也发送了 ACK），然后双方都发送 SYN-ACK 来确认请求。之后，数据传输开始。&lt;br /&gt;
&lt;br /&gt;
最后，TCP 实现必须包含一个计时器，用于判断何时放弃建立连接。系统会尝试重新建立连接，通常采用指数退避算法，但一旦达到最大重试次数或时间阈值，连接就会被视为已断开。&lt;br /&gt;
&lt;br /&gt;
= TCP 选项 =&lt;br /&gt;
TCP 报头段的最后一个字段保留用于存储 TCP 选项。最初的规范提供了三个选项，但后来的规范又增加了更多选项。接下来，我们将介绍最常用的选项。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;最大段大小&#039;&#039; (MSS) 选项指定 TCP 实现能够接收的最大 TCP 段大小。在 IPv4 中，该值的典型值为 1460 字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;选择性确认&#039;&#039; (SACK) 选项优化了传输过程中大量数据包丢失、接收端数据窗口出现“空洞”的情况。为了弥补由此导致的吞吐量下降，TCP 实现可以通过 SACK 通知发送方哪些数据包未收到。因此，与累积确认机制相比，发送方能够以更直接的方式获取数据状态信息。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;窗口缩放&#039;&#039;选项可以增大原本受限的 16 位窗口大小。具体来说，如果双方都在握手段中包含此选项，则窗口大小将乘以该缩放比例。更大的窗口大小对于大批量数据传输尤为重要。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;时间戳&#039;&#039;选项允许发送方在 TCP 数据段中插入时间戳，该时间戳可用于计算每个 ACK​​ 数据段的往返时间 (RTT)。此信息随后可用于计算 TCP 重传超时时间。&lt;br /&gt;
&lt;br /&gt;
= 测试TCP握手 =&lt;br /&gt;
现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:&lt;br /&gt;
[[文件:屏幕截图 2025-12-25 225216.png|居中|缩略图|627x627像素]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
由于nmap进行SYN扫描(它只等待 SYN-ACK 来判断目标端口是否打开),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。&lt;br /&gt;
&lt;br /&gt;
= 关键实现 =&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct net_ops tcp_ops = {&lt;br /&gt;
    .alloc_sock = &amp;amp;tcp_alloc_sock,&lt;br /&gt;
    .init = &amp;amp;tcp_v4_init_sock,&lt;br /&gt;
    .connect = &amp;amp;tcp_v4_connect,&lt;br /&gt;
    .disconnect = &amp;amp;tcp_disconnect,&lt;br /&gt;
    .write = &amp;amp;tcp_write,&lt;br /&gt;
    .read = &amp;amp;tcp_read,&lt;br /&gt;
    .recv_notify = &amp;amp;tcp_recv_notify,&lt;br /&gt;
    .close = &amp;amp;tcp_close,&lt;br /&gt;
    .abort = &amp;amp;tcp_abort,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
void tcp_init()&lt;br /&gt;
{&lt;br /&gt;
}&lt;br /&gt;
static void tcp_init_segment(struct tcphdr *th, struct iphdr *ih, struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    th-&amp;gt;sport = ntohs(th-&amp;gt;sport);&lt;br /&gt;
    th-&amp;gt;dport = ntohs(th-&amp;gt;dport);&lt;br /&gt;
    th-&amp;gt;seq = ntohl(th-&amp;gt;seq);&lt;br /&gt;
    th-&amp;gt;ack_seq = ntohl(th-&amp;gt;ack_seq);&lt;br /&gt;
    th-&amp;gt;win = ntohs(th-&amp;gt;win);&lt;br /&gt;
    th-&amp;gt;csum = ntohs(th-&amp;gt;csum);&lt;br /&gt;
    th-&amp;gt;urp = ntohs(th-&amp;gt;urp);&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;seq = th-&amp;gt;seq;&lt;br /&gt;
    skb-&amp;gt;dlen = ip_len(ih) - tcp_hlen(th);&lt;br /&gt;
    skb-&amp;gt;len = skb-&amp;gt;dlen + th-&amp;gt;syn + th-&amp;gt;fin;&lt;br /&gt;
    skb-&amp;gt;end_seq = skb-&amp;gt;seq + skb-&amp;gt;dlen;&lt;br /&gt;
    skb-&amp;gt;payload = th-&amp;gt;data;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void tcp_clear_queues(struct tcp_sock *tsk) {&lt;br /&gt;
    skb_queue_free(&amp;amp;tsk-&amp;gt;ofo_queue);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_in(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk;&lt;br /&gt;
    struct iphdr *iph;&lt;br /&gt;
    struct tcphdr *th;&lt;br /&gt;
&lt;br /&gt;
    iph = ip_hdr(skb);&lt;br /&gt;
    th = (struct tcphdr*) iph-&amp;gt;data;&lt;br /&gt;
&lt;br /&gt;
    tcp_init_segment(th, iph, skb);&lt;br /&gt;
    &lt;br /&gt;
    sk = inet_lookup(skb, th-&amp;gt;sport, th-&amp;gt;dport);&lt;br /&gt;
&lt;br /&gt;
    if (sk == NULL) {&lt;br /&gt;
        print_err(&amp;quot;No TCP socket for sport %d dport %d\n&amp;quot;,&lt;br /&gt;
                  th-&amp;gt;sport, th-&amp;gt;dport);&lt;br /&gt;
        free_skb(skb);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
&lt;br /&gt;
    tcp_in_dbg(th, sk, skb);&lt;br /&gt;
    /* if (tcp_checksum(iph, th) != 0) { */&lt;br /&gt;
    /*     goto discard; */&lt;br /&gt;
    /* } */&lt;br /&gt;
    tcp_input_state(sk, th, skb);&lt;br /&gt;
&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_udp_checksum(uint32_t saddr, uint32_t daddr, uint8_t proto,&lt;br /&gt;
                     uint8_t *data, uint16_t len)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t sum = 0;&lt;br /&gt;
&lt;br /&gt;
    sum += saddr;&lt;br /&gt;
    sum += daddr;&lt;br /&gt;
    sum += htons(proto);&lt;br /&gt;
    sum += htons(len);&lt;br /&gt;
    &lt;br /&gt;
    return checksum(data, len, sum);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_checksum(struct sk_buff *skb, uint32_t saddr, uint32_t daddr)&lt;br /&gt;
{&lt;br /&gt;
    return tcp_udp_checksum(saddr, daddr, IP_TCP, skb-&amp;gt;data, skb-&amp;gt;len);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
struct sock *tcp_alloc_sock()&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = malloc(sizeof(struct tcp_sock));&lt;br /&gt;
&lt;br /&gt;
    memset(tsk, 0, sizeof(struct tcp_sock));&lt;br /&gt;
    tsk-&amp;gt;sk.state = TCP_CLOSE;&lt;br /&gt;
    tsk-&amp;gt;sackok = 1;&lt;br /&gt;
    &lt;br /&gt;
    tsk-&amp;gt;rmss = 1460;&lt;br /&gt;
    // Default to 536 as per spec&lt;br /&gt;
    tsk-&amp;gt;smss = 536;&lt;br /&gt;
&lt;br /&gt;
    skb_queue_init(&amp;amp;tsk-&amp;gt;ofo_queue);&lt;br /&gt;
    &lt;br /&gt;
    return (struct sock *)tsk;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_init_sock(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    tcp_init_sock(sk);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_init_sock(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void __tcp_set_state(struct sock *sk, uint32_t state)&lt;br /&gt;
{&lt;br /&gt;
    sk-&amp;gt;state = state;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static uint16_t generate_port()&lt;br /&gt;
{&lt;br /&gt;
    /* TODO: Generate a proper port */&lt;br /&gt;
    static int port = 40000;&lt;br /&gt;
&lt;br /&gt;
    pthread_rwlock_wrlock(&amp;amp;tcplock);&lt;br /&gt;
    int copy =  ++port + (timer_get_tick() % 10000);&lt;br /&gt;
    pthread_rwlock_unlock(&amp;amp;tcplock);&lt;br /&gt;
&lt;br /&gt;
    return copy;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int generate_iss()&lt;br /&gt;
{&lt;br /&gt;
    /* TODO: Generate a proper ISS */&lt;br /&gt;
    return (int)time(NULL) * rand();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_v4_connect(struct sock *sk, const struct sockaddr *addr, int addrlen, int flags)&lt;br /&gt;
{&lt;br /&gt;
    uint16_t dport = ((struct sockaddr_in *)addr)-&amp;gt;sin_port;&lt;br /&gt;
    uint32_t daddr = ((struct sockaddr_in *)addr)-&amp;gt;sin_addr.s_addr;&lt;br /&gt;
&lt;br /&gt;
    sk-&amp;gt;dport = ntohs(dport);&lt;br /&gt;
    sk-&amp;gt;sport = generate_port();&lt;br /&gt;
    sk-&amp;gt;daddr = ntohl(daddr);&lt;br /&gt;
    /* TODO: Do not hardcode lvl-ip local interface */&lt;br /&gt;
    sk-&amp;gt;saddr = parse_ipv4_string(&amp;quot;10.0.0.4&amp;quot;); &lt;br /&gt;
&lt;br /&gt;
    return tcp_connect(sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_disconnect(struct sock *sk, int flags)&lt;br /&gt;
{&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_write(struct sock *sk, const void *buf, int len)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    int ret = sk-&amp;gt;err;&lt;br /&gt;
&lt;br /&gt;
    if (ret != 0) goto out;&lt;br /&gt;
&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        break;&lt;br /&gt;
    default:&lt;br /&gt;
        ret = -EBADF;&lt;br /&gt;
        goto out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return tcp_send(tsk, buf, len);    &lt;br /&gt;
&lt;br /&gt;
out: &lt;br /&gt;
    return ret;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_read(struct sock *sk, void *buf, int len)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    int ret = -1;&lt;br /&gt;
&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE:&lt;br /&gt;
        ret = -EBADF;&lt;br /&gt;
        goto out;&lt;br /&gt;
    case TCP_LISTEN:&lt;br /&gt;
    case TCP_SYN_SENT:&lt;br /&gt;
    case TCP_SYN_RECEIVED:&lt;br /&gt;
        /* Queue for processing after entering ESTABLISHED state.  If there&lt;br /&gt;
           is no room to queue this request, respond with &amp;quot;error:&lt;br /&gt;
           insufficient resources&amp;quot;. */&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
    case TCP_FIN_WAIT_1:&lt;br /&gt;
    case TCP_FIN_WAIT_2:&lt;br /&gt;
        /* If insufficient incoming segments are queued to satisfy the&lt;br /&gt;
           request, queue the request. */&lt;br /&gt;
        &lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        /* If no text is awaiting delivery, the RECEIVE will get a&lt;br /&gt;
           &amp;quot;error:  connection closing&amp;quot; response.  Otherwise, any remaining&lt;br /&gt;
           text can be used to satisfy the RECEIVE. */&lt;br /&gt;
        if (!skb_queue_empty(&amp;amp;tsk-&amp;gt;sk.receive_queue)) break;&lt;br /&gt;
        if (tsk-&amp;gt;flags &amp;amp; TCP_FIN) {&lt;br /&gt;
            tsk-&amp;gt;flags &amp;amp;= ~TCP_FIN;&lt;br /&gt;
            return 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSING:&lt;br /&gt;
    case TCP_LAST_ACK:&lt;br /&gt;
    case TCP_TIME_WAIT:&lt;br /&gt;
        ret = sk-&amp;gt;err;&lt;br /&gt;
        goto out;&lt;br /&gt;
    default:&lt;br /&gt;
        goto out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return tcp_receive(tsk, buf, len);    &lt;br /&gt;
&lt;br /&gt;
out: &lt;br /&gt;
    return ret;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_recv_notify(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    if (&amp;amp;(sk-&amp;gt;recv_wait)) {&lt;br /&gt;
        return wait_wakeup(&amp;amp;sk-&amp;gt;recv_wait);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // No recv wait lock&lt;br /&gt;
    return -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_close(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE:&lt;br /&gt;
    case TCP_CLOSING:&lt;br /&gt;
    case TCP_LAST_ACK:&lt;br /&gt;
    case TCP_TIME_WAIT:&lt;br /&gt;
    case TCP_FIN_WAIT_1:&lt;br /&gt;
    case TCP_FIN_WAIT_2:&lt;br /&gt;
        /* Respond with &amp;quot;error:  connection closing&amp;quot;. */&lt;br /&gt;
        sk-&amp;gt;err = -EBADF;&lt;br /&gt;
        return -1;&lt;br /&gt;
    case TCP_LISTEN:&lt;br /&gt;
    case TCP_SYN_SENT:&lt;br /&gt;
    case TCP_SYN_RECEIVED:&lt;br /&gt;
        return tcp_done(sk);&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
        /* Queue this until all preceding SENDs have been segmentized, then&lt;br /&gt;
           form a FIN segment and send it.  In any case, enter FIN-WAIT-1&lt;br /&gt;
           state. */&lt;br /&gt;
        tcp_set_state(sk, TCP_FIN_WAIT_1);&lt;br /&gt;
        tcp_queue_fin(sk);&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        /* Queue this request until all preceding SENDs have been&lt;br /&gt;
           segmentized; then send a FIN segment, enter LAST_ACK state. */&lt;br /&gt;
        tcp_queue_fin(sk);&lt;br /&gt;
        break;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;Unknown TCP state for close\n&amp;quot;);&lt;br /&gt;
        return -1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_abort(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcp_send_reset(tsk);&lt;br /&gt;
    return tcp_done(sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static int tcp_free(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    tcp_clear_timers(sk);&lt;br /&gt;
    tcp_clear_queues(tsk);&lt;br /&gt;
&lt;br /&gt;
    wait_wakeup(&amp;amp;sk-&amp;gt;sock-&amp;gt;sleep);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_done(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    tcp_set_state(sk, TCP_CLOSING);&lt;br /&gt;
    tcp_free(sk);&lt;br /&gt;
    return socket_delete(sk-&amp;gt;sock);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_clear_timers(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcp_stop_rto_timer(tsk);&lt;br /&gt;
    tcp_stop_delack_timer(tsk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;keepalive);&lt;br /&gt;
    tsk-&amp;gt;keepalive = NULL;&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_stop_rto_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk) {&lt;br /&gt;
        timer_cancel(tsk-&amp;gt;retransmit);&lt;br /&gt;
        tsk-&amp;gt;retransmit = NULL;&lt;br /&gt;
        tsk-&amp;gt;backoff = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_release_rto_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk) {&lt;br /&gt;
        timer_release(tsk-&amp;gt;retransmit);&lt;br /&gt;
        tsk-&amp;gt;retransmit = NULL;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_stop_delack_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;delack);&lt;br /&gt;
    tsk-&amp;gt;delack = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_release_delack_timer(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    timer_release(tsk-&amp;gt;delack);&lt;br /&gt;
    tsk-&amp;gt;delack = NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_handle_fin_state(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    switch (sk-&amp;gt;state) {&lt;br /&gt;
    case TCP_CLOSE_WAIT:&lt;br /&gt;
        tcp_set_state(sk, TCP_LAST_ACK);&lt;br /&gt;
        break;&lt;br /&gt;
    case TCP_ESTABLISHED:&lt;br /&gt;
        tcp_set_state(sk, TCP_FIN_WAIT_1);&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void *tcp_linger(void *arg)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk = (struct sock *) arg;&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcpsock_dbg(&amp;quot;TCP time-wait timeout, freeing TCB&amp;quot;, sk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
&lt;br /&gt;
    tcp_done(sk);&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
&lt;br /&gt;
    return NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
static void *tcp_user_timeout(void *arg)&lt;br /&gt;
{&lt;br /&gt;
    struct sock *sk = (struct sock *) arg;&lt;br /&gt;
    socket_wr_acquire(sk-&amp;gt;sock);&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
    tcpsock_dbg(&amp;quot;TCP user timeout, freeing TCB and aborting conn&amp;quot;, sk);&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    tsk-&amp;gt;linger = NULL;&lt;br /&gt;
&lt;br /&gt;
    tcp_abort(sk);&lt;br /&gt;
    socket_release(sk-&amp;gt;sock);&lt;br /&gt;
    &lt;br /&gt;
    return NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_enter_time_wait(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    tcp_set_state(sk, TCP_TIME_WAIT);&lt;br /&gt;
&lt;br /&gt;
    tcp_clear_timers(sk);&lt;br /&gt;
    /* RFC793 arbitrarily defines MSL to be 2 minutes */&lt;br /&gt;
    tsk-&amp;gt;linger = timer_add(TCP_2MSL, &amp;amp;tcp_linger, sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_rearm_user_timeout(struct sock *sk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sock *tsk = tcp_sk(sk);&lt;br /&gt;
&lt;br /&gt;
    if (sk-&amp;gt;state == TCP_TIME_WAIT) return;&lt;br /&gt;
&lt;br /&gt;
    timer_cancel(tsk-&amp;gt;linger);&lt;br /&gt;
    /* RFC793 set user timeout */&lt;br /&gt;
    tsk-&amp;gt;linger = timer_add(TCP_USER_TIMEOUT, &amp;amp;tcp_user_timeout, sk);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void tcp_rtt(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    if (tsk-&amp;gt;backoff &amp;gt; 0 || !tsk-&amp;gt;retransmit) {&lt;br /&gt;
        // Karn&#039;s Algorithm: Don&#039;t measure retransmissions&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int r = timer_get_tick() - (tsk-&amp;gt;retransmit-&amp;gt;expires - tsk-&amp;gt;rto);&lt;br /&gt;
    if (r &amp;lt; 0) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tsk-&amp;gt;srtt) {&lt;br /&gt;
        /* RFC6298 2.2 first measurement is made */&lt;br /&gt;
        tsk-&amp;gt;srtt = r;&lt;br /&gt;
        tsk-&amp;gt;rttvar = r / 2;&lt;br /&gt;
    } else {&lt;br /&gt;
        /* RFC6298 2.3 a subsequent measurement is made */&lt;br /&gt;
        double beta = 0.25;&lt;br /&gt;
        double alpha = 0.125;&lt;br /&gt;
        tsk-&amp;gt;rttvar = (1 - beta) * tsk-&amp;gt;rttvar + beta * abs(tsk-&amp;gt;srtt - r);&lt;br /&gt;
        tsk-&amp;gt;srtt = (1 - alpha) * tsk-&amp;gt;srtt + alpha * r;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int k = 4 * tsk-&amp;gt;rttvar;&lt;br /&gt;
&lt;br /&gt;
    /* RFC6298 says RTO should be at least 1 second. Linux uses 200ms */&lt;br /&gt;
    if (k &amp;lt; 200) k = 200;&lt;br /&gt;
&lt;br /&gt;
    tsk-&amp;gt;rto = tsk-&amp;gt;srtt + k;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int tcp_calculate_sacks(struct tcp_sock *tsk)&lt;br /&gt;
{&lt;br /&gt;
    struct tcp_sack_block *sb = &amp;amp;tsk-&amp;gt;sacks[tsk-&amp;gt;sacklen];&lt;br /&gt;
&lt;br /&gt;
    sb-&amp;gt;left = 0;&lt;br /&gt;
    sb-&amp;gt;right = 0;&lt;br /&gt;
&lt;br /&gt;
    struct sk_buff *next;&lt;br /&gt;
    struct list_head *item, *tmp;&lt;br /&gt;
&lt;br /&gt;
    list_for_each_safe(item, tmp, &amp;amp;tsk-&amp;gt;ofo_queue.head) {&lt;br /&gt;
        next = list_entry(item, struct sk_buff, list);&lt;br /&gt;
&lt;br /&gt;
        if (sb-&amp;gt;left == 0) {&lt;br /&gt;
            sb-&amp;gt;left = next-&amp;gt;seq;&lt;br /&gt;
            tsk-&amp;gt;sacklen++;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        if (sb-&amp;gt;right == 0) sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
        else if (sb-&amp;gt;right == next-&amp;gt;seq) sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
        else {&lt;br /&gt;
            if (tsk-&amp;gt;sacklen &amp;gt;= tsk-&amp;gt;sacks_allowed) break;&lt;br /&gt;
            &lt;br /&gt;
            sb = &amp;amp;tsk-&amp;gt;sacks[tsk-&amp;gt;sacklen];&lt;br /&gt;
            sb-&amp;gt;left = next-&amp;gt;seq;&lt;br /&gt;
            sb-&amp;gt;right = next-&amp;gt;end_seq;&lt;br /&gt;
            tsk-&amp;gt;sacklen++;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;该项目的源代码托管在 GitHub 上。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_225216.png&amp;diff=54</id>
		<title>文件:屏幕截图 2025-12-25 225216.png</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_225216.png&amp;diff=54"/>
		<updated>2025-12-25T14:52:54Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;测试截图&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_224135.png&amp;diff=53</id>
		<title>文件:屏幕截图 2025-12-25 224135.png</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_224135.png&amp;diff=53"/>
		<updated>2025-12-25T14:42:16Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;tcp握手流程&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_223342.png&amp;diff=52</id>
		<title>文件:屏幕截图 2025-12-25 223342.png</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_223342.png&amp;diff=52"/>
		<updated>2025-12-25T14:34:45Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;tcp报文头&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_222529.png&amp;diff=51</id>
		<title>文件:屏幕截图 2025-12-25 222529.png</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E6%96%87%E4%BB%B6:%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_2025-12-25_222529.png&amp;diff=51"/>
		<updated>2025-12-25T14:26:29Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;滑动窗口图&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=50</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=50"/>
		<updated>2025-12-24T10:50:04Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[编写一个TCP/IP栈2-IPv4和ICMPv4]]&lt;br /&gt;
* [[编写一个TCP/IP栈1-以太网和ARP]]&lt;br /&gt;
* [[构建自己的区块链|构建自己的区块链（python教程）]]&lt;br /&gt;
* [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]]&lt;br /&gt;
* [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=49</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=49"/>
		<updated>2025-12-24T10:48:31Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
[[编写一个TCP/IP栈3-TCP基础与握手|动手做系列：编写一个TCP/IP栈3-TCP基础与握手]]&lt;br /&gt;
&lt;br /&gt;
[[编写一个TCP/IP栈2-IPv4和ICMPv4|动手做系列：编写一个TCP/IP栈2-IPv4和ICMPv4]]&lt;br /&gt;
&lt;br /&gt;
[[编写一个TCP/IP栈1-以太网和ARP|动手做系列：编写一个TCP/IP栈1-以太网和ARP]]&lt;br /&gt;
&lt;br /&gt;
[[构建自己的区块链|动手做系列：构建自己的区块链]]&lt;br /&gt;
&lt;br /&gt;
[[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]]&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=48</id>
		<title>编写一个TCP/IP栈3-TCP基础与握手</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%883-TCP%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%8F%A1%E6%89%8B&amp;diff=48"/>
		<updated>2025-12-24T10:40:38Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人恐惧的传输控制协议(TCP)了。  &amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;&amp;#039;&amp;#039;transport&amp;#039;&amp;#039;在第四组OSI网络1层、传输端操作时,TCP负责修复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。  TCP &amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;并非全新的协议——第一个规范于1974…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人恐惧的传输控制协议(TCP)了。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;&#039;&#039;transport&#039;&#039;在第四组OSI网络1层、传输端操作时,TCP负责修复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。&lt;br /&gt;
&lt;br /&gt;
TCP &amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;并非全新的协议——第一个规范于1974年问世。从那以后,很多事情都发生了变化,TCP &amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;也获得了许多扩展和修正3。&lt;br /&gt;
&lt;br /&gt;
本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。&lt;br /&gt;
&lt;br /&gt;
= 可靠性机制 =&lt;br /&gt;
可靠地发送数据的问题看似肤浅,但其实际实现却涉及问题。主要涉及数据报式网络中的错误修复问题:&lt;br /&gt;
&lt;br /&gt;
* 发送方应等待接收方确认多长时间?&lt;br /&gt;
* 如果接收方无法按发送速度处理数据怎么办?&lt;br /&gt;
* 如果中间的网络(例如路由器)无法像发送数据时那样快速处理数据,该怎么办?&lt;br /&gt;
&lt;br /&gt;
在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。&lt;br /&gt;
&lt;br /&gt;
为解决这些问题,可以使用多种机制。&#039;&#039;sliding window&#039;&#039;最常见的可能是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:&lt;br /&gt;
&lt;br /&gt;
使用这种滑动窗口的便利之处在于,&#039;&#039;flow control&#039;&#039;它还能缓解流量控制问题。当接收器无法以发送速度快速处理数据时,需要进行流量控制。在这种情况下,滑动窗口的大小将被协商以降低,从而从发送方获得节流输出。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Congestion control&#039;&#039;另一方面,充血控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,&amp;lt;sup&amp;gt;相关研究至今仍在进行。&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= TCP基础 =&lt;br /&gt;
TCP 中的底层机制比其他协议(如 UDP 和 IP)要多得多。&#039;&#039;即TCP是一种面向连接的协议&#039;&#039;,这意味着作为第一步,在两边之间建立统一通信通道。双方正在积极维护这一联系:建立联系(握手),向对方告知数据状况及可能的问题。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;streaming&#039;&#039;TCP的另一个重要特性是它是一种流式协议。与UDP不同,TCP 无法保证应用程序在发送和接收时能够稳定地“记录”数据。TCP 实现必须缓冲数据,当数据包丢失、重新排序或损坏时,TCP 必须等待并组织缓冲区中的数据。只有当数据被认定为完整时,TCP 才能将数据交给应用程序的套接字。&lt;br /&gt;
&lt;br /&gt;
当TCP以数据流的形式运行时,来自该流的“块”必须转换为IP可以承载的数据包。&#039;&#039;这称为包化&#039;&#039;,其中TCP头块包含流中当前索引的序列号。这还具有方便的属性,即流可以分成许多可变大小的段,&#039;&#039;repacketize&#039;&#039;而TCP则知道如何重新集成它们。&lt;br /&gt;
&lt;br /&gt;
与IP类似,TCP也会检查消息是否具有完整性。这是通过与IP相同的校验和算法实现的,但增加了更多细节。主要情况下,校验和是端到端的,这意味着头像和数据都包含在校验中。此外,还包含一个由IP头编造的伪头。&lt;br /&gt;
&lt;br /&gt;
如果TCP实现接收损坏的片段,就会丢弃它们,并且不会通知发送方。此错误由发送方设置的定时器进行纠正,如果接收方从未承认该片段,则可用于重新传输片段。&lt;br /&gt;
&lt;br /&gt;
TCP &#039;&#039;full-duplex&#039;&#039;也是一个全双工系统,意味着流量可以双向同时流动。这意味着通信端必须将数据顺序保持在内存的两个方向。更深入地说,TCP 通过在发送的细分领域中包含对相反流量的识别来保护其流量占用空间。&lt;br /&gt;
&lt;br /&gt;
本质上,数据流的排序是TCP的主要原理。然而,保持同步的问题并不简单。&lt;br /&gt;
&lt;br /&gt;
= TCP 头像格式 =&lt;br /&gt;
接下来,我们将定义消息头并描述其字段。TCP 头似乎很简单,但包含了大量关于通信状态的信息。&lt;br /&gt;
&lt;br /&gt;
TCP 头部为 20 个 &amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt; 号&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Source PortDestination Port&#039;&#039;源端口和目的地端口字段用于建立来自主机和主机的多个连接。即,伯克利套接字是应用程序绑定到TCP网络栈的常见接口。通过端口,网络栈知道该将流量引导到哪里。由于字段大小为16位,因此端口值范围为0到65535。&lt;br /&gt;
&lt;br /&gt;
由于流中的每个字节都已编号,&#039;&#039;Sequence Number&#039;&#039;序列号表示TCP段的窗口索引。握手时,&#039;&#039;包含初始序列号(&#039;&#039;ISN)。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Acknowledgment Number&#039;&#039;已知号包含发送方期望接收的下一个字节的窗口索引。握手后,必须始终驻足处理ACK场地。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;头长&#039;&#039;(HL)字段以32位字表示头长。&lt;br /&gt;
&lt;br /&gt;
接着,会显示几面旗帜。前4位(&#039;&#039;rsvd&#039;&#039;)未使用。&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;降压窗口&#039;&#039;(C)用于告知发送方降低发送速率。&lt;br /&gt;
# &#039;&#039;ECN Echo&#039;&#039;(E)通知发送者已收到交通拥堵通知。&lt;br /&gt;
# &#039;&#039;紧急指针&#039;&#039;(U)表示该细分部分包含优先数据。&lt;br /&gt;
# &#039;&#039;ACK&#039;&#039;(A)字段用于传达TCP握手的状态。其余连接时仍会保持。&lt;br /&gt;
# &#039;&#039;PSH&#039;&#039;PSH(P)用于表示接收方应尽快将数据推送到应用程序。&lt;br /&gt;
# &#039;&#039;RST&#039;&#039;(R) 重置 TCP 连接。&lt;br /&gt;
# &#039;&#039;SYN&#039;&#039;(S)用于在初始握手中同步序列号。&lt;br /&gt;
# &#039;&#039;FIN&#039;&#039;(F) 表示发送方已发送数据。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Window Size&#039;&#039;窗口大小字段用于宣传窗口尺寸。换句话说,这是接收方愿意接受的字节数。由于它是一个16位字段,最大窗口大小为65,535字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;TCP Checksum&#039;&#039; 字段用于验证 TCP 段的完整性。该算法与互联网协议相同,但输入段也包含TCP数据,以及IP数据报的伪头。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Urgent Pointer&#039;&#039;紧急指针在设置U旗时使用。指针表示流中紧急数据的位置。&lt;br /&gt;
&lt;br /&gt;
头球后,可以提供多种选项。&#039;&#039;这些选项的一个示例是最大分段大小&#039;&#039;(MSS),其中发送方向另一端的片段最大尺寸提供信息。&lt;br /&gt;
&lt;br /&gt;
在可能的选项之后,实际数据随之而来。然而,这些数据并非必需。例如,仅使用TCP头完成握手。&lt;br /&gt;
&lt;br /&gt;
= TCP握手 =&lt;br /&gt;
TCP连接通常经历以下阶段:连接设置(握手)、数据传输以及连接的关闭。以下图表描述了TCP的惯常握手习惯:&lt;br /&gt;
&lt;br /&gt;
# 主机A的插座处于封闭状态,这意味着它不接受连接。相反,连接到特定端口的主机B的插座正在监听新的连接。&lt;br /&gt;
# 主机A计划与主机B发起连接。因此,A 会创建一个 TCP 段,其 SYN 标志集以及 Sequence 字段也填充了一个值(100)。&lt;br /&gt;
# 主机B使用具有SYN和ACK字段集的TCP段进行响应,并通过向其添加1(ACK=101)来确认A的序列号。同样,B 生成一个序列号(300)。&lt;br /&gt;
# 三方握手由连接请求的发起方(A)发送的ACK完成。确认字段反映了主机接下来期望从另一侧接收的序列号。&lt;br /&gt;
# 数据开始流动,主要是因为双方都承认了彼此的细分数据。&lt;br /&gt;
&lt;br /&gt;
这是建立TCP连接的常见场景。然而,出现了几个问题:&lt;br /&gt;
&lt;br /&gt;
# 初始序列号是如何选择的?&lt;br /&gt;
# 如果双方同时要求彼此连接会怎样?&lt;br /&gt;
# 如果片段被延迟一段时间或无限期地推迟了怎么办?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;初始序列号&#039;&#039;(ISN)由双方在初次联系时由双方独立选择。由于这是识别连接的关键部分,因此必须选择它,以便其最有可能独一无二且不易被轻易猜出。事实上,&#039;&#039;TCP Sequence Number Attack&#039;&#039;&amp;lt;sup&amp;gt;6&amp;lt;/sup&amp;gt;TCP序列号攻击6是指攻击者能够复制TCP连接并有效地将数据馈送到目标,冒充可信主机的情况。&lt;br /&gt;
&lt;br /&gt;
原始规范表明,ISN 是由一个计数器选择的,该计数器每4微秒递增一次。然而,攻击者可以猜测这一点。事实上,现代网络栈通过更复杂的方法生成ISN。&lt;br /&gt;
&lt;br /&gt;
两个端点相互接收连接请求(SYN)&#039;&#039;Simultaneous Open&#039;&#039;的情况称为同步开放。通过TCP握手中的额外消息交换来解决:双方发送ACK(但不知道对方也做到了),双方SYN-ACK请求均未确定。此后,数据传输开始。&lt;br /&gt;
&lt;br /&gt;
最后,TCP 实现必须有一个计时器,以便知道何时放弃建立连接。试图重新建立连接,通常具有指数级的回退,但一旦达到最高重试或时间阈值,该连接就被视为不存在。&lt;br /&gt;
&lt;br /&gt;
= TCP 选项 =&lt;br /&gt;
TCP 头端段的最后一个字段保留用于可能的 TCP 选项。原始规格提供了三种选择,但后续的规格又增加了更多。接下来,我们将介绍最常见的选项。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;最大分段大小&#039;&#039;(MSS)选项可告知TCP实现愿意接收的最大TCP段大小。IPv4 中的典型值为 1460 字节。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;选择性识别&#039;&#039;(SACK)选项可优化传输过程中丢失许多数据包,并在接收机的数据窗口中填充“漏洞”的情况。为了修复导致的吞吐量下降,TCP 实现可以向发送方告知未使用 SACK 接收到的特定数据包。因此,发送者比累积的已知方案更直接地接收到有关数据状态的信息。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Window Scale&#039;&#039;窗口缩放选项可增加有限的16位窗口大小。即,如果双方在握手部分包含此选项,则窗口大小会随此量表倍增加。更大的窗户尺寸对批量数据传输主要很重要。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Timestamps&#039;&#039;时间戳选项允许发送者将时间戳设置为TCP段,然后用于计算每个ACK段的RTT。此信息可用于计算TCP重传超时。&lt;br /&gt;
&lt;br /&gt;
= 测试TCP握手 =&lt;br /&gt;
现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:&lt;br /&gt;
&lt;br /&gt;
由于nmap进行SYN扫描(仅等待SYN-ACK决定目标端口是否开放),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。&lt;br /&gt;
&lt;br /&gt;
= 结论 =&lt;br /&gt;
只需选择一个序列号、设置SYN-ACK标志并计算生成的TCP段的校验和,即可实现最小可行的TCP握手程序,这相对轻松。&lt;br /&gt;
&lt;br /&gt;
下次,我们将探讨TCP最重要的责任:可靠的数据传输。管理流的窗口对于使用TCP传输数据至关重要,其逻辑可能会变得有些复杂。&lt;br /&gt;
&lt;br /&gt;
此外,&#039;&#039;sockets&#039;&#039;通过套接字为应用程序提供绑定到TCP实现的方法。因此,我们将研究伯克利Socket API,看看是否可以针对应用程序进行模拟,从而实现我们自定义的TCP实现。&lt;br /&gt;
&lt;br /&gt;
该项目的源代码托管在 GitHub 上。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=47</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=47"/>
		<updated>2025-12-24T10:38:03Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈3-TCP基础与握手|动手做系列：编写一个TCP/IP栈3-TCP基础与握手]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈2-IPv4和ICMPv4|动手做系列：编写一个TCP/IP栈2-IPv4和ICMPv4]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP|动手做系列：编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=46</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=46"/>
		<updated>2025-12-24T10:30:15Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[编写一个TCP/IP栈2-IPv4和ICMPv4]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=45</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=45"/>
		<updated>2025-12-24T10:29:20Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈2-IPv4和ICMPv4]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%882-IPv4%E5%92%8CICMPv4&amp;diff=44</id>
		<title>编写一个TCP/IP栈2-IPv4和ICMPv4</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%882-IPv4%E5%92%8CICMPv4&amp;diff=44"/>
		<updated>2025-12-24T10:27:45Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“此次在我们的用户空间TCP/IP堆栈中,我们将实现一个最小可行的IP层,&amp;#039;&amp;#039;并使用ICMP的回声请求(&amp;#039;&amp;#039;也称为pings)进行测试。  我们将查看IPv4和ICMPv4的格式,并介绍如何检查其完整性。一些功能,例如IP分片,作为练习。  对于我们的网络栈, 选择IPv4 优先于 IPv6,因为它仍然是互联网的默认网络协议。然而,未来我们的网络栈可以通过IPv6进行扩展。  = 互联网协议版本4 =…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;此次在我们的用户空间TCP/IP堆栈中,我们将实现一个最小可行的IP层,&#039;&#039;并使用ICMP的回声请求(&#039;&#039;也称为pings)进行测试。&lt;br /&gt;
&lt;br /&gt;
我们将查看IPv4和ICMPv4的格式,并介绍如何检查其完整性。一些功能,例如IP分片,作为练习。&lt;br /&gt;
&lt;br /&gt;
对于我们的网络栈, 选择IPv4 优先于 IPv6,因为它仍然是互联网的默认网络协议。然而,未来我们的网络栈可以通过IPv6进行扩展。&lt;br /&gt;
&lt;br /&gt;
= 互联网协议版本4 =&lt;br /&gt;
在实现以太网帧之后,下一层(L3)负责将数据传输到目标。&#039;&#039;即互联网协议&#039;&#039;(IP) 是为TCP和UDP等传输协议提供基础而发明的。它是无连接的,这意味着与TCP不同,所有数据报在网络堆栈中都是相互独立处理的。这也意味着IP数据报可能会丢失或者过期&amp;lt;sup&amp;gt;。&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
此外, IP 不保证成功交付。这是协议设计者有意识地选择的,因为IP旨在为同样无法保证交付的协议提供基础。UDP就是这样一个协议。&lt;br /&gt;
&lt;br /&gt;
如果需要通信方之间的可靠性,则在IP之上使用TCP等协议。在这种情况下,更高级别的协议负责检测缺失的数据, 并确保所有数据均已送达。&lt;br /&gt;
&lt;br /&gt;
== 头部格式 ==&lt;br /&gt;
IPv4 头通常长度为 20 个 八位。头部可以包含尾随选项,但在我们的实现中会省略这些选项。字段的含义相对简单,可以描述为一个C结构体:&lt;br /&gt;
&lt;br /&gt;
4位&amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;字段表示互联网头的格式。在我们的情况下,IPv4 的值为 4。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
struct iphdr {&lt;br /&gt;
    uint8_t version : 4;&lt;br /&gt;
    uint8_t ihl : 4;&lt;br /&gt;
    uint8_t tos;&lt;br /&gt;
    uint16_t len;&lt;br /&gt;
    uint16_t id;&lt;br /&gt;
    uint16_t flags : 3;&lt;br /&gt;
    uint16_t frag_offset : 13;&lt;br /&gt;
    uint8_t ttl;&lt;br /&gt;
    uint8_t proto;&lt;br /&gt;
    uint16_t csum;&lt;br /&gt;
    uint32_t saddr;&lt;br /&gt;
    uint32_t daddr;&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&#039;&#039;I&#039;&#039;互联网头长字段&amp;lt;code&amp;gt;ihl&amp;lt;/code&amp;gt;长度同样为4位,&#039;&#039;words&#039;&#039;表示IP头中32位字数。由于字段大小为 4 位,因此最大值只能为 15。因此,IP头的最大长度为60个八位。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;服务字段的类型&#039;&#039;&amp;lt;code&amp;gt;tos&amp;lt;/code&amp;gt;源自第一个IP规范。在后续规格中,它已被划分为更小的字段,但为了简单起见,我们将按照原始规范中定义的字段进行处理。该字段传达了用于IP数据报的服务质量。&lt;br /&gt;
&lt;br /&gt;
全长场&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;传达整个IP数据报的长度。由于是16位字段,因此最大长度为65535字节。大型IP数据报存在碎片化现象,在这些数据报中,它们被分割成更小的数据报,以满足不同通信接口的最大传输单元(MTU)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;字段用于索引数据报,最终用于重新组装碎片化的IP数据报。字段的值只是一个由发送方递增的计数器。接收方通过该字段知道如何对传入的分片数据排序。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;flags&amp;lt;/code&amp;gt;字段定义了数据报的各种控制标志。具体而言,发送方可以指定数据报是否允许分片,是最后分片数据还是更多分片数据进入。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;分片偏移字段&#039;&#039;&amp;lt;code&amp;gt;frag_offset&amp;lt;/code&amp;gt;在数据报中表示片段的位置。当然,第一个数据报将该索引设置为0。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ttl&amp;lt;/code&amp;gt;一个常用的属性,用来计算数据报的生命周期。通常由原始发送方设置为64,每个接收方都会逐一声明此计数器。当数据报降至零时,数据报将被丢弃, 并可能回复ICMP消息以表示错误。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;proto&amp;lt;/code&amp;gt;字段为数据图提供了在其有效载荷中携带其他协议的固有能力。该字段通常包含16(UDP)或6(TCP)等值,仅用于将实际数据类型传达给接收方。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;csum&amp;lt;/code&amp;gt;用于验证IP头的完整性。其算法相对简单,将在本教程中进一步说明。&lt;br /&gt;
&lt;br /&gt;
最后,&amp;lt;code&amp;gt;saddr&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;和daddr&amp;lt;/code&amp;gt;字段分别表示数据报的源地址和目标地址。尽管这些字段长度为32位,因此提供了约45亿个地址,但地址范围将在不久的将来耗尽。IPv6协议将这一长度延长至128位,因此未来能够支持更大范围的互联网协议的地址范围。&lt;br /&gt;
&lt;br /&gt;
== 校验和 ==&lt;br /&gt;
使用校验和字段来检查IP数据报的完整性。&lt;br /&gt;
&lt;br /&gt;
算法的实际代码如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
uint16_t checksum(void *addr, int count)&lt;br /&gt;
{&lt;br /&gt;
    /* Compute Internet Checksum for &amp;quot;count&amp;quot; bytes&lt;br /&gt;
     *         beginning at location &amp;quot;addr&amp;quot;.&lt;br /&gt;
     * Taken from https://tools.ietf.org/html/rfc1071&lt;br /&gt;
     */&lt;br /&gt;
&lt;br /&gt;
    register uint32_t sum = 0;&lt;br /&gt;
    uint16_t * ptr = addr;&lt;br /&gt;
&lt;br /&gt;
    while( count &amp;gt; 1 )  {&lt;br /&gt;
        /*  This is the inner loop */&lt;br /&gt;
        sum += * ptr++;&lt;br /&gt;
        count -= 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*  Add left-over byte, if any */&lt;br /&gt;
    if( count &amp;gt; 0 )&lt;br /&gt;
        sum += * (uint8_t *) ptr;&lt;br /&gt;
&lt;br /&gt;
    /*  Fold 32-bit sum to 16 bits */&lt;br /&gt;
    while (sum&amp;gt;&amp;gt;16)&lt;br /&gt;
        sum = (sum &amp;amp; 0xffff) + (sum &amp;gt;&amp;gt; 16);&lt;br /&gt;
&lt;br /&gt;
    return ~sum;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;以IP头为例&amp;lt;code&amp;gt;45 00 00 54 41 e0 40 00 40 01 00 00 0a 00 00 04 0a 00 00 05&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# 将字段加在一起,使两者的互补之数产生&amp;lt;code&amp;gt;01 1b 3e&amp;lt;/code&amp;gt;。&lt;br /&gt;
# 然后,为了将其转换为其互补:&amp;lt;code&amp;gt;1b 3e&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;01&amp;lt;/code&amp;gt;=&amp;lt;code&amp;gt;1b 3f&amp;lt;/code&amp;gt;。&lt;br /&gt;
# 最后,取一个互补的值,从而得出了校对值&amp;lt;code&amp;gt;e4c0&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
IP头&amp;lt;code&amp;gt;45 00 00 54 41 e0 40 00 40 01 e4 c0 0a 00 00 04 0a 00 00 05&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
可以通过再次应用算法来验证校验,如果结果为0,数据很可能就好了。&lt;br /&gt;
&lt;br /&gt;
= 互联网控制消息协议版本（ICMP协议） =&lt;br /&gt;
由于互联网协议缺乏可靠性机制,因此需要通过某种方式向各方通报可能的错误情况。因此,&#039;&#039;互联网控制消息协议&#039;&#039;(ICMP) 被用于网络中的诊断措施。一个例子是网关无法访问的情况——识别此功能的网络堆栈会将ICMP“Gateway Unreachable”消息发送回源。&lt;br /&gt;
&lt;br /&gt;
== 头部格式 ==&lt;br /&gt;
ICMP 头位于相应 IP 数据包的有效载荷中。ICMPv4 头标的结构如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4 {&lt;br /&gt;
    uint8_t type;&lt;br /&gt;
    uint8_t code;&lt;br /&gt;
    uint16_t csum;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在这里,&amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt;字段发送消息的类型。该字段预留了42个值,仅使用约8个常用值。在这里的实现中,使用了类型0(回声回复)、3(目的地不可访问)和8型(回声请求)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;code&amp;lt;/code&amp;gt;字段进一步描述了消息的含义。例如,当类型为3(目的地不可访问)时,代码字段意味着原因。常见错误是数据包无法路由到网络时:源主机很可能接收到带有第3类代码和代码0(Net Unreachable)的ICMP消息。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;csum&amp;lt;/code&amp;gt;字段与 IPv4 头字段相同,并且可以使用相同的算法来计算它。然而,在ICMPv4中,&#039;&#039;校验和是端到端的&#039;&#039;,这意味着在计算校验和时也包含有效载荷。&lt;br /&gt;
&lt;br /&gt;
== 消息及其处理 ==&lt;br /&gt;
实际的ICMP有效载荷包括查询/信息消息和错误消息。首先,我们将查看 Echo 请求/回复消息,通常称为网络中的“ping”:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4_echo {&lt;br /&gt;
    uint16_t id;&lt;br /&gt;
    uint16_t seq;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;消息格式很紧凑。字段&amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;由发送主机设置,以确定回声回复的处理过程。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;字段seq&amp;lt;/code&amp;gt;是响应的序列号,它只是一个从零开始的数值,每当出现新的响应请求时,它就会逐一递增。用于检测在传输过程中是否出现响应消息消失或被重新排序。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;字段是可选的,但通常包含像响应的时间戳这样的信息。然后可用于估算主机之间的往返时间。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Destination Unreachable&#039;&#039;最常见的ICMPv4错误消息“目的地无法访问”具有以下格式:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4_dst_unreachable {&lt;br /&gt;
    uint8_t unused;&lt;br /&gt;
    uint8_t len;&lt;br /&gt;
    uint16_t var;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;第一个octet 未使用。然后&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;字段表示原始数据报的长度,在 IPv4 的 4 个octet 中。2-octet 字段的值&amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;取决于ICMP代码。&lt;br /&gt;
&lt;br /&gt;
最后,尽可能将导致“目的地无法到达状态”的原始IP数据包放入&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;字段&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
= 测试实现 =&lt;br /&gt;
从 shell 中,我们可以验证我们的用户空间网络堆栈是否响应 ICMP 的回波请求:&lt;br /&gt;
&lt;br /&gt;
= 关键代码实现 =&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void icmpv4_incoming(struct sk_buff *skb) &lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *iphdr = ip_hdr(skb);&lt;br /&gt;
    struct icmp_v4 *icmp = (struct icmp_v4 *) iphdr-&amp;gt;data;&lt;br /&gt;
&lt;br /&gt;
    //TODO: Check csum&lt;br /&gt;
&lt;br /&gt;
    switch (icmp-&amp;gt;type) {&lt;br /&gt;
    case ICMP_V4_ECHO:&lt;br /&gt;
        icmpv4_reply(skb);&lt;br /&gt;
        return;&lt;br /&gt;
    case ICMP_V4_DST_UNREACHABLE:&lt;br /&gt;
        print_err(&amp;quot;ICMPv4 received &#039;dst unreachable&#039; code %d, &amp;quot;&lt;br /&gt;
                  &amp;quot;check your routes and firewall rules\n&amp;quot;, icmp-&amp;gt;code);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;ICMPv4 did not match supported types\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
drop_pkt:&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void icmpv4_reply(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *iphdr = ip_hdr(skb);&lt;br /&gt;
    struct icmp_v4 *icmp;&lt;br /&gt;
    struct sock sk;&lt;br /&gt;
    memset(&amp;amp;sk, 0, sizeof(struct sock));&lt;br /&gt;
    &lt;br /&gt;
    uint16_t icmp_len = iphdr-&amp;gt;len - (iphdr-&amp;gt;ihl * 4);&lt;br /&gt;
&lt;br /&gt;
    skb_reserve(skb, ETH_HDR_LEN + IP_HDR_LEN + icmp_len);&lt;br /&gt;
    skb_push(skb, icmp_len);&lt;br /&gt;
    &lt;br /&gt;
    icmp = (struct icmp_v4 *)skb-&amp;gt;data;&lt;br /&gt;
        &lt;br /&gt;
    icmp-&amp;gt;type = ICMP_V4_REPLY;&lt;br /&gt;
    icmp-&amp;gt;csum = 0;&lt;br /&gt;
    icmp-&amp;gt;csum = checksum(icmp, icmp_len, 0);&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;protocol = ICMPV4;&lt;br /&gt;
    sk.daddr = iphdr-&amp;gt;saddr;&lt;br /&gt;
&lt;br /&gt;
    ip_output(&amp;amp;sk, skb);&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;IP报文的接收&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int ip_rcv(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *ih = ip_hdr(skb);&lt;br /&gt;
    uint16_t csum = -1;&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;version != IPV4) {&lt;br /&gt;
        print_err(&amp;quot;Datagram version was not IPv4\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;ihl &amp;lt; 5) {&lt;br /&gt;
        print_err(&amp;quot;IPv4 header length must be at least 5\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;ttl == 0) {&lt;br /&gt;
        //TODO: Send ICMP error&lt;br /&gt;
        print_err(&amp;quot;Time to live of datagram reached 0\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    csum = checksum(ih, ih-&amp;gt;ihl * 4, 0);&lt;br /&gt;
&lt;br /&gt;
    if (csum != 0) {&lt;br /&gt;
        // Invalid checksum, drop packet handling&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // TODO: Check fragmentation, possibly reassemble&lt;br /&gt;
&lt;br /&gt;
    ip_init_pkt(ih);&lt;br /&gt;
&lt;br /&gt;
    ip_dbg(&amp;quot;in&amp;quot;, ih);&lt;br /&gt;
&lt;br /&gt;
    switch (ih-&amp;gt;proto) {&lt;br /&gt;
    case ICMPV4:&lt;br /&gt;
        icmpv4_incoming(skb);&lt;br /&gt;
        return 0;&lt;br /&gt;
    case IP_TCP:&lt;br /&gt;
        tcp_in(skb);&lt;br /&gt;
        return 0;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;Unknown IP header proto\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
drop_pkt:&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;IP报文发送：&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void ip_send_check(struct iphdr *ihdr)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t csum = checksum(ihdr, ihdr-&amp;gt;ihl * 4, 0);&lt;br /&gt;
    ihdr-&amp;gt;csum = csum;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int ip_output(struct sock *sk, struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct rtentry *rt;&lt;br /&gt;
    struct iphdr *ihdr = ip_hdr(skb);&lt;br /&gt;
&lt;br /&gt;
    rt = route_lookup(sk-&amp;gt;daddr);&lt;br /&gt;
&lt;br /&gt;
    if (!rt) {&lt;br /&gt;
        // TODO: dest_unreachable&lt;br /&gt;
        print_err(&amp;quot;IP output route lookup fail\n&amp;quot;);&lt;br /&gt;
        return -1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;dev = rt-&amp;gt;dev;&lt;br /&gt;
    skb-&amp;gt;rt = rt;&lt;br /&gt;
&lt;br /&gt;
    skb_push(skb, IP_HDR_LEN);&lt;br /&gt;
&lt;br /&gt;
    ihdr-&amp;gt;version = IPV4;&lt;br /&gt;
    ihdr-&amp;gt;ihl = 0x05;&lt;br /&gt;
    ihdr-&amp;gt;tos = 0;&lt;br /&gt;
    ihdr-&amp;gt;len = skb-&amp;gt;len;&lt;br /&gt;
    ihdr-&amp;gt;id = ihdr-&amp;gt;id;&lt;br /&gt;
    ihdr-&amp;gt;frag_offset = 0x4000;&lt;br /&gt;
    ihdr-&amp;gt;ttl = 64;&lt;br /&gt;
    ihdr-&amp;gt;proto = skb-&amp;gt;protocol;&lt;br /&gt;
    ihdr-&amp;gt;saddr = skb-&amp;gt;dev-&amp;gt;addr;&lt;br /&gt;
    ihdr-&amp;gt;daddr = sk-&amp;gt;daddr;&lt;br /&gt;
    ihdr-&amp;gt;csum = 0;&lt;br /&gt;
&lt;br /&gt;
    ip_dbg(&amp;quot;out&amp;quot;, ihdr);&lt;br /&gt;
&lt;br /&gt;
    ihdr-&amp;gt;len = htons(ihdr-&amp;gt;len);&lt;br /&gt;
    ihdr-&amp;gt;id = htons(ihdr-&amp;gt;id);&lt;br /&gt;
    ihdr-&amp;gt;daddr = htonl(ihdr-&amp;gt;daddr);&lt;br /&gt;
    ihdr-&amp;gt;saddr = htonl(ihdr-&amp;gt;saddr);&lt;br /&gt;
    ihdr-&amp;gt;csum = htons(ihdr-&amp;gt;csum);&lt;br /&gt;
    ihdr-&amp;gt;frag_offset = htons(ihdr-&amp;gt;frag_offset);&lt;br /&gt;
&lt;br /&gt;
    ip_send_check(ihdr);&lt;br /&gt;
&lt;br /&gt;
    return dst_neigh_output(skb);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这篇文章的[https://github.com/saminiir/level-ip 源代码]可在 GitHub 上找到。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=43</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=43"/>
		<updated>2025-12-24T09:39:45Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=42</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=42"/>
		<updated>2025-12-24T09:38:07Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=41</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=41"/>
		<updated>2025-12-24T09:36:16Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈2-IPv4和ICMPv4|动手做系列：编写一个TCP/IP栈2-IPv4和ICMPv4]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP|动手做系列：编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=39</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=39"/>
		<updated>2025-12-24T09:33:58Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-IPv4和ICMPv4|动手做系列：编写一个TCP/IP栈1-IPv4和ICMPv4]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP|动手做系列：编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=38</id>
		<title>编写一个TCP/IP栈1-以太网和ARP</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=38"/>
		<updated>2025-12-24T09:31:19Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;编写自己的TCP/IP栈可能看起来是一项艰巨的任务。事实上,TCP在其三十多年的生命周期中积累了许多规范。然而,&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;核心规范看似紧凑——重要的部分是TCP头解析、状态机、拥塞控制以及传输超时计算。&lt;br /&gt;
&lt;br /&gt;
最常见的第2层和第3层协议,分别是以太网和IP,与TCP的复杂性相形见绌。在本博客系列中,我们将实现一个适用于 Linux 的最小用户空间 TCP/IP 栈。&lt;br /&gt;
&lt;br /&gt;
这些帖子和生成软件的目的纯粹是教育性的——在更深层次上学习网络和系统编程。&lt;br /&gt;
&lt;br /&gt;
*&lt;br /&gt;
&lt;br /&gt;
= TUN/TAP 设备 =&lt;br /&gt;
为了拦截来自Linux内核的低级别网络流量,我们将使用Linux TAP设备。简而言之,TUN/TAP 设备经常被网络用户空间应用程序用于操作 L3/L2 流量。一个流行的例子是隧道,其中数据包被包裹在另一个数据包的有效载荷内。&lt;br /&gt;
&lt;br /&gt;
TUN/TAP 设备的优点在于在用户空间程序中易于设置,并且已在众多程序(如 OpenVPN)中使用。&lt;br /&gt;
&lt;br /&gt;
由于我们希望从第2层向上构建网络栈,因此需要一个TAP设备。我们这样进行即时验证:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
 * Taken from Kernel Documentation/networking/tuntap.txt&lt;br /&gt;
 */&lt;br /&gt;
int tun_alloc(char *dev)&lt;br /&gt;
{&lt;br /&gt;
    struct ifreq ifr;&lt;br /&gt;
    int fd, err;&lt;br /&gt;
&lt;br /&gt;
    if( (fd = open(&amp;quot;/dev/net/tap&amp;quot;, O_RDWR)) &amp;lt; 0 ) {&lt;br /&gt;
        print_error(&amp;quot;Cannot open TUN/TAP dev&amp;quot;);&lt;br /&gt;
        exit(1);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLEAR(ifr);&lt;br /&gt;
&lt;br /&gt;
    /* Flags: IFF_TUN   - TUN device (no Ethernet headers)&lt;br /&gt;
     *        IFF_TAP   - TAP device&lt;br /&gt;
     *&lt;br /&gt;
     *        IFF_NO_PI - Do not provide packet information&lt;br /&gt;
     */&lt;br /&gt;
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;&lt;br /&gt;
    if( *dev ) {&lt;br /&gt;
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if( (err = ioctl(fd, TUNSETIFF, (void *) &amp;amp;ifr)) &amp;lt; 0 ){&lt;br /&gt;
        print_error(&amp;quot;ERR: Could not ioctl tun: %s\n&amp;quot;, strerror(errno));&lt;br /&gt;
        close(fd);&lt;br /&gt;
        return err;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    strcpy(dev, ifr.ifr_name);&lt;br /&gt;
    return fd;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;之后,返回的文件描述符&amp;lt;code&amp;gt;fd&amp;lt;/code&amp;gt;可以用来&amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;write&amp;lt;/code&amp;gt;数据到虚拟设备的以太网缓冲区。&lt;br /&gt;
&lt;br /&gt;
旗帜&amp;lt;code&amp;gt;IFF_NO_PI&amp;lt;/code&amp;gt;这里至关重要,否则我们最终会收到预设在以太网帧的不必要的数据包信息。你实际上可以查看隧道设备驱动程序的[https://github.com/torvalds/linux/blob/v4.4/drivers/net/tun.c#L1306 内核源代码],并自行验证。&lt;br /&gt;
&lt;br /&gt;
= 以太网帧格式 =&lt;br /&gt;
&#039;&#039;多种不同的以太网网络技术是局域网&#039;&#039;(LAN)中连接计算机的支柱。与所有物理技术一样,&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;以太网标准已从其1980年由数字设备公司、英特尔和施乐公司发布的首版2版大有演进。&lt;br /&gt;
&lt;br /&gt;
以太网的首个版本在当今标准中较为缓慢——大约为10兆字节/秒,它采用了半双工通信技术,意味着您发送或接收数据,但并非同时进行。因此,&#039;&#039;必须采用媒体访问控制&#039;&#039;(MAC)协议来组织数据流。时至今日,如果在半双工模式下运行以太网接口,仍需要使用“载波感知”、带碰撞检测(CSMA/CD&#039;&#039;)&#039;&#039;的多重访问方式。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;100BASE-T&#039;&#039;采用100BASE-T以太网标准,采用双绞线,实现全双工通信和更高的传输速度。此外,以太网交换机的普及程度同时增加,使得CSMA/CD基本过时。&lt;br /&gt;
&lt;br /&gt;
不同的以太网标准由IEEE &amp;lt;sup&amp;gt;802.3&amp;lt;/sup&amp;gt; 3工作组维护。&lt;br /&gt;
&lt;br /&gt;
接下来,我们将查看以太网帧头。如下所示,可以将其声明为C结构:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;linux/if_ether.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct eth_hdr&lt;br /&gt;
{&lt;br /&gt;
    unsigned char dmac[6];&lt;br /&gt;
    unsigned char smac[6];&lt;br /&gt;
    uint16_t ethertype;&lt;br /&gt;
    unsigned char payload[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;smac&amp;lt;/code&amp;gt;是相当不言自明的领域。它们包含通信方的MAC地址(分别为目的地和来源)。&lt;br /&gt;
&lt;br /&gt;
超载的字段&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;是一个2-octet 字段,根据其值表示有效载荷的长度或类型。具体来说,如果该字段的值大于或等于1536,则该字段包含有效载荷的类型(例如IPv4、ARP)如果值小于该值,则包含有效载荷的长度。&lt;br /&gt;
&lt;br /&gt;
类型字段之后&#039;&#039;tags&#039;&#039;,以太网帧可能会出现多个不同的标签。&#039;&#039;这些标签可用于描述帧的虚拟局域网&#039;&#039;(VLAN)&#039;&#039;或服务质量&#039;&#039;(QoS)类型。以太网帧标签不在我们的实现中,因此相应的字段也不会显示在我们的协议声明中。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;字段payload&amp;lt;/code&amp;gt;包含指向以太网帧有效载荷的指针。在我们的情况下,这将包含一个ARP或IPv4数据包。如果有效载荷长度小于所需最小48字节(不含标签),则将空格字节附加到有效载荷的末尾,以满足要求。&lt;br /&gt;
&lt;br /&gt;
我们还包括&amp;lt;code&amp;gt;if_ether.h&amp;lt;/code&amp;gt;Linux 头向提供 ethertypes 与其十六进制值之间的映射。&lt;br /&gt;
&lt;br /&gt;
最后,&#039;&#039;Frame Check Sequence&#039;&#039;以太网帧格式还包含了帧勾选句字段,&#039;&#039;Cyclic Redundancy Check&#039;&#039;该字段与循环冗余检查(CRC)配合使用,以检查帧的完整性。我们将在实现此字段时省略处理。&lt;br /&gt;
&lt;br /&gt;
= 以太网框架解析 =&lt;br /&gt;
&#039;&#039;packed&#039;&#039;结构体声明中包含的属性是一个实现细节——它用于指示GNU &amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;C编译器不要针对数据对齐进行填充字节4的结构体内存布局进行优化。使用此属性完全源于我们“解析”协议缓冲区的方式,该缓冲区只是使用正确协议结构对数据缓冲区进行的一种类型转换:&lt;br /&gt;
&lt;br /&gt;
struct eth_hdr *hdr = (struct eth_hdr *) buf;&lt;br /&gt;
&lt;br /&gt;
一种便携且略显费力的方法,是手动对协议数据进行序列化。这样,编译器可以自由添加填充字节,以更好地满足不同处理器的数据对齐要求。&lt;br /&gt;
&lt;br /&gt;
解析和处理传入的以太网帧的总体情况很简单:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
if (tun_read(buf, BUFLEN) &amp;lt; 0) {&lt;br /&gt;
    print_error(&amp;quot;ERR: Read from tun_fd: %s\n&amp;quot;, strerror(errno));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
struct eth_hdr *hdr = init_eth_hdr(buf);&lt;br /&gt;
&lt;br /&gt;
handle_frame(&amp;amp;netdev, hdr);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;handle_frame&amp;lt;/code&amp;gt;函数只是看&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;以太网标头字段,并根据值决定下一步操作。&lt;br /&gt;
&lt;br /&gt;
= 地址解析协议（Arp） =&lt;br /&gt;
&#039;&#039;Address Resolution Protocol&#039;&#039;地址解析协议(ARP)用于动态地将48位以太网地址(MAC地址)映射到协议地址(例如IPv4 地址。关键是使用ARP,可以使用多种不同的L3协议:不仅使用IPv4,还可以使用其他协议,例如宣布16位协议地址的CHAOS。&lt;br /&gt;
&lt;br /&gt;
通常情况下,你了解局域网中某个服务的IP地址,但要建立实际通信,硬件地址(MAC)也需要被知晓。因此,ARP 用于广播和查询网络,要求 IP 地址的所有者报告其硬件地址。&lt;br /&gt;
&lt;br /&gt;
ARP 数据包格式相对简单:&lt;br /&gt;
&lt;br /&gt;
ARP头球(&amp;lt;code&amp;gt;arp_hdr&amp;lt;/code&amp;gt;包含2-octet&amp;lt;code&amp;gt;hwtype&amp;lt;/code&amp;gt;确定所使用的链接层类型。这是我们的以太网,实际值是&amp;lt;code&amp;gt;0x0001&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
2-octet&amp;lt;code&amp;gt;protype&amp;lt;/code&amp;gt;字段表示协议类型。在我们的例子中,这是IPv4,它与值进行通信&amp;lt;code&amp;gt;0x0800&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;hwsize&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;prosize&amp;lt;/code&amp;gt;字段大小均为1-octet,且分别包含硬件和协议字段的大小。在我们的情况下,这些地址为 MAC 地址为 6 字节,IP 地址为 4 字节。&lt;br /&gt;
&lt;br /&gt;
2-octe&amp;lt;code&amp;gt;opcode&amp;lt;/code&amp;gt;声明 ARP 消息的类型。可以是 ARP 请求(1)、ARP 回复(2)、RARP 请求(3)或 RARP 回复(4)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;字段包含 ARP 消息的实际有效载荷,在我们的情况下,这将包含 IPv4 的特定信息:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct arp_ipv4&lt;br /&gt;
{&lt;br /&gt;
    unsigned char smac[6];&lt;br /&gt;
    uint32_t sip;&lt;br /&gt;
    unsigned char dmac[6];&lt;br /&gt;
    uint32_t dip;&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;smac和&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;分别包含发送方和接收方的6字节MAC地址。&amp;lt;code&amp;gt;sip&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;dip&amp;lt;/code&amp;gt;分别包含发送方和接收方的IP地址。&lt;br /&gt;
&lt;br /&gt;
= 地址解析算法 =&lt;br /&gt;
原始规范描述了用于地址解析的简单算法:&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
?Do I have the hardware type in ar$hrd?&lt;br /&gt;
Yes: (almost definitely)&lt;br /&gt;
  [optionally check the hardware length ar$hln]&lt;br /&gt;
  ?Do I speak the protocol in ar$pro?&lt;br /&gt;
  Yes:&lt;br /&gt;
    [optionally check the protocol length ar$pln]&lt;br /&gt;
    Merge_flag := false&lt;br /&gt;
    If the pair &amp;lt;protocol type, sender protocol address&amp;gt; is&lt;br /&gt;
        already in my translation table, update the sender&lt;br /&gt;
        hardware address field of the entry with the new&lt;br /&gt;
        information in the packet and set Merge_flag to true.&lt;br /&gt;
    ?Am I the target protocol address?&lt;br /&gt;
    Yes:&lt;br /&gt;
      If Merge_flag is false, add the triplet &amp;lt;protocol type,&lt;br /&gt;
          sender protocol address, sender hardware address&amp;gt; to&lt;br /&gt;
          the translation table.&lt;br /&gt;
      ?Is the opcode ares_op$REQUEST?  (NOW look at the opcode!!)&lt;br /&gt;
      Yes:&lt;br /&gt;
        Swap hardware and protocol fields, putting the local&lt;br /&gt;
            hardware and protocol addresses in the sender fields.&lt;br /&gt;
        Set the ar$op field to ares_op$REPLY&lt;br /&gt;
        Send the packet to the (new) target hardware address on&lt;br /&gt;
            the same hardware on which the request was received.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;即&amp;lt;code&amp;gt;translation table&amp;lt;/code&amp;gt;用于存储 ARP 的结果,以便主机能够查看其缓存中是否已包含该条目。这可以避免为冗余的 ARP 请求发送网络垃圾邮件。&lt;br /&gt;
&lt;br /&gt;
该算法在 [https://github.com/saminiir/level-ip/blob/e9ceb08f01a5499b85f03e2d615309c655b97e8f/src/arp.c#L53 arp.c] 中实现。&lt;br /&gt;
&lt;br /&gt;
最后,实现ARP的最终测试是,它是否能正确回复ARP请求:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
[saminiir@localhost lvl-ip]$ arping -I tap0 10.0.0.4&lt;br /&gt;
ARPING 10.0.0.4 from 192.168.1.32 tap0&lt;br /&gt;
Unicast reply from 10.0.0.4 [00:0C:29:6D:50:25]  3.170ms&lt;br /&gt;
Unicast reply from 10.0.0.4 [00:0C:29:6D:50:25]  13.309ms&lt;br /&gt;
&lt;br /&gt;
[saminiir@localhost lvl-ip]$ arp&lt;br /&gt;
Address                  HWtype  HWaddress           Flags Mask            Iface&lt;br /&gt;
10.0.0.4                 ether   00:0c:29:6d:50:25   C                     tap0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;内核的网络栈从我们的自定义网络堆栈中识别出ARP响应,随后通过输入我们的虚拟网络设备来填充其ARP缓存。成功!&lt;br /&gt;
&lt;br /&gt;
= 结论 =&lt;br /&gt;
以太网帧处理和自动处理处理的实现非常简单,只需几行代码即可完成。相反,奖励因素相当高,因为你可以使用自己的支持性以太网设备来填充Linux主机的ARP缓存!&lt;br /&gt;
&lt;br /&gt;
该项目的[https://github.com/saminiir/level-ip 源代码]可在 GitHub 上找到。&lt;br /&gt;
&lt;br /&gt;
在下一篇文章中,我们将继续使用ICMP回声与回复(ping)以及IPv4数据包解析来实现。&lt;br /&gt;
&lt;br /&gt;
= 来源 =&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://tools.ietf.org/html/rfc7414&amp;lt;/nowiki&amp;gt; ↩↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;http://ethernethistory.typepad.com/papers/EthernetSpec.pdf&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://en.wikipedia.org/wiki/IEEE_802.3&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attrites.html#Common-Type-Attributes&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://github.com/chobits/tapip&amp;lt;/nowiki&amp;gt; ↩&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=37</id>
		<title>编写一个TCP/IP栈1-以太网和ARP</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=37"/>
		<updated>2025-12-24T08:57:32Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;编写自己的TCP/IP栈可能看起来是一项艰巨的任务。事实上,TCP在其三十多年的生命周期中积累了许多规范。然而,&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;核心规范看似紧凑——重要的部分是TCP头解析、状态机、拥塞控制以及传输超时计算。&lt;br /&gt;
&lt;br /&gt;
最常见的第2层和第3层协议,分别是以太网和IP,与TCP的复杂性相形见绌。在本博客系列中,我们将实现一个适用于 Linux 的最小用户空间 TCP/IP 栈。&lt;br /&gt;
&lt;br /&gt;
这些帖子和生成软件的目的纯粹是教育性的——在更深层次上学习网络和系统编程。&lt;br /&gt;
&lt;br /&gt;
*&lt;br /&gt;
&lt;br /&gt;
= TUN/TAP 设备 =&lt;br /&gt;
为了拦截来自Linux内核的低级别网络流量,我们将使用Linux TAP设备。简而言之,TUN/TAP 设备经常被网络用户空间应用程序用于操作 L3/L2 流量。一个流行的例子是隧道,其中数据包被包裹在另一个数据包的有效载荷内。&lt;br /&gt;
&lt;br /&gt;
TUN/TAP 设备的优点在于在用户空间程序中易于设置,并且已在众多程序(如 OpenVPN)中使用。&lt;br /&gt;
&lt;br /&gt;
由于我们希望从第2层向上构建网络栈,因此需要一个TAP设备。我们这样进行即时验证:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
 * Taken from Kernel Documentation/networking/tuntap.txt&lt;br /&gt;
 */&lt;br /&gt;
int tun_alloc(char *dev)&lt;br /&gt;
{&lt;br /&gt;
    struct ifreq ifr;&lt;br /&gt;
    int fd, err;&lt;br /&gt;
&lt;br /&gt;
    if( (fd = open(&amp;quot;/dev/net/tap&amp;quot;, O_RDWR)) &amp;lt; 0 ) {&lt;br /&gt;
        print_error(&amp;quot;Cannot open TUN/TAP dev&amp;quot;);&lt;br /&gt;
        exit(1);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLEAR(ifr);&lt;br /&gt;
&lt;br /&gt;
    /* Flags: IFF_TUN   - TUN device (no Ethernet headers)&lt;br /&gt;
     *        IFF_TAP   - TAP device&lt;br /&gt;
     *&lt;br /&gt;
     *        IFF_NO_PI - Do not provide packet information&lt;br /&gt;
     */&lt;br /&gt;
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;&lt;br /&gt;
    if( *dev ) {&lt;br /&gt;
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if( (err = ioctl(fd, TUNSETIFF, (void *) &amp;amp;ifr)) &amp;lt; 0 ){&lt;br /&gt;
        print_error(&amp;quot;ERR: Could not ioctl tun: %s\n&amp;quot;, strerror(errno));&lt;br /&gt;
        close(fd);&lt;br /&gt;
        return err;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    strcpy(dev, ifr.ifr_name);&lt;br /&gt;
    return fd;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;之后,返回的文件描述符&amp;lt;code&amp;gt;fd&amp;lt;/code&amp;gt;可以用来&amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;write&amp;lt;/code&amp;gt;数据到虚拟设备的以太网缓冲区。&lt;br /&gt;
&lt;br /&gt;
旗帜&amp;lt;code&amp;gt;IFF_NO_PI&amp;lt;/code&amp;gt;这里至关重要,否则我们最终会收到预设在以太网帧的不必要的数据包信息。你实际上可以查看隧道设备驱动程序的[https://github.com/torvalds/linux/blob/v4.4/drivers/net/tun.c#L1306 内核源代码],并自行验证。&lt;br /&gt;
&lt;br /&gt;
= 以太网帧格式 =&lt;br /&gt;
&#039;&#039;多种不同的以太网网络技术是局域网&#039;&#039;(LAN)中连接计算机的支柱。与所有物理技术一样,&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;以太网标准已从其1980年由数字设备公司、英特尔和施乐公司发布的首版2版大有演进。&lt;br /&gt;
&lt;br /&gt;
以太网的首个版本在当今标准中较为缓慢——大约为10兆字节/秒,它采用了半双工通信技术,意味着您发送或接收数据,但并非同时进行。因此,&#039;&#039;必须采用媒体访问控制&#039;&#039;(MAC)协议来组织数据流。时至今日,如果在半双工模式下运行以太网接口,仍需要使用“载波感知”、带碰撞检测(CSMA/CD&#039;&#039;)&#039;&#039;的多重访问方式。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;100BASE-T&#039;&#039;采用100BASE-T以太网标准,采用双绞线,实现全双工通信和更高的传输速度。此外,以太网交换机的普及程度同时增加,使得CSMA/CD基本过时。&lt;br /&gt;
&lt;br /&gt;
不同的以太网标准由IEEE &amp;lt;sup&amp;gt;802.3&amp;lt;/sup&amp;gt; 3工作组维护。&lt;br /&gt;
&lt;br /&gt;
接下来,我们将查看以太网帧头。如下所示,可以将其声明为C结构:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;linux/if_ether.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
struct eth_hdr&lt;br /&gt;
{&lt;br /&gt;
    unsigned char dmac[6];&lt;br /&gt;
    unsigned char smac[6];&lt;br /&gt;
    uint16_t ethertype;&lt;br /&gt;
    unsigned char payload[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;smac&amp;lt;/code&amp;gt;是相当不言自明的领域。它们包含通信方的MAC地址(分别为目的地和来源)。&lt;br /&gt;
&lt;br /&gt;
超载的字段&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;是一个2-octet 字段,根据其值表示有效载荷的长度或类型。具体来说,如果该字段的值大于或等于1536,则该字段包含有效载荷的类型(例如IPv4、ARP)如果值小于该值,则包含有效载荷的长度。&lt;br /&gt;
&lt;br /&gt;
类型字段之后&#039;&#039;tags&#039;&#039;,以太网帧可能会出现多个不同的标签。&#039;&#039;这些标签可用于描述帧的虚拟局域网&#039;&#039;(VLAN)&#039;&#039;或服务质量&#039;&#039;(QoS)类型。以太网帧标签不在我们的实现中,因此相应的字段也不会显示在我们的协议声明中。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;字段payload&amp;lt;/code&amp;gt;包含指向以太网帧有效载荷的指针。在我们的情况下,这将包含一个ARP或IPv4数据包。如果有效载荷长度小于所需最小48字节(不含标签),则将空格字节附加到有效载荷的末尾,以满足要求。&lt;br /&gt;
&lt;br /&gt;
我们还包括&amp;lt;code&amp;gt;if_ether.h&amp;lt;/code&amp;gt;Linux 头向提供 ethertypes 与其十六进制值之间的映射。&lt;br /&gt;
&lt;br /&gt;
最后,&#039;&#039;Frame Check Sequence&#039;&#039;以太网帧格式还包含了帧勾选句字段,&#039;&#039;Cyclic Redundancy Check&#039;&#039;该字段与循环冗余检查(CRC)配合使用,以检查帧的完整性。我们将在实现此字段时省略处理。&lt;br /&gt;
&lt;br /&gt;
= 以太网框架解析 =&lt;br /&gt;
&#039;&#039;packed&#039;&#039;结构体声明中包含的属性是一个实现细节——它用于指示GNU &amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;C编译器不要针对数据对齐进行填充字节4的结构体内存布局进行优化。使用此属性完全源于我们“解析”协议缓冲区的方式,该缓冲区只是使用正确协议结构对数据缓冲区进行的一种类型转换:&lt;br /&gt;
&lt;br /&gt;
struct eth_hdr *hdr = (struct eth_hdr *) buf;&lt;br /&gt;
&lt;br /&gt;
一种便携且略显费力的方法,是手动对协议数据进行序列化。这样,编译器可以自由添加填充字节,以更好地满足不同处理器的数据对齐要求。&lt;br /&gt;
&lt;br /&gt;
解析和处理传入的以太网帧的总体情况很简单:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;handle_frame&amp;lt;/code&amp;gt;函数只是看&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;以太网标头字段,并根据值决定下一步操作。&lt;br /&gt;
&lt;br /&gt;
= 解决协议 =&lt;br /&gt;
&#039;&#039;Address Resolution Protocol&#039;&#039;地址解析协议(ARP)用于动态地将48位以太网地址(MAC地址)映射到协议地址(例如IPv4 地址。关键是使用ARP,可以使用多种不同的L3协议:不仅使用IPv4,还可以使用其他协议,例如宣布16位协议地址的CHAOS。&lt;br /&gt;
&lt;br /&gt;
通常情况下,你了解局域网中某个服务的IP地址,但要建立实际通信,硬件地址(MAC)也需要被知晓。因此,ARP 用于广播和查询网络,要求 IP 地址的所有者报告其硬件地址。&lt;br /&gt;
&lt;br /&gt;
ARP 数据包格式相对简单:&lt;br /&gt;
&lt;br /&gt;
ARP头球(&amp;lt;code&amp;gt;arp_hdr&amp;lt;/code&amp;gt;包含2-ctet&amp;lt;code&amp;gt;hwtype&amp;lt;/code&amp;gt;确定所使用的链接层类型。这是我们的以太网,实际价值是&amp;lt;code&amp;gt;0x0001&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
两枚八胞胎&amp;lt;code&amp;gt;protype&amp;lt;/code&amp;gt;字段表示协议类型。在我们的例子中,这是IPv4,它与值进行通信&amp;lt;code&amp;gt;0x0800&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;hwsize&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;prosize&amp;lt;/code&amp;gt;字段大小均为1-八位,且分别包含硬件和协议字段的大小。在我们的情况下,这些地址为 MAC 地址为 6 字节,IP 地址为 4 字节。&lt;br /&gt;
&lt;br /&gt;
两八分位&amp;lt;code&amp;gt;opcode&amp;lt;/code&amp;gt;声明 ARP 消息的类型。可以是 ARP 请求(1)、ARP 回复(2)、RARP 请求(3)或 RARP 回复(4)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;字段包含 ARP 消息的实际有效载荷,在我们的情况下,这将包含 IPv4 的特定信息:&lt;br /&gt;
&lt;br /&gt;
这些领域相当自我解释。&amp;lt;code&amp;gt;smac&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;分别包含发送方和接收方的6字节MAC地址。&amp;lt;code&amp;gt;sip&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;dip&amp;lt;/code&amp;gt;分别包含发送方和接收方的IP地址。&lt;br /&gt;
&lt;br /&gt;
= 地址解析算法 =&lt;br /&gt;
original specification原始规范描述了这种用于地址解析的简单算法:&lt;br /&gt;
&lt;br /&gt;
即&amp;lt;code&amp;gt;translation table&amp;lt;/code&amp;gt;用于存储 ARP 的结果,以便主机能够查看其缓存中是否已包含该条目。这可以避免为冗余的 ARP 请求发送网络垃圾邮件。&lt;br /&gt;
&lt;br /&gt;
该算法在 arp.c 中实现。&lt;br /&gt;
&lt;br /&gt;
最后,实现ARP的最终测试是,它是否能正确回复ARP请求:&lt;br /&gt;
&lt;br /&gt;
内核的网络栈从我们的自定义网络堆栈中识别出ARP响应,随后通过输入我们的虚拟网络设备来填充其ARP缓存。成功!&lt;br /&gt;
&lt;br /&gt;
= 结论 =&lt;br /&gt;
以太网帧处理和自动处理处理的实现非常简单,只需几行代码即可完成。相反,奖励因素相当高,因为你可以使用自己的支持性以太网设备来填充Linux主机的ARP缓存!&lt;br /&gt;
&lt;br /&gt;
该项目的源代码可在 GitHub 上找到。&lt;br /&gt;
&lt;br /&gt;
在下一篇文章中,我们将继续使用ICMP回声与回复(ping)以及IPv4数据包解析来实现。&lt;br /&gt;
&lt;br /&gt;
= 来源 =&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://tools.ietf.org/html/rfc7414&amp;lt;/nowiki&amp;gt; ↩↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;http://ethernethistory.typepad.com/papers/EthernetSpec.pdf&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://en.wikipedia.org/wiki/IEEE_802.3&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attrites.html#Common-Type-Attributes&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://github.com/chobits/tapip&amp;lt;/nowiki&amp;gt; ↩&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=36</id>
		<title>编写一个TCP/IP栈1-以太网和ARP</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%881-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%92%8CARP&amp;diff=36"/>
		<updated>2025-12-24T08:50:24Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“编写自己的TCP/IP栈可能看起来是一项艰巨的任务。事实上,TCP在其三十多年的生命周期中积累了许多规范。然而,&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;核心规范看似紧凑——重要的部分是TCP头解析、状态机、拥塞控制以及传输超时计算。  最常见的第2层和第3层协议,分别是以太网和IP,与TCP的复杂性相形见绌。在本博客系列中,我们将实现一个适用于 Linux 的最小用户空间 TCP/IP 栈。…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;编写自己的TCP/IP栈可能看起来是一项艰巨的任务。事实上,TCP在其三十多年的生命周期中积累了许多规范。然而,&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;核心规范看似紧凑——重要的部分是TCP头解析、状态机、拥塞控制以及传输超时计算。&lt;br /&gt;
&lt;br /&gt;
最常见的第2层和第3层协议,分别是以太网和IP,与TCP的复杂性相形见绌。在本博客系列中,我们将实现一个适用于 Linux 的最小用户空间 TCP/IP 栈。&lt;br /&gt;
&lt;br /&gt;
这些帖子和生成软件的目的纯粹是教育性的——在更深层次上学习网络和系统编程。&lt;br /&gt;
&lt;br /&gt;
*&lt;br /&gt;
&lt;br /&gt;
= TUN/TAP 设备 =&lt;br /&gt;
为了拦截来自Linux内核的低级别网络流量,我们将使用Linux TAP设备。简而言之,TUN/TAP 设备经常被网络用户空间应用程序用于操作 L3/L2 流量。一个流行的例子是隧道,其中数据包被包裹在另一个数据包的有效载荷内。&lt;br /&gt;
&lt;br /&gt;
TUN/TAP 设备的优点在于在用户空间程序中易于设置,并且已在众多程序(如 OpenVPN)中使用。&lt;br /&gt;
&lt;br /&gt;
由于我们希望从第2层向上构建网络栈,因此需要一个TAP设备。我们这样进行即时验证:&lt;br /&gt;
&lt;br /&gt;
之后,返回的文件描述符&amp;lt;code&amp;gt;fd&amp;lt;/code&amp;gt;可以用来&amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;write&amp;lt;/code&amp;gt;数据到虚拟设备的以太网缓冲区。&lt;br /&gt;
&lt;br /&gt;
旗帜&amp;lt;code&amp;gt;IFF_NO_PI&amp;lt;/code&amp;gt;这里至关重要,否则我们最终会收到预设在以太网帧的不必要的数据包信息。你实际上可以查看隧道设备驱动程序的内核源代码,并自行验证。&lt;br /&gt;
&lt;br /&gt;
= 以太网帧格式 =&lt;br /&gt;
&#039;&#039;多种不同的以太网网络技术是局域网&#039;&#039;(LAN)中连接计算机的支柱。与所有物理技术一样,&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;以太网标准已从其1980年由数字设备公司、英特尔和施乐公司发布的首版2版大有演进。&lt;br /&gt;
&lt;br /&gt;
以太网的首个版本在当今标准中较为缓慢——大约为10兆字节/秒,它采用了半双工通信技术,意味着您发送或接收数据,但并非同时进行。因此,&#039;&#039;必须采用媒体访问控制&#039;&#039;(MAC)协议来组织数据流。时至今日,如果在半双工模式下运行以太网接口,仍需要使用“载波感知”、带碰撞检测(CSMA/CD&#039;&#039;)&#039;&#039;的多重访问方式。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;100BASE-T&#039;&#039;采用100BASE-T以太网标准,采用双绞线,实现全双工通信和更高的传输速度。此外,以太网交换机的普及程度同时增加,使得CSMA/CD基本过时。&lt;br /&gt;
&lt;br /&gt;
不同的以太网标准由IEEE &amp;lt;sup&amp;gt;802.3&amp;lt;/sup&amp;gt; 3工作组维护。&lt;br /&gt;
&lt;br /&gt;
接下来,我们将查看以太网帧头。如下所示,可以将其声明为C结构:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;smac&amp;lt;/code&amp;gt;是相当不言自明的领域。它们包含通信方的MAC地址(分别为目的地和来源)。&lt;br /&gt;
&lt;br /&gt;
超载的字段&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;是一个2-ctet字段,根据其值表示有效载荷的长度或类型。具体来说,如果该字段的值大于或等于1536,则该字段包含有效载荷的类型(例如IPv4、ARP)如果值小于该值,则包含有效载荷的长度。&lt;br /&gt;
&lt;br /&gt;
类型字段之后&#039;&#039;tags&#039;&#039;,以太网帧可能会出现多个不同的标签。&#039;&#039;这些标签可用于描述帧的虚拟局域网&#039;&#039;(VLAN)&#039;&#039;或服务质量&#039;&#039;(QoS)类型。以太网帧标签不在我们的实现中,因此相应的字段也不会显示在我们的协议声明中。&lt;br /&gt;
&lt;br /&gt;
田野&amp;lt;code&amp;gt;payload&amp;lt;/code&amp;gt;包含指向以太网帧有效载荷的指针。在我们的情况下,这将包含一个ARP或IPv4数据包。如果有效载荷长度小于所需最小48字节(不含标签),则将空格字节附加到有效载荷的末尾,以满足要求。&lt;br /&gt;
&lt;br /&gt;
我们还包括&amp;lt;code&amp;gt;if_ether.h&amp;lt;/code&amp;gt;Linux 头向提供 ethertypes 与其十六进制值之间的映射。&lt;br /&gt;
&lt;br /&gt;
最后,&#039;&#039;Frame Check Sequence&#039;&#039;以太网帧格式还包含了帧勾选句字段,&#039;&#039;Cyclic Redundancy Check&#039;&#039;该字段与循环冗余检查(CRC)配合使用,以检查帧的完整性。我们将在实现此字段时省略处理。&lt;br /&gt;
&lt;br /&gt;
= 以太网框架解析 =&lt;br /&gt;
&#039;&#039;packed&#039;&#039;结构体声明中包含的属性是一个实现细节——它用于指示GNU &amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;C编译器不要针对数据对齐进行填充字节4的结构体内存布局进行优化。使用此属性完全源于我们“解析”协议缓冲区的方式,该缓冲区只是使用正确协议结构对数据缓冲区进行的一种类型转换:&lt;br /&gt;
&lt;br /&gt;
一种便携且略显费力的方法,是手动对协议数据进行序列化。这样,编译器可以自由添加填充字节,以更好地满足不同处理器的数据对齐要求。&lt;br /&gt;
&lt;br /&gt;
解析和处理传入的以太网帧的总体情况很简单:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;handle_frame&amp;lt;/code&amp;gt;函数只是看&amp;lt;code&amp;gt;ethertype&amp;lt;/code&amp;gt;以太网标头字段,并根据值决定下一步操作。&lt;br /&gt;
&lt;br /&gt;
= 解决协议 =&lt;br /&gt;
&#039;&#039;Address Resolution Protocol&#039;&#039;地址解析协议(ARP)用于动态地将48位以太网地址(MAC地址)映射到协议地址(例如IPv4 地址。关键是使用ARP,可以使用多种不同的L3协议:不仅使用IPv4,还可以使用其他协议,例如宣布16位协议地址的CHAOS。&lt;br /&gt;
&lt;br /&gt;
通常情况下,你了解局域网中某个服务的IP地址,但要建立实际通信,硬件地址(MAC)也需要被知晓。因此,ARP 用于广播和查询网络,要求 IP 地址的所有者报告其硬件地址。&lt;br /&gt;
&lt;br /&gt;
ARP 数据包格式相对简单:&lt;br /&gt;
&lt;br /&gt;
ARP头球(&amp;lt;code&amp;gt;arp_hdr&amp;lt;/code&amp;gt;包含2-ctet&amp;lt;code&amp;gt;hwtype&amp;lt;/code&amp;gt;确定所使用的链接层类型。这是我们的以太网,实际价值是&amp;lt;code&amp;gt;0x0001&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
两枚八胞胎&amp;lt;code&amp;gt;protype&amp;lt;/code&amp;gt;字段表示协议类型。在我们的例子中,这是IPv4,它与值进行通信&amp;lt;code&amp;gt;0x0800&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;hwsize&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;prosize&amp;lt;/code&amp;gt;字段大小均为1-八位,且分别包含硬件和协议字段的大小。在我们的情况下,这些地址为 MAC 地址为 6 字节,IP 地址为 4 字节。&lt;br /&gt;
&lt;br /&gt;
两八分位&amp;lt;code&amp;gt;opcode&amp;lt;/code&amp;gt;声明 ARP 消息的类型。可以是 ARP 请求(1)、ARP 回复(2)、RARP 请求(3)或 RARP 回复(4)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;字段包含 ARP 消息的实际有效载荷,在我们的情况下,这将包含 IPv4 的特定信息:&lt;br /&gt;
&lt;br /&gt;
这些领域相当自我解释。&amp;lt;code&amp;gt;smac&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;dmac&amp;lt;/code&amp;gt;分别包含发送方和接收方的6字节MAC地址。&amp;lt;code&amp;gt;sip&amp;lt;/code&amp;gt;而且&amp;lt;code&amp;gt;dip&amp;lt;/code&amp;gt;分别包含发送方和接收方的IP地址。&lt;br /&gt;
&lt;br /&gt;
= 地址解析算法 =&lt;br /&gt;
original specification原始规范描述了这种用于地址解析的简单算法:&lt;br /&gt;
&lt;br /&gt;
即&amp;lt;code&amp;gt;translation table&amp;lt;/code&amp;gt;用于存储 ARP 的结果,以便主机能够查看其缓存中是否已包含该条目。这可以避免为冗余的 ARP 请求发送网络垃圾邮件。&lt;br /&gt;
&lt;br /&gt;
该算法在 arp.c 中实现。&lt;br /&gt;
&lt;br /&gt;
最后,实现ARP的最终测试是,它是否能正确回复ARP请求:&lt;br /&gt;
&lt;br /&gt;
内核的网络栈从我们的自定义网络堆栈中识别出ARP响应,随后通过输入我们的虚拟网络设备来填充其ARP缓存。成功!&lt;br /&gt;
&lt;br /&gt;
= 结论 =&lt;br /&gt;
以太网帧处理和自动处理处理的实现非常简单,只需几行代码即可完成。相反,奖励因素相当高,因为你可以使用自己的支持性以太网设备来填充Linux主机的ARP缓存!&lt;br /&gt;
&lt;br /&gt;
该项目的源代码可在 GitHub 上找到。&lt;br /&gt;
&lt;br /&gt;
在下一篇文章中,我们将继续使用ICMP回声与回复(ping)以及IPv4数据包解析来实现。&lt;br /&gt;
&lt;br /&gt;
= 来源 =&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://tools.ietf.org/html/rfc7414&amp;lt;/nowiki&amp;gt; ↩↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;http://ethernethistory.typepad.com/papers/EthernetSpec.pdf&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://en.wikipedia.org/wiki/IEEE_802.3&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attrites.html#Common-Type-Attributes&amp;lt;/nowiki&amp;gt; ↩&lt;br /&gt;
# &amp;lt;nowiki&amp;gt;https://github.com/chobits/tapip&amp;lt;/nowiki&amp;gt; ↩&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=35</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=35"/>
		<updated>2025-12-24T08:46:31Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[编写一个TCP/IP栈1-以太网和ARP|动手做系列：编写一个TCP/IP栈1-以太网和ARP]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=34</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=34"/>
		<updated>2025-12-24T07:21:47Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* 构建自己的区块链（python教程） */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|构建自己的KV数据库（python教程）]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=500%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%9E%84%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93_DBDB&amp;diff=33</id>
		<title>500行代码构建自己的数据库 DBDB</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=500%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%9E%84%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93_DBDB&amp;diff=33"/>
		<updated>2025-12-24T07:19:58Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“=== 介绍 === DBDB（狗床数据库）是一个 Python 库，它实现了一个简单的键值数据库。它允许你将一个键与一个值关联起来，并将这种关联存储在磁盘上以便以后检索。  DBDB 旨在计算机崩溃和错误情况下保存数据。它还避免一次性将所有数据都加载到 RAM 中，因此您可以存储比 RAM 容量更多的数据。  === DBDB数据库架构 === DBDB数据库将内容放在磁盘上的某个…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== 介绍 ===&lt;br /&gt;
DBDB（狗床数据库）是一个 Python 库，它实现了一个简单的键值数据库。它允许你将一个键与一个值关联起来，并将这种关联存储在磁盘上以便以后检索。&lt;br /&gt;
&lt;br /&gt;
DBDB 旨在计算机崩溃和错误情况下保存数据。它还避免一次性将所有数据都加载到 RAM 中，因此您可以存储比 RAM 容量更多的数据。&lt;br /&gt;
&lt;br /&gt;
=== DBDB数据库架构 ===&lt;br /&gt;
DBDB数据库将内容放在磁盘上的某个地方（数据在文件中的布局方式；物理层）与数据的逻辑结构（本例中为二叉树；逻辑层）以及键值存储的内容（键 &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; 与值 &amp;lt;code&amp;gt;foo&amp;lt;/code&amp;gt; 的关联；公共 API）分开。&lt;br /&gt;
&lt;br /&gt;
许多数据库将逻辑方面和物理方面分开，因为提供每种方面的替代实现以获得不同的性能特征通常很有用，例如 DB2 的 SMS（文件系统中的文件）与 DMS（原始块设备）表空间，或者 MySQL 的替代引擎实现 。&lt;br /&gt;
&lt;br /&gt;
=== 探索设计 ===&lt;br /&gt;
本文的大部分章节都描述了程序从构思到完成的整个构建过程。然而，这并非我们大多数人与代码交互的方式。我们通常会发现别人编写的代码，然后想办法修改或扩展它，使其实现不同的功能。&lt;br /&gt;
&lt;br /&gt;
在本章中，我们将假设 DBDB 是一个已完成的项目，并逐步讲解它的工作原理。首先，让我们来了解一下整个项目的结构。&lt;br /&gt;
&lt;br /&gt;
=== 组织结构 ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;tool.py&amp;lt;/code&amp;gt; 定义了一个命令行工具，用于从终端窗口浏览数据库。&lt;br /&gt;
* &amp;lt;code&amp;gt;interface.py&amp;lt;/code&amp;gt; 定义了一个类 ( &amp;lt;code&amp;gt;DBDB&amp;lt;/code&amp;gt; )，该类使用具体的 &amp;lt;code&amp;gt;BinaryTree&amp;lt;/code&amp;gt; 实现来实现 Python 字典 API。这就是在 Python 程序中使用 DBDB 的方法。&lt;br /&gt;
* &amp;lt;code&amp;gt;logical.py&amp;lt;/code&amp;gt; 定义了逻辑层。它是一个键值存储的抽象接口。&lt;br /&gt;
** &amp;lt;code&amp;gt;LogicalBase&amp;lt;/code&amp;gt; 提供逻辑更新（例如 get、set 和 commit） API，并将更新的具体实现委托给具体的子类。它还管理存储锁定和内部节点的解引用。&lt;br /&gt;
** &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 是一个 Python 对象，它指向数据库中存储的二进制数据块。这种间接方式使我们能够避免一次性将整个数据存储加载到内存中。&lt;br /&gt;
* &amp;lt;code&amp;gt;binary_tree.py&amp;lt;/code&amp;gt; 在逻辑接口底层定义了一个具体的二叉树算法。&lt;br /&gt;
** &amp;lt;code&amp;gt;BinaryTree&amp;lt;/code&amp;gt; 提供了二叉树的具体实现，并提供了获取、插入和删除键值对的方法。BinaryTree 代表一棵不可变树；更新操作会返回一棵与原树结构相同的新树。&lt;br /&gt;
** &amp;lt;code&amp;gt;BinaryNode&amp;lt;/code&amp;gt; 实现了二叉树中的一个节点。&lt;br /&gt;
** &amp;lt;code&amp;gt;BinaryNodeRef&amp;lt;/code&amp;gt; 是一个特殊的 &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; ，它知道如何序列化和反序列化 &amp;lt;code&amp;gt;BinaryNode&amp;lt;/code&amp;gt; 。&lt;br /&gt;
* &amp;lt;code&amp;gt;physical.py&amp;lt;/code&amp;gt; 定义了物理层。Storage提供持久化（主要是）仅追加记录存储。&lt;br /&gt;
&lt;br /&gt;
这些模块的出现源于让每个类只承担单一职责的尝试。换句话说，每个类应该只有一个修改的理由。&lt;br /&gt;
&lt;br /&gt;
=== 读数据 ===&lt;br /&gt;
我们先从最简单的例子开始：从数据库中读取一个值。让我们看看当我们尝试获取 &amp;lt;code&amp;gt;example.db&amp;lt;/code&amp;gt; 中键为 &amp;lt;code&amp;gt;foo&amp;lt;/code&amp;gt; 值时会发生什么：&lt;br /&gt;
 &amp;lt;code&amp;gt;$ python -m dbdb.tool example.db get foo&amp;lt;/code&amp;gt;&lt;br /&gt;
这会运行模块 &amp;lt;code&amp;gt;dbdb.tool&amp;lt;/code&amp;gt; 中的 &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; 函数：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/tool.py&lt;br /&gt;
def main(argv):&lt;br /&gt;
    if not (4 &amp;lt;= len(argv) &amp;lt;= 5):&lt;br /&gt;
        usage()&lt;br /&gt;
        return BAD_ARGS&lt;br /&gt;
    dbname, verb, key, value = (argv[1:] + [None])[:4]&lt;br /&gt;
    if verb not in {&#039;get&#039;, &#039;set&#039;, &#039;delete&#039;}:&lt;br /&gt;
        usage()&lt;br /&gt;
        return BAD_VERB&lt;br /&gt;
    db = dbdb.connect(dbname)          # CONNECT&lt;br /&gt;
    try:&lt;br /&gt;
        if verb == &#039;get&#039;:&lt;br /&gt;
            sys.stdout.write(db[key])  # GET VALUE&lt;br /&gt;
        elif verb == &#039;set&#039;:&lt;br /&gt;
            db[key] = value&lt;br /&gt;
            db.commit()&lt;br /&gt;
        else:&lt;br /&gt;
            del db[key]&lt;br /&gt;
            db.commit()&lt;br /&gt;
    except KeyError:&lt;br /&gt;
        print(&amp;quot;Key not found&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return BAD_KEY&lt;br /&gt;
    return OK&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;connect()&amp;lt;/code&amp;gt; 函数打开数据库文件（可能会创建该文件，但绝不会覆盖它），并返回一个 &amp;lt;code&amp;gt;DBDB&amp;lt;/code&amp;gt; 实例：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/__init__.py&lt;br /&gt;
def connect(dbname):&lt;br /&gt;
    try:&lt;br /&gt;
        f = open(dbname, &#039;r+b&#039;)&lt;br /&gt;
    except IOError:&lt;br /&gt;
        fd = os.open(dbname, os.O_RDWR | os.O_CREAT)&lt;br /&gt;
        f = os.fdopen(fd, &#039;r+b&#039;)&lt;br /&gt;
    return DBDB(f)&lt;br /&gt;
&lt;br /&gt;
# dbdb/interface.py&lt;br /&gt;
class DBDB(object):&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, f):&lt;br /&gt;
        self._storage = Storage(f)&lt;br /&gt;
        self._tree = BinaryTree(self._storage)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们立刻发现 &amp;lt;code&amp;gt;DBDB&amp;lt;/code&amp;gt; 引用了 &amp;lt;code&amp;gt;Storage&amp;lt;/code&amp;gt; 的一个实例，但它也与 &amp;lt;code&amp;gt;self._tree&amp;lt;/code&amp;gt; 共享该引用。&lt;br /&gt;
&lt;br /&gt;
在设计中，确定哪些对象“拥有”某个资源通常是一个重要的问题，因为它能提示我们哪些更改可能存在安全隐患。&lt;br /&gt;
&lt;br /&gt;
一旦我们有了 DBDB 实例，获取 &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt; 的值是通过字典查找 ( &amp;lt;code&amp;gt;db[key]&amp;lt;/code&amp;gt; ) 完成的，这会导致 Python 解释器调用 &amp;lt;code&amp;gt;DBDB.__getitem__()&amp;lt;/code&amp;gt; 。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/interface.py&lt;br /&gt;
class DBDB(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def __getitem__(self, key):&lt;br /&gt;
        self._assert_not_closed()&lt;br /&gt;
        return self._tree.get(key)&lt;br /&gt;
&lt;br /&gt;
    def _assert_not_closed(self):&lt;br /&gt;
        if self._storage.closed:&lt;br /&gt;
            raise ValueError(&#039;Database closed.&#039;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;__getitem__()&amp;lt;/code&amp;gt; 通过调用 &amp;lt;code&amp;gt;_assert_not_closed&amp;lt;/code&amp;gt; 来确保数据库仍然处于打开状态。这里我们至少找到了一个 &amp;lt;code&amp;gt;DBDB&amp;lt;/code&amp;gt; 需要直接访问 &amp;lt;code&amp;gt;Storage&amp;lt;/code&amp;gt; 实例的原因：它可以强制执行前提条件。&lt;br /&gt;
&lt;br /&gt;
然后，DBDB 通过调用 &amp;lt;code&amp;gt;LogicalBase&amp;lt;/code&amp;gt; 提供的 &amp;lt;code&amp;gt;_tree.get()&amp;lt;/code&amp;gt; 方法来检索与内部 &amp;lt;code&amp;gt;_tree&amp;lt;/code&amp;gt; 上的 &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt; 关联的值：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/logical.py&lt;br /&gt;
class LogicalBase(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def get(self, key):&lt;br /&gt;
        if not self._storage.locked:&lt;br /&gt;
            self._refresh_tree_ref()&lt;br /&gt;
        return self._get(self._follow(self._tree_ref), key)&lt;br /&gt;
        &lt;br /&gt;
    def _refresh_tree_ref(self):&lt;br /&gt;
        self._tree_ref = self.node_ref_class(&lt;br /&gt;
            address=self._storage.get_root_address())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;_refresh_tree_ref&amp;lt;/code&amp;gt; 会将树的“视图”重置为磁盘上当前的数据，从而允许我们执行完全最新的读取操作。&lt;br /&gt;
&lt;br /&gt;
如果在尝试读取数据时存储&#039;&#039;被&#039;&#039;锁定怎么办？这意味着其他进程可能正在修改我们想要读取的数据；我们的读取结果很可能与数据的当前状态不符。这通常被称为“脏读”。这种模式允许多个读取器访问数据而无需担心阻塞，但代价是数据略微过时。&lt;br /&gt;
&lt;br /&gt;
现在，让我们来看看我们实际是如何获取数据的：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/binary_tree.py&lt;br /&gt;
class BinaryTree(LogicalBase):&lt;br /&gt;
# ...&lt;br /&gt;
    def _get(self, node, key):&lt;br /&gt;
        while node is not None:&lt;br /&gt;
            if key &amp;lt; node.key:&lt;br /&gt;
                node = self._follow(node.left_ref)&lt;br /&gt;
            elif node.key &amp;lt; key:&lt;br /&gt;
                node = self._follow(node.right_ref)&lt;br /&gt;
            else:&lt;br /&gt;
                return self._follow(node.value_ref)&lt;br /&gt;
        raise KeyError&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这是一个标准的二叉树搜索，通过节点引用进行查找。我们从 &amp;lt;code&amp;gt;BinaryTree&amp;lt;/code&amp;gt; 文档中得知， &amp;lt;code&amp;gt;Node&amp;lt;/code&amp;gt; ）和 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; ）都是值对象：它们不可变，其内容永远不会改变。 &amp;lt;code&amp;gt;Node&amp;lt;/code&amp;gt; 创建时会关联一个键和一个值，以及左子节点和右子节点。这些关联也永远不会改变。只有当根节点被替换时，整个 &amp;lt;code&amp;gt;BinaryTree&amp;lt;/code&amp;gt; 的内容才会发生可见的变化。这意味着在执行搜索时，我们无需担心树的内容会发生变化。&lt;br /&gt;
&lt;br /&gt;
一旦找到关联的值， &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; 函数会将其写入 &amp;lt;code&amp;gt;stdout&amp;lt;/code&amp;gt; ，而不添加任何额外的换行符，以完全保留用户的数据。&lt;br /&gt;
&lt;br /&gt;
=== 插入和更新 ===&lt;br /&gt;
现在我们将 &amp;lt;code&amp;gt;example.db&amp;lt;/code&amp;gt; 中键为 &amp;lt;code&amp;gt;foo&amp;lt;/code&amp;gt; 的值设置为 &amp;lt;code&amp;gt;bar&amp;lt;/code&amp;gt; ：&lt;br /&gt;
 &amp;lt;code&amp;gt;$ python -m dbdb.tool example.db set foo bar&amp;lt;/code&amp;gt;&lt;br /&gt;
同样，这段代码会运行 &amp;lt;code&amp;gt;dbdb.tool&amp;lt;/code&amp;gt; 模块中的 &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; 函数。由于我们之前已经见过这段代码，所以这里只重点介绍一下其中的关键部分：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/tool.py&lt;br /&gt;
def main(argv):&lt;br /&gt;
    ...&lt;br /&gt;
    db = dbdb.connect(dbname)          # CONNECT&lt;br /&gt;
    try:&lt;br /&gt;
        ...&lt;br /&gt;
        elif verb == &#039;set&#039;:&lt;br /&gt;
            db[key] = value            # SET VALUE&lt;br /&gt;
            db.commit()                # COMMIT&lt;br /&gt;
        ...&lt;br /&gt;
    except KeyError:&lt;br /&gt;
        ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这次我们使用 &amp;lt;code&amp;gt;db[key] = value&amp;lt;/code&amp;gt; 来设置值，它会调用 &amp;lt;code&amp;gt;DBDB.__setitem__()&amp;lt;/code&amp;gt; 。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/interface.py&lt;br /&gt;
class DBDB(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def __setitem__(self, key, value):&lt;br /&gt;
        self._assert_not_closed()&lt;br /&gt;
        return self._tree.set(key, value)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;__setitem__&amp;lt;/code&amp;gt; 确保数据库仍然打开，然后通过调用 &amp;lt;code&amp;gt;_tree.set()&amp;lt;/code&amp;gt; 将 &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt; 到 &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; 关联存储在内部 &amp;lt;code&amp;gt;_tree&amp;lt;/code&amp;gt; 中。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;_tree.set()&amp;lt;/code&amp;gt; 由 &amp;lt;code&amp;gt;LogicalBase&amp;lt;/code&amp;gt; 提供：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/logical.py&lt;br /&gt;
class LogicalBase(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def set(self, key, value):&lt;br /&gt;
        if self._storage.lock():&lt;br /&gt;
            self._refresh_tree_ref()&lt;br /&gt;
        self._tree_ref = self._insert(&lt;br /&gt;
            self._follow(self._tree_ref), key, self.value_ref_class(value))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;set()&amp;lt;/code&amp;gt; 首先检查存储锁：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/storage.py&lt;br /&gt;
class Storage(object):&lt;br /&gt;
    ...&lt;br /&gt;
    def lock(self):&lt;br /&gt;
        if not self.locked:&lt;br /&gt;
            portalocker.lock(self._f, portalocker.LOCK_EX)&lt;br /&gt;
            self.locked = True&lt;br /&gt;
            return True&lt;br /&gt;
        else:&lt;br /&gt;
            return False&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这里有两点需要注意：&lt;br /&gt;
&lt;br /&gt;
* 我们的锁是由名为 portalocker 的第三方文件锁定库提供的。&lt;br /&gt;
* &amp;lt;code&amp;gt;lock()&amp;lt;/code&amp;gt; 如果数据库已被锁定则返回 &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; ，否则返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
回到 &amp;lt;code&amp;gt;_tree.set()&amp;lt;/code&amp;gt; ，我们现在可以理解它最初为什么要检查 &amp;lt;code&amp;gt;lock()&amp;lt;/code&amp;gt; 的返回值：这使我们能够调用 &amp;lt;code&amp;gt;_refresh_tree_ref&amp;lt;/code&amp;gt; 获取最新的根节点引用，从而避免丢失自上次从磁盘刷新树以来其他进程所做的更新。然后，它会将根树节点替换为包含插入（或更新）的键/值对的新树。&lt;br /&gt;
&lt;br /&gt;
插入或更新树不会改变任何节点，因为 &amp;lt;code&amp;gt;_insert()&amp;lt;/code&amp;gt; 返回的是一棵新树。新树与原树共享未更改的部分，以节省内存和执行时间。很自然地，可以用递归的方式实现这一点：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/binary_tree.py&lt;br /&gt;
class BinaryTree(LogicalBase):&lt;br /&gt;
# ...&lt;br /&gt;
    def _insert(self, node, key, value_ref):&lt;br /&gt;
        if node is None:&lt;br /&gt;
            new_node = BinaryNode(&lt;br /&gt;
                self.node_ref_class(), key, value_ref, self.node_ref_class(), 1)&lt;br /&gt;
        elif key &amp;lt; node.key:&lt;br /&gt;
            new_node = BinaryNode.from_node(&lt;br /&gt;
                node,&lt;br /&gt;
                left_ref=self._insert(&lt;br /&gt;
                    self._follow(node.left_ref), key, value_ref))&lt;br /&gt;
        elif node.key &amp;lt; key:&lt;br /&gt;
            new_node = BinaryNode.from_node(&lt;br /&gt;
                node,&lt;br /&gt;
                right_ref=self._insert(&lt;br /&gt;
                    self._follow(node.right_ref), key, value_ref))&lt;br /&gt;
        else:&lt;br /&gt;
            new_node = BinaryNode.from_node(node, value_ref=value_ref)&lt;br /&gt;
        return self.node_ref_class(referent=new_node)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;注意我们总是返回一个新节点（用 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; 包裹）。我们不是更新一个节点使其指向新的子树，而是创建一个新节点，该节点共享未更改的子树。这使得二叉树成为一种不可变数据结构。&lt;br /&gt;
&lt;br /&gt;
您可能已经注意到这里有些奇怪：我们还没有对磁盘上的任何数据进行任何更改。我们所做的只是通过移动树节点来改变我们对磁盘数据的视图。&lt;br /&gt;
&lt;br /&gt;
为了将这些更改实际写入磁盘，我们需要显式调用 &amp;lt;code&amp;gt;commit()&amp;lt;/code&amp;gt; ，我们在本节开头在 &amp;lt;code&amp;gt;tool.py&amp;lt;/code&amp;gt; 中看到的是 &amp;lt;code&amp;gt;set&amp;lt;/code&amp;gt; 操作的第二部分。&lt;br /&gt;
&lt;br /&gt;
提交操作包括将内存中的所有脏状态写入磁盘，然后保存树的新根节点的磁盘地址。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;从 API 开始：&#039;&#039;&#039;&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/interface.py&lt;br /&gt;
class DBDB(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def commit(self):&lt;br /&gt;
        self._assert_not_closed()&lt;br /&gt;
        self._tree.commit()&lt;br /&gt;
        &lt;br /&gt;
# dbdb/logical.py&lt;br /&gt;
class LogicalBase(object)&lt;br /&gt;
# ...&lt;br /&gt;
    def commit(self):&lt;br /&gt;
        self._tree_ref.store(self._storage)&lt;br /&gt;
        self._storage.commit_root_address(self._tree_ref.address)&lt;br /&gt;
        &lt;br /&gt;
# dbdb/logical.py&lt;br /&gt;
class ValueRef(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def store(self, storage):&lt;br /&gt;
        if self._referent is not None and not self._address:&lt;br /&gt;
            self.prepare_to_store(storage)&lt;br /&gt;
            self._address = storage.write(self.referent_to_string(self._referent))&lt;br /&gt;
            &lt;br /&gt;
# dbdb/binary_tree.py&lt;br /&gt;
class BinaryNodeRef(ValueRef):&lt;br /&gt;
    def prepare_to_store(self, storage):&lt;br /&gt;
        if self._referent:&lt;br /&gt;
            self._referent.store_refs(storage)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;_tree.commit()&amp;lt;/code&amp;gt; 的实现来自 &amp;lt;code&amp;gt;LogicalBase&amp;lt;/code&amp;gt; ：&lt;br /&gt;
&lt;br /&gt;
所有 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; 都知道如何通过首先请求其子节点使用 &amp;lt;code&amp;gt;prepare_to_store()&amp;lt;/code&amp;gt; 进行序列化来将自身序列化到磁盘：&lt;br /&gt;
&lt;br /&gt;
在这种情况下， &amp;lt;code&amp;gt;LogicalBase&amp;lt;/code&amp;gt; 中的 &amp;lt;code&amp;gt;self._tree_ref&amp;lt;/code&amp;gt; 实际上是一个 &amp;lt;code&amp;gt;BinaryNodeRef&amp;lt;/code&amp;gt; （ &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 的子类），因此 &amp;lt;code&amp;gt;prepare_to_store()&amp;lt;/code&amp;gt; 的具体实现如上边代码所示：&lt;br /&gt;
&lt;br /&gt;
相关 &amp;lt;code&amp;gt;BinaryNode&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;_referent&amp;lt;/code&amp;gt; 要求其引用存储自身：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/binary_tree.py&lt;br /&gt;
class BinaryNode(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def store_refs(self, storage):&lt;br /&gt;
        self.value_ref.store(storage)&lt;br /&gt;
        self.left_ref.store(storage)&lt;br /&gt;
        self.right_ref.store(storage)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这会一直递归下去，直到找到任何有未写入更改的 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; （例如 &amp;lt;code&amp;gt;_address&amp;lt;/code&amp;gt; ）。&lt;br /&gt;
&lt;br /&gt;
现在我们又回到了 &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 的 &amp;lt;code&amp;gt;store&amp;lt;/code&amp;gt; 方法中，调用栈的上一级函数。store &amp;lt;code&amp;gt;store()&amp;lt;/code&amp;gt; 的最后一步是序列化这个节点并保存它的存储地址：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/logical.py&lt;br /&gt;
class ValueRef(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def store(self, storage):&lt;br /&gt;
        if self._referent is not None and not self._address:&lt;br /&gt;
            self.prepare_to_store(storage)&lt;br /&gt;
            self._address = storage.write(self.referent_to_string(self._referent))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;此时， &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; 的 &amp;lt;code&amp;gt;_referent&amp;lt;/code&amp;gt; 保证拥有其所有引用的可用地址，因此我们通过创建一个表示此节点的字节串来对其进行序列化：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/binary_tree.py&lt;br /&gt;
class BinaryNodeRef(ValueRef):&lt;br /&gt;
# ...&lt;br /&gt;
    @staticmethod&lt;br /&gt;
    def referent_to_string(referent):&lt;br /&gt;
        return pickle.dumps({&lt;br /&gt;
            &#039;left&#039;: referent.left_ref.address,&lt;br /&gt;
            &#039;key&#039;: referent.key,&lt;br /&gt;
            &#039;value&#039;: referent.value_ref.address,&lt;br /&gt;
            &#039;right&#039;: referent.right_ref.address,&lt;br /&gt;
            &#039;length&#039;: referent.length,&lt;br /&gt;
        })&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在 &amp;lt;code&amp;gt;store()&amp;lt;/code&amp;gt; 方法中更新地址，从技术上讲是对 &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 的修改。由于它不会影响用户可见的值，因此我们可以认为它是不可变的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
当对根 &amp;lt;code&amp;gt;_tree_ref&amp;lt;/code&amp;gt; 的 &amp;lt;code&amp;gt;store()&amp;lt;/code&amp;gt; 操作完成（在 &amp;lt;code&amp;gt;LogicalBase.commit()&amp;lt;/code&amp;gt; 中）后，我们就知道所有数据都已写入磁盘。现在我们可以通过调用以下方法提交根地址：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot;&amp;gt;&lt;br /&gt;
# dbdb/physical.py&lt;br /&gt;
class Storage(object):&lt;br /&gt;
# ...&lt;br /&gt;
    def commit_root_address(self, root_address):&lt;br /&gt;
        self.lock()&lt;br /&gt;
        self._f.flush()&lt;br /&gt;
        self._seek_superblock()&lt;br /&gt;
        self._write_integer(root_address)&lt;br /&gt;
        self._f.flush()&lt;br /&gt;
        self.unlock()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们确保文件句柄被刷新（以便操作系统知道我们希望将所有数据保存到像固态硬盘这样的稳定​​存储设备上），并写入根节点的地址。我们知道最后一次写入是原子性的，因为我们将磁盘地址存储在扇区边界上。它是文件中的第一个元素，因此无论扇区大小如何，这个结论都成立，并且磁盘硬件保证单扇区磁盘写入是原子性的。&lt;br /&gt;
&lt;br /&gt;
由于根节点地址要么是旧值，要么是新值（绝不会同时包含一部分旧值和一部分新值），其他进程无需加锁即可从数据库读取数据。外部进程可能看到旧树或新树，但绝不会同时看到两者。这样，提交操作就具有原子性。&lt;br /&gt;
&lt;br /&gt;
由于我们在写入根节点地址之前就将新数据写入磁盘并调用了 &amp;lt;code&amp;gt;fsync&amp;lt;/code&amp;gt; 系统&amp;lt;sup&amp;gt;调用&amp;lt;/sup&amp;gt; ，因此未提交的数据是无法访问的。相反，一旦根节点地址更新，我们就知道它引用的所有数据也都已在磁盘上。这样，提交操作也具有持久性。&lt;br /&gt;
&lt;br /&gt;
=== NodeRefs 如何节省内存 ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
为了避免同时将整个树结构保存在内存中，当从磁盘读取一个逻辑节点时，其左右子节点的磁盘地址（以及该子节点的值）会被加载到内存中。访问子节点及其值需要额外调用一次 &amp;lt;code&amp;gt;NodeRef.get()&amp;lt;/code&amp;gt; 函数来解引用（“真正获取”）数据。&lt;br /&gt;
&lt;br /&gt;
构建 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; 只需要一个地址：&lt;br /&gt;
 &amp;lt;code&amp;gt;+---------+&lt;br /&gt;
 | NodeRef |&lt;br /&gt;
 | ------- |&lt;br /&gt;
 | addr=3  |&lt;br /&gt;
 | get()   |&lt;br /&gt;
 +---------+&amp;lt;/code&amp;gt;&lt;br /&gt;
对其调用 &amp;lt;code&amp;gt;get()&amp;lt;/code&amp;gt; 方法将返回具体的节点，以及该节点的引用（以 &amp;lt;code&amp;gt;NodeRef&amp;lt;/code&amp;gt; 的形式）：&lt;br /&gt;
 &amp;lt;code&amp;gt;+---------+     +---------+     +---------+&lt;br /&gt;
 | NodeRef |     | Node    |     | NodeRef |&lt;br /&gt;
 | ------- |     | ------- | +-&amp;gt; | ------- |&lt;br /&gt;
 | addr=3  |     | key=A   | |   | addr=1  |&lt;br /&gt;
 | get() ------&amp;gt; | value=B | |   +---------+&lt;br /&gt;
 +---------+     | left  ----+&lt;br /&gt;
                 | right ----+   +---------+&lt;br /&gt;
                 +---------+ |   | NodeRef |&lt;br /&gt;
                             +-&amp;gt; | ------- |&lt;br /&gt;
                                 | addr=2  |&lt;br /&gt;
                                 +---------+&amp;lt;/code&amp;gt;&lt;br /&gt;
当对树的更改尚未提交时，这些更改会以引用的形式存在于内存中，从根节点一直指向已更改的叶节点。这些更改尚未保存到磁盘，因此已更改的节点包含具体的键值对，但没有磁盘地址。执行写入操作的进程可以看到未提交的更改，并且可以在发出提交指令之前进行更多更改，因为如果存在未提交的值， &amp;lt;code&amp;gt;NodeRef.get()&amp;lt;/code&amp;gt; 会返回该值；通过 API 访问时，已提交和未提交的数据之间没有区别。所有更新对于其他读取者来说都是原子性的，因为只有当新的根节点地址写入磁盘时，更改才可见。并发更新会被磁盘上的锁文件阻塞。锁在首次更新时获取，并在提交后释放。&lt;br /&gt;
&lt;br /&gt;
=== 思考练习 ===&lt;br /&gt;
DBDB 允许多个进程同时读取同一个数据库而不会阻塞；但缺点是读取器有时会检索到过时的数据。如果我们需要以一致的方式读取某些数据该怎么办？一个常见的用例是读取一个值，然后根据该值更新数据库。您会如何在 &amp;lt;code&amp;gt;DBDB&amp;lt;/code&amp;gt; 中编写一个方法来执行此操作？为了实现此功能，您需要做出哪些权衡？&lt;br /&gt;
&lt;br /&gt;
更新数据存储所用的算法可以通过替换 &amp;lt;code&amp;gt;interface.py&amp;lt;/code&amp;gt; 中的字符串 &amp;lt;code&amp;gt;BinaryTree&amp;lt;/code&amp;gt; 来完全更改。数据存储通常使用更复杂的搜索树类型，例如 B 树、B+ 树等，以提高性能。虽然平衡二叉树（而这里使用的并非平衡二叉树）需要这样做。 例如， &#039;&#039;O&#039;&#039;(&#039;&#039;log&amp;lt;small&amp;gt;&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;sup&amp;gt;N&amp;lt;/sup&amp;gt;&amp;lt;/small&amp;gt;)&#039;&#039; 随机节点读取即可找到一个值，而 B+ 树所需的读取次数要少得多。  因为每个节点会分成 32 个分支，而不是只有 2 个，查找复杂度为&#039;&#039;O&#039;&#039;(&#039;&#039;log&#039;&#039;&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;&#039;&#039;&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&#039;&#039;)。这在实践中会产生巨大的差异，因为遍历 40 亿个条目将需要从&#039;&#039;log&#039;&#039; &amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; (2&amp;lt;sup&amp;gt;32)&amp;lt;/sup&amp;gt;  =32到  &#039;&#039;log&#039;&#039;&amp;lt;sub&amp;gt;32&amp;lt;/sub&amp;gt;(2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;)≈6.4 次查找。每次查找都是一次随机访问，这对使用旋转盘片的硬盘来说开销极大。固态硬盘 (SSD) 有助于降低延迟，但 I/O 方面的开销优势依然存在。&lt;br /&gt;
&lt;br /&gt;
默认情况下，值由 &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 存储，ValueRef 接受字节作为值（直接传递给 &amp;lt;code&amp;gt;Storage&amp;lt;/code&amp;gt; ）。二叉树节点本身只是 &amp;lt;code&amp;gt;ValueRef&amp;lt;/code&amp;gt; 的一个子类。要通过 JSON 或 msgpack 存储更丰富的数据，只需编写自定义的 ValueRef &amp;lt;code&amp;gt;BinaryNodeRef&amp;lt;/code&amp;gt; 并将其设置为 &amp;lt;code&amp;gt;value_ref_class&amp;lt;/code&amp;gt; 即可。BinaryNodeRef 是使用 pickle 序列化数据的一个示例。&lt;br /&gt;
&lt;br /&gt;
数据库压缩是另一个有趣的练习。压缩可以通过对树进行中值遍历来实现，边遍历边写入数据。最好将所有树节点放在一起，因为查找任何数据都需要遍历这些节点。将尽可能多的中间节点打包到一个磁盘扇区中应该可以提高读取性能，至少在压缩后是如此。如果您尝试自己实现，这里会有一些细节（例如内存使用情况）。记住：务必在压缩前后进行性能基准测试！结果往往会让您感到惊讶。&lt;br /&gt;
&lt;br /&gt;
=== 模式与原则 ===&lt;br /&gt;
测试的是接口，而不是实现。在开发 DBDB 的过程中，我编写了许多测试用例，描述了我希望如何使用它。最初的测试用例是针对数据库的内存版本运行的，后来我扩展了 DBDB 的功能，使其能够持久化到磁盘，甚至后来还添加了 NodeRef 的概念。大多数测试用例都不需要修改，这让我确信系统仍然运行良好。&lt;br /&gt;
&lt;br /&gt;
遵循单一职责原则。类最多只能有一个修改的理由。虽然数据库数据库（DBDB）并非完全如此，但它有很多扩展途径，只需要进行局部修改。在添加新功能的同时进行重构，真是令人愉悦！&lt;br /&gt;
&lt;br /&gt;
=== 总结 ===&lt;br /&gt;
DBDB 是一个简单的数据库，它提供的保证也很简单，但即便如此，问题还是很快变得复杂起来。为了应对这种复杂性，我做的最重要的事情就是用一个不可变的数据结构来实现一个表面上可变的对象。下次当你遇到棘手的问题，觉得各种边界情况多到难以处理时，建议你考虑一下这种方法。&lt;br /&gt;
&lt;br /&gt;
# 附加功能：您能保证压缩后的树状结构保持平衡吗？这有助于长期维持性能 。↩&lt;br /&gt;
&lt;br /&gt;
2. 对文件描述符调用 &amp;lt;code&amp;gt;fsync&amp;lt;/code&amp;gt; 函数会请求操作系统和硬盘（或固态硬盘）立即写入所有缓冲数据。为了提高性能，操作系统和硬盘通常不会立即写入所有数据 。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=32</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=32"/>
		<updated>2025-12-24T04:09:57Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* 构建自己的区块链（python教程） */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|构建自己的区块链（python教程）]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=31</id>
		<title>分类:动手做系列</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E5%8A%A8%E6%89%8B%E5%81%9A%E7%B3%BB%E5%88%97&amp;diff=31"/>
		<updated>2025-12-24T04:08:58Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“=== 构建自己的区块链（python教程） ===”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链（python教程）]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=30</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=30"/>
		<updated>2025-12-24T04:08:08Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;br /&gt;
[[分类:动手做系列]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=29</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=29"/>
		<updated>2025-12-24T04:07:09Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[500行代码构建自己的数据库 DBDB|动手做系列：500行代码构建自己的数据库 DBDB: Dog Bed Database]] ===&lt;br /&gt;
[[分类:Web3]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E6%9E%84%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8C%BA%E5%9D%97%E9%93%BE&amp;diff=28</id>
		<title>构建自己的区块链</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E6%9E%84%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8C%BA%E5%9D%97%E9%93%BE&amp;diff=28"/>
		<updated>2025-12-24T04:02:15Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“== 如何构建自己的区块链：Python 教程 == 本教程将引导您从零开始构建区块链的基础知识。通过一个具体示例的详细讲解，您将更深入地了解区块链的优势和局限性。如需更全面的概述，我推荐您阅读 BitsOnBlocks 上的[https://bitsonblocks.net/2015/09/09/gentle-introduction-blockchain-technology/ 这篇优秀文章]。  === 1. 交易、验证和系统状态更新 === 区块链本质上是一个分…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 如何构建自己的区块链：Python 教程 ==&lt;br /&gt;
本教程将引导您从零开始构建区块链的基础知识。通过一个具体示例的详细讲解，您将更深入地了解区块链的优势和局限性。如需更全面的概述，我推荐您阅读 BitsOnBlocks 上的[https://bitsonblocks.net/2015/09/09/gentle-introduction-blockchain-technology/ 这篇优秀文章]。&lt;br /&gt;
&lt;br /&gt;
=== 1. 交易、验证和系统状态更新 ===&lt;br /&gt;
区块链本质上是一个分布式数据库，它有一套规则来验证数据库中新增的数据。我们将首先追踪两个虚拟人物——爱丽丝和鲍勃——的账户，他们将彼此进行虚拟货币交易。&lt;br /&gt;
&lt;br /&gt;
我们需要创建一个用于存储传入交易的交易池，验证这些交易，并将它们打包成一个区块。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
我们将使用哈希函数为每笔交易创建一个“指纹”——这个哈希函数将每个区块彼此关联起来。为了方便使用，我们将定义一个辅助函数来封装我们使用的 Python 哈希函数。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import hashlib, json, sys&lt;br /&gt;
&lt;br /&gt;
def hashMe(msg=&amp;quot;&amp;quot;):&lt;br /&gt;
    # For convenience, this is a helper function that wraps our hashing algorithm&lt;br /&gt;
    if type(msg)!=str:&lt;br /&gt;
        msg = json.dumps(msg,sort_keys=True)  # If we don&#039;t sort keys, we can&#039;t guarantee repeatability!&lt;br /&gt;
        &lt;br /&gt;
    if sys.version_info.major == 2:&lt;br /&gt;
        return unicode(hashlib.sha256(msg).hexdigest(),&#039;utf-8&#039;)&lt;br /&gt;
    else:&lt;br /&gt;
        return hashlib.sha256(str(msg).encode(&#039;utf-8&#039;)).hexdigest()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来，我们要创建一个函数来生成 Alice 和 Bob 之间的交易。我们将用负数表示取款，用正数表示存款。我们将确保交易始终发生在我们系统的两个用户之间，并且存款金额与取款金额大小相等——也就是说，我们既不创造货币，也不销毁货币。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import random&lt;br /&gt;
random.seed(0)&lt;br /&gt;
&lt;br /&gt;
def makeTransaction(maxValue=3):&lt;br /&gt;
    # This will create valid transactions in the range of (1,maxValue)&lt;br /&gt;
    sign      = int(random.getrandbits(1))*2 - 1   # This will randomly choose -1 or 1&lt;br /&gt;
    amount    = random.randint(1,maxValue)&lt;br /&gt;
    alicePays = sign * amount&lt;br /&gt;
    bobPays   = -1 * alicePays&lt;br /&gt;
    # By construction, this will always return transactions that respect the conservation of tokens.&lt;br /&gt;
    # However, note that we have not done anything to check whether these overdraft an account&lt;br /&gt;
    return {u&#039;Alice&#039;:alicePays,u&#039;Bob&#039;:bobPays}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;现在让我们创建大量交易，然后将它们分成区块。&lt;br /&gt;
&lt;br /&gt;
txnBuffer = [makeTransaction() for i in range(30)]&lt;br /&gt;
&lt;br /&gt;
下一步：创建我们自己的区块！我们将从交易缓冲区中取出前 k 笔交易，并将它们打包成一个区块。在此之前，我们需要定义一个方法来检查我们提取到区块中的交易的有效性。对于比特币，验证函数会检查输入值是否为有效的未花费交易输出（UTXO），交易输出是否不大于输入值，以及用于签名的密钥是否有效。在以太坊中，验证函数会检查智能合约是否被忠实执行并遵守 gas 限制。&lt;br /&gt;
&lt;br /&gt;
不过别担心，我们不必构建如此复杂的系统。我们将定义一套非常简单的规则，这些规则适用于基本的代币系统：&lt;br /&gt;
&lt;br /&gt;
* 存款和取款的总和必须为 0（代币既不被创造也不被销毁）&lt;br /&gt;
* 用户账户必须有足够的资金来支付任何提款。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
如果违反了其中任何一个条件，我们将拒绝交易。&lt;br /&gt;
&lt;br /&gt;
同时提供了一些交易示例，其中一些是欺诈性的——但我们现在可以检查它们的有效性！&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def updateState(txn, state):&lt;br /&gt;
    # Inputs: txn, state: dictionaries keyed with account names, holding numeric values for transfer amount (txn) or account balance (state)&lt;br /&gt;
    # Returns: Updated state, with additional users added to state if necessary&lt;br /&gt;
    # NOTE: This does not not validate the transaction- just updates the state!&lt;br /&gt;
    &lt;br /&gt;
    # If the transaction is valid, then update the state&lt;br /&gt;
    state = state.copy() # As dictionaries are mutable, let&#039;s avoid any confusion by creating a working copy of the data.&lt;br /&gt;
    for key in txn:&lt;br /&gt;
        if key in state.keys():&lt;br /&gt;
            state[key] += txn[key]&lt;br /&gt;
        else:&lt;br /&gt;
            state[key] = txn[key]&lt;br /&gt;
    return state&lt;br /&gt;
    &lt;br /&gt;
def isValidTxn(txn,state):&lt;br /&gt;
    # Assume that the transaction is a dictionary keyed by account names&lt;br /&gt;
&lt;br /&gt;
    # Check that the sum of the deposits and withdrawals is 0&lt;br /&gt;
    if sum(txn.values()) is not 0:&lt;br /&gt;
        return False&lt;br /&gt;
    &lt;br /&gt;
    # Check that the transaction does not cause an overdraft&lt;br /&gt;
    for key in txn.keys():&lt;br /&gt;
        if key in state.keys(): &lt;br /&gt;
            acctBalance = state[key]&lt;br /&gt;
        else:&lt;br /&gt;
            acctBalance = 0&lt;br /&gt;
        if (acctBalance + txn[key]) &amp;lt; 0:&lt;br /&gt;
            return False&lt;br /&gt;
    &lt;br /&gt;
    return True&lt;br /&gt;
    &lt;br /&gt;
state = {u&#039;Alice&#039;:5,u&#039;Bob&#039;:5}&lt;br /&gt;
&lt;br /&gt;
print(isValidTxn({u&#039;Alice&#039;: -3, u&#039;Bob&#039;: 3},state))  # Basic transaction- this works great! True&lt;br /&gt;
print(isValidTxn({u&#039;Alice&#039;: -4, u&#039;Bob&#039;: 3},state))  # But we can&#039;t create or destroy tokens! False&lt;br /&gt;
print(isValidTxn({u&#039;Alice&#039;: -6, u&#039;Bob&#039;: 6},state))  # We also can&#039;t overdraft our account. False&lt;br /&gt;
print(isValidTxn({u&#039;Alice&#039;: -4, u&#039;Bob&#039;: 2,&#039;Lisa&#039;:2},state)) # Creating new users is valid. True&lt;br /&gt;
print(isValidTxn({u&#039;Alice&#039;: -4, u&#039;Bob&#039;: 3,&#039;Lisa&#039;:2},state)) # But the same rules still apply! False&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;每个区块包含一批交易、指向前一个区块哈希值的引用（如果区块编号大于 1）以及其内容和区块头的哈希值。&lt;br /&gt;
&lt;br /&gt;
=== 2. 构建区块链：从交易到区块 ===&lt;br /&gt;
我们准备开始构建区块链！目前区块链上什么都没有，但我们可以通过定义“创世区块”（系统中的第一个区块）来启动它。由于创世区块不与任何先前的区块关联，它的处理方式略有不同，我们可以任意设置系统状态。在本例中，我们将为两位用户（Alice 和 Bob）创建账户，并分别给予他们 50 个代币。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
state = {u&#039;Alice&#039;:50, u&#039;Bob&#039;:50}  # Define the initial state&lt;br /&gt;
genesisBlockTxns = [state]&lt;br /&gt;
genesisBlockContents = {u&#039;blockNumber&#039;:0,u&#039;parentHash&#039;:None,u&#039;txnCount&#039;:1,u&#039;txns&#039;:genesisBlockTxns}&lt;br /&gt;
genesisHash = hashMe( genesisBlockContents )&lt;br /&gt;
genesisBlock = {u&#039;hash&#039;:genesisHash,u&#039;contents&#039;:genesisBlockContents}&lt;br /&gt;
genesisBlockStr = json.dumps(genesisBlock, sort_keys=True)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这将成为所有其他要素联系在一起的第一个要素。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span lang=&amp;quot;zh-Hans-CN&amp;quot; dir=&amp;quot;ltr&amp;quot;&amp;gt;chain = [genesisBlock]&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
对于每个区块，我们希望收集一组交易信息，创建区块头，对其进行哈希处理，然后将其添加到链中。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def makeBlock(txns,chain):&lt;br /&gt;
    parentBlock = chain[-1]&lt;br /&gt;
    parentHash  = parentBlock[u&#039;hash&#039;]&lt;br /&gt;
    blockNumber = parentBlock[u&#039;contents&#039;][u&#039;blockNumber&#039;] + 1&lt;br /&gt;
    txnCount    = len(txns)&lt;br /&gt;
    blockContents = {u&#039;blockNumber&#039;:blockNumber,u&#039;parentHash&#039;:parentHash,&lt;br /&gt;
                     u&#039;txnCount&#039;:len(txns),&#039;txns&#039;:txns}&lt;br /&gt;
    blockHash = hashMe( blockContents )&lt;br /&gt;
    block = {u&#039;hash&#039;:blockHash,u&#039;contents&#039;:blockContents}&lt;br /&gt;
    &lt;br /&gt;
    return block&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;让我们用这个方法将交易缓冲区处理成一组数据块：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
blockSizeLimit = 5  # Arbitrary number of transactions per block- &lt;br /&gt;
               #  this is chosen by the block miner, and can vary between blocks!&lt;br /&gt;
&lt;br /&gt;
while len(txnBuffer) &amp;gt; 0:&lt;br /&gt;
    bufferStartSize = len(txnBuffer)&lt;br /&gt;
    &lt;br /&gt;
    ## Gather a set of valid transactions for inclusion&lt;br /&gt;
    txnList = []&lt;br /&gt;
    while (len(txnBuffer) &amp;gt; 0) &amp;amp; (len(txnList) &amp;lt; blockSizeLimit):&lt;br /&gt;
        newTxn = txnBuffer.pop()&lt;br /&gt;
        validTxn = isValidTxn(newTxn,state) # This will return False if txn is invalid&lt;br /&gt;
        &lt;br /&gt;
        if validTxn:           # If we got a valid state, not &#039;False&#039;&lt;br /&gt;
            txnList.append(newTxn)&lt;br /&gt;
            state = updateState(newTxn,state)&lt;br /&gt;
        else:&lt;br /&gt;
            print(&amp;quot;ignored transaction&amp;quot;)&lt;br /&gt;
            sys.stdout.flush()&lt;br /&gt;
            continue  # This was an invalid transaction; ignore it and move on&lt;br /&gt;
        &lt;br /&gt;
    ## Make a block&lt;br /&gt;
    myBlock = makeBlock(txnList,chain)&lt;br /&gt;
    chain.append(myBlock)  &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;链信息输出如下：&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
chain[0]&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
    &#039;contents&#039;: {&#039;blockNumber&#039;: 0,&lt;br /&gt;
    &#039;parentHash&#039;: None,&lt;br /&gt;
    &#039;txnCount&#039;: 1,&lt;br /&gt;
    &#039;txns&#039;: [{&#039;Alice&#039;: 50, &#039;Bob&#039;: 50}]},&lt;br /&gt;
    &#039;hash&#039;: &#039;7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507&#039;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
 chain[1]&lt;br /&gt;
 &lt;br /&gt;
 {&lt;br /&gt;
    &#039;contents&#039;: &lt;br /&gt;
        {&lt;br /&gt;
            &#039;blockNumber&#039;: 1,&lt;br /&gt;
            &#039;parentHash&#039;: &#039;7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507&#039;,&lt;br /&gt;
            &#039;txnCount&#039;: 5,&lt;br /&gt;
            &#039;txns&#039;: [{&#039;Alice&#039;: 3, &#039;Bob&#039;: -3},&lt;br /&gt;
            {&#039;Alice&#039;: -1, &#039;Bob&#039;: 1},&lt;br /&gt;
            {&#039;Alice&#039;: 3, &#039;Bob&#039;: -3},&lt;br /&gt;
            {&#039;Alice&#039;: -2, &#039;Bob&#039;: 2},&lt;br /&gt;
            {&#039;Alice&#039;: 3, &#039;Bob&#039;: -3}]&lt;br /&gt;
        },&lt;br /&gt;
    &#039;hash&#039;: &#039;7a91fc8206c5351293fd11200b33b7192e87fad6545504068a51aba868bc6f72&#039;&lt;br /&gt;
     &lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;正如预期的那样，创世区块包含一笔无效交易，该交易会初始化账户余额（凭空创建代币）。子区块引用了父区块的哈希值，其中包含一组影响系统状态的新交易。现在我们可以看到系统状态已更新，包含了这些交易。&lt;br /&gt;
&lt;br /&gt;
=== 3 检查链有效性 ===&lt;br /&gt;
既然我们已经知道如何创建新区块并将它们连接成链，那么让我们定义一些函数来检查新区块是否有效，以及整个链是否有效。&lt;br /&gt;
在区块链网络中，这一点在两个方面变得尤为重要：&lt;br /&gt;
&lt;br /&gt;
* 在初始设置节点时，我们会下载完整的区块链历史记录。下载完成后，我们需要遍历整个区块链来计算系统状态。为了防止有人在初始链中插入无效交易，我们需要在初始下载时检查整条链的有效性。&lt;br /&gt;
* 一旦我们的节点与网络同步（拥有最新的区块链副本和系统状态的表示），它就需要检查广播到网络的新区块的有效性。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
我们需要三个功能来实现这一点：&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;checkBlockHash&#039;&#039;&#039;: 一个简单的辅助函数，用于确保区块内容与哈希值匹配。&lt;br /&gt;
* &#039;&#039;&#039;checkBlockValidity&#039;&#039;&#039;: 检查给定父节点和当前系统状态的区块的有效性。如果区块有效，我们希望它返回更新后的状态；否则，抛出错误。&lt;br /&gt;
* &#039;&#039;&#039;checkChain&#039;&#039;&#039;: 检查整个区块链的有效性，并计算从创世区块开始的系统状态。如果区块链有效，则返回系统状态；否则，抛出错误。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def checkBlockHash(block):&lt;br /&gt;
    # Raise an exception if the hash does not match the block contents&lt;br /&gt;
    expectedHash = hashMe( block[&#039;contents&#039;] )&lt;br /&gt;
    if block[&#039;hash&#039;]!=expectedHash:&lt;br /&gt;
        raise Exception(&#039;Hash does not match contents of block %s&#039;%&lt;br /&gt;
                        block[&#039;contents&#039;][&#039;blockNumber&#039;])&lt;br /&gt;
    return&lt;br /&gt;
    &lt;br /&gt;
def checkBlockValidity(block,parent,state):    &lt;br /&gt;
    # We want to check the following conditions:&lt;br /&gt;
    # - Each of the transactions are valid updates to the system state&lt;br /&gt;
    # - Block hash is valid for the block contents&lt;br /&gt;
    # - Block number increments the parent block number by 1&lt;br /&gt;
    # - Accurately references the parent block&#039;s hash&lt;br /&gt;
    parentNumber = parent[&#039;contents&#039;][&#039;blockNumber&#039;]&lt;br /&gt;
    parentHash   = parent[&#039;hash&#039;]&lt;br /&gt;
    blockNumber  = block[&#039;contents&#039;][&#039;blockNumber&#039;]&lt;br /&gt;
    &lt;br /&gt;
    # Check transaction validity; throw an error if an invalid transaction was found.&lt;br /&gt;
    for txn in block[&#039;contents&#039;][&#039;txns&#039;]:&lt;br /&gt;
        if isValidTxn(txn,state):&lt;br /&gt;
            state = updateState(txn,state)&lt;br /&gt;
        else:&lt;br /&gt;
            raise Exception(&#039;Invalid transaction in block %s: %s&#039;%(blockNumber,txn))&lt;br /&gt;
&lt;br /&gt;
    checkBlockHash(block) # Check hash integrity; raises error if inaccurate&lt;br /&gt;
&lt;br /&gt;
    if blockNumber!=(parentNumber+1):&lt;br /&gt;
        raise Exception(&#039;Hash does not match contents of block %s&#039;%blockNumber)&lt;br /&gt;
&lt;br /&gt;
    if block[&#039;contents&#039;][&#039;parentHash&#039;] != parentHash:&lt;br /&gt;
        raise Exception(&#039;Parent hash not accurate at block %s&#039;%blockNumber)&lt;br /&gt;
    &lt;br /&gt;
    return state&lt;br /&gt;
    &lt;br /&gt;
def checkChain(chain):&lt;br /&gt;
    # Work through the chain from the genesis block (which gets special treatment), &lt;br /&gt;
    #  checking that all transactions are internally valid,&lt;br /&gt;
    #    that the transactions do not cause an overdraft,&lt;br /&gt;
    #    and that the blocks are linked by their hashes.&lt;br /&gt;
    # This returns the state as a dictionary of accounts and balances,&lt;br /&gt;
    #   or returns False if an error was detected&lt;br /&gt;
&lt;br /&gt;
    &lt;br /&gt;
    ## Data input processing: Make sure that our chain is a list of dicts&lt;br /&gt;
    if type(chain)==str:&lt;br /&gt;
        try:&lt;br /&gt;
            chain = json.loads(chain)&lt;br /&gt;
            assert( type(chain)==list)&lt;br /&gt;
        except:  # This is a catch-all, admittedly crude&lt;br /&gt;
            return False&lt;br /&gt;
    elif type(chain)!=list:&lt;br /&gt;
        return False&lt;br /&gt;
    &lt;br /&gt;
    state = {}&lt;br /&gt;
    ## Prime the pump by checking the genesis block&lt;br /&gt;
    # We want to check the following conditions:&lt;br /&gt;
    # - Each of the transactions are valid updates to the system state&lt;br /&gt;
    # - Block hash is valid for the block contents&lt;br /&gt;
&lt;br /&gt;
    for txn in chain[0][&#039;contents&#039;][&#039;txns&#039;]:&lt;br /&gt;
        state = updateState(txn,state)&lt;br /&gt;
    checkBlockHash(chain[0])&lt;br /&gt;
    parent = chain[0]&lt;br /&gt;
    &lt;br /&gt;
    ## Checking subsequent blocks: These additionally need to check&lt;br /&gt;
    #    - the reference to the parent block&#039;s hash&lt;br /&gt;
    #    - the validity of the block number&lt;br /&gt;
    for block in chain[1:]:&lt;br /&gt;
        state = checkBlockValidity(block,parent,state)&lt;br /&gt;
        parent = block&lt;br /&gt;
        &lt;br /&gt;
    return state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4 整合：最终的区块链架构 ===&lt;br /&gt;
在实际的区块链网络中，新节点会下载区块链副本并进行验证（就像我们刚才做的那样），然后在点对点网络上宣告自身的存在，并开始监听交易。它们将交易打包成一个区块，然后将自己提议的区块传递给其他节点。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
我们已经了解了如何验证区块链副本，以及如何将交易打包成一个区块。如果我们从其他地方收到一个区块，验证它并将其添加到我们自己的区块链中也很容易。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
假设以下代码在节点 A 上运行，该节点负责挖矿：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import copy&lt;br /&gt;
nodeBchain = copy.copy(chain)&lt;br /&gt;
nodeBtxns  = [makeTransaction() for i in range(5)]&lt;br /&gt;
newBlock   = makeBlock(nodeBtxns,nodeBchain)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;现在假设 &amp;lt;code&amp;gt;newBlock&amp;lt;/code&amp;gt; 被发送到我们的节点，我们想要检查它是否为有效区块，如果是则更新我们的状态：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
print(&amp;quot;Blockchain on Node A is currently %s blocks long&amp;quot;%len(chain))&lt;br /&gt;
&lt;br /&gt;
try:&lt;br /&gt;
    print(&amp;quot;New Block Received; checking validity...&amp;quot;)&lt;br /&gt;
    state = checkBlockValidity(newBlock,chain[-1],state) # Update the state- this will throw an error if the block is invalid!&lt;br /&gt;
    chain.append(newBlock)&lt;br /&gt;
except:&lt;br /&gt;
    print(&amp;quot;Invalid block; ignoring and waiting for the next block...&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;Blockchain on Node A is now %s blocks long&amp;quot;%len(chain))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Blockchain on Node A is currently 7 blocks long&lt;br /&gt;
&lt;br /&gt;
New Block Received; checking validity...&lt;br /&gt;
&lt;br /&gt;
Blockchain on Node A is now 8 blocks long&lt;br /&gt;
&lt;br /&gt;
=== 5 结论与拓展 ===&lt;br /&gt;
我们已经构建了区块链的所有基本架构，从状态转换规则集到区块创建方法，再到用于验证交易、区块和整条链有效性的机制。我们可以从下载的区块链副本中获取系统状态，验证从网络接收的新区块，并创建我们自己的区块。&lt;br /&gt;
&lt;br /&gt;
我们创建的系统状态实际上是一个分布式账本或数据库——这是许多区块链的核心。我们可以扩展它，使其包含特殊交易类型或完整的智能合约。&lt;br /&gt;
&lt;br /&gt;
我们尚未探讨网络架构、工作量证明或状态证明验证步骤，以及为区块链提供抗攻击安全性的共识机制。我们也尚未讨论公钥密码学、隐私和验证步骤。未来我们将对此进行更深入的探讨！&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Web3&amp;diff=27</id>
		<title>分类:Web3</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Web3&amp;diff=27"/>
		<updated>2025-12-24T03:27:32Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“=== 如何构建自己的区块链 ===”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[构建自己的区块链|如何构建自己的区块链]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=26</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=26"/>
		<updated>2025-12-24T03:26:37Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[构建自己的区块链|动手做系列：构建自己的区块链]] ===&lt;br /&gt;
[[分类:Web3]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=25</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=25"/>
		<updated>2025-12-24T02:41:28Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
&lt;br /&gt;
        return PongGame()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:   &lt;br /&gt;
&lt;br /&gt;
    canvas:&lt;br /&gt;
&lt;br /&gt;
        Rectangle:&lt;br /&gt;
&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
&lt;br /&gt;
           &lt;br /&gt;
&lt;br /&gt;
    Label:&lt;br /&gt;
&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
&lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
       &lt;br /&gt;
&lt;br /&gt;
    Label:&lt;br /&gt;
&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
&lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    canvas:&lt;br /&gt;
&lt;br /&gt;
        Rectangle:&lt;br /&gt;
&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    # ...&lt;br /&gt;
&lt;br /&gt;
    Label:&lt;br /&gt;
&lt;br /&gt;
        font_size: 70&lt;br /&gt;
&lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    Label:&lt;br /&gt;
&lt;br /&gt;
        font_size: 70&lt;br /&gt;
&lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
&lt;br /&gt;
    # velocity of the ball on x and y axis&lt;br /&gt;
&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    # referencelist property so we can use ball.velocity as&lt;br /&gt;
&lt;br /&gt;
    # a shorthand, just like e.g. w.pos for w.x and w.y&lt;br /&gt;
&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    # ``move`` function will move the ball one step. This&lt;br /&gt;
&lt;br /&gt;
    #  will be called in equal intervals to animate the ball&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;以下是用于将球画成白色圆圈的 kv 规则：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    size: 50, 50&lt;br /&gt;
&lt;br /&gt;
    canvas:&lt;br /&gt;
&lt;br /&gt;
        Ellipse:&lt;br /&gt;
&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
&lt;br /&gt;
            size: self.size&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== [[Kv 设计语言|4 Kv 设计语言]] ===&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=24</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=24"/>
		<updated>2025-12-24T02:20:57Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* Kivy速成教程 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=23</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=23"/>
		<updated>2025-12-24T02:18:58Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;br /&gt;
&lt;br /&gt;
=== [[Kivy速成教程]]    ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=MediaWiki:Sidebar&amp;diff=22</id>
		<title>MediaWiki:Sidebar</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=MediaWiki:Sidebar&amp;diff=22"/>
		<updated>2025-12-24T02:14:20Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“ * navigation ** mainpage|mainpage-description ** recentchanges-url|recentchanges ** randompage-url|randompage ** specialpages-url|specialpages * SEARCH * TOOLBOX * LANGUAGES”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
* navigation&lt;br /&gt;
** mainpage|mainpage-description&lt;br /&gt;
** recentchanges-url|recentchanges&lt;br /&gt;
** randompage-url|randompage&lt;br /&gt;
** specialpages-url|specialpages&lt;br /&gt;
* SEARCH&lt;br /&gt;
* TOOLBOX&lt;br /&gt;
* LANGUAGES&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=21</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=21"/>
		<updated>2025-12-24T01:53:32Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;code&amp;gt;class PongBall(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # velocity of the ball on x and y axis&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_x = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_y = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # referencelist property so we can use ball.velocity as&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # a shorthand, just like e.g. w.pos for w.x and w.y&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity = ReferenceListProperty(velocity_x, velocity_y)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ``move`` function will move the ball one step. This&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    #  will be called in equal intervals to animate the ball&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    def move(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        self.pos = Vector(*self.velocity) + self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是用于将球画成白色圆圈的 kv 规则：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongBall&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    size: 50, 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Ellipse:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: self.size&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However, that still doesn’t change the fact that we don’t have a reference to the &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; child widget created by the kv rule.  To fix this, we can add an &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; method and even make it bounce off the edges:&lt;br /&gt;
&lt;br /&gt;
然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== [[Kv 设计语言|4 Kv 设计语言]] ===&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E7%AE%97%E6%B3%95&amp;diff=20</id>
		<title>分类:算法</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:%E7%AE%97%E6%B3%95&amp;diff=20"/>
		<updated>2025-12-24T01:48:56Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“=== Kv 设计语言 ===”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[Kv 设计语言]] ===&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=19</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=19"/>
		<updated>2025-12-24T01:45:23Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;br /&gt;
[[分类:算法]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=18</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=18"/>
		<updated>2025-12-24T01:41:52Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[分类:Kivy]]&lt;br /&gt;
[[分类:C/C++]]&lt;br /&gt;
[[分类:Linux操作系统]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=17</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=17"/>
		<updated>2025-12-23T15:19:47Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* Kv 设计语言 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;code&amp;gt;class PongBall(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # velocity of the ball on x and y axis&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_x = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_y = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # referencelist property so we can use ball.velocity as&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # a shorthand, just like e.g. w.pos for w.x and w.y&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity = ReferenceListProperty(velocity_x, velocity_y)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ``move`` function will move the ball one step. This&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    #  will be called in equal intervals to animate the ball&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    def move(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        self.pos = Vector(*self.velocity) + self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是用于将球画成白色圆圈的 kv 规则：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongBall&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    size: 50, 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Ellipse:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: self.size&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However, that still doesn’t change the fact that we don’t have a reference to the &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; child widget created by the kv rule.  To fix this, we can add an &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; method and even make it bounce off the edges:&lt;br /&gt;
&lt;br /&gt;
然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=Kv_%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80&amp;diff=16</id>
		<title>Kv 设计语言</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=Kv_%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80&amp;diff=16"/>
		<updated>2025-12-23T15:16:08Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kivy 提供了一种专门针对易用性和可扩展性而设计的GUI 设计语言。该语言使得界面设计与图形用户界面设计分离变得简单。 应用程序逻辑遵循关注点分离原则(separation of concerns principle)。&lt;br /&gt;
&lt;br /&gt;
[[Category:Kivy]]&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=15</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=15"/>
		<updated>2025-12-23T15:05:40Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* Kv 设计语言 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;code&amp;gt;class PongBall(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # velocity of the ball on x and y axis&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_x = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_y = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # referencelist property so we can use ball.velocity as&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # a shorthand, just like e.g. w.pos for w.x and w.y&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity = ReferenceListProperty(velocity_x, velocity_y)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ``move`` function will move the ball one step. This&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    #  will be called in equal intervals to animate the ball&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    def move(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        self.pos = Vector(*self.velocity) + self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是用于将球画成白色圆圈的 kv 规则：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongBall&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    size: 50, 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Ellipse:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: self.size&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However, that still doesn’t change the fact that we don’t have a reference to the &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; child widget created by the kv rule.  To fix this, we can add an &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; method and even make it bounce off the edges:&lt;br /&gt;
&lt;br /&gt;
然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Kv 设计语言 =&lt;br /&gt;
Kivy 提供了一种专门针对易用性和可扩展性而设计的GUI 设计语言。该语言使得界面设计与图形用户界面设计分离变得简单。 应用程序逻辑遵循关注点分离原则(separation of concerns principle)。&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=14</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=14"/>
		<updated>2025-12-23T15:04:23Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;code&amp;gt;class PongBall(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # velocity of the ball on x and y axis&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_x = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_y = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # referencelist property so we can use ball.velocity as&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # a shorthand, just like e.g. w.pos for w.x and w.y&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity = ReferenceListProperty(velocity_x, velocity_y)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ``move`` function will move the ball one step. This&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    #  will be called in equal intervals to animate the ball&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    def move(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        self.pos = Vector(*self.velocity) + self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是用于将球画成白色圆圈的 kv 规则：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongBall&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    size: 50, 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Ellipse:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: self.size&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However, that still doesn’t change the fact that we don’t have a reference to the &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; child widget created by the kv rule.  To fix this, we can add an &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; method and even make it bounce off the edges:&lt;br /&gt;
&lt;br /&gt;
然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Kv 设计语言 ==&lt;br /&gt;
Kivy 提供了一种专门针对易用性和可扩展性而设计的GUI 设计语言。该语言使得界面设计与图形用户界面设计分离变得简单。 应用程序逻辑遵循关注点分离原则(separation of concerns principle)。&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=Kv_%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80&amp;diff=13</id>
		<title>Kv 设计语言</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=Kv_%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80&amp;diff=13"/>
		<updated>2025-12-23T14:56:50Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​创建页面，内容为“Kivy 提供了一种专门针对易用性和可扩展性而设计的GUI 设计语言。该语言使得界面设计与图形用户界面设计分离变得简单。 应用程序逻辑遵循关注点分离原则(separation of concerns principle)。”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kivy 提供了一种专门针对易用性和可扩展性而设计的GUI 设计语言。该语言使得界面设计与图形用户界面设计分离变得简单。 应用程序逻辑遵循关注点分离原则(separation of concerns principle)。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=12</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=12"/>
		<updated>2025-12-23T14:48:22Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
在进行下一步之前，您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容，弄清楚它的作用。&lt;br /&gt;
&lt;br /&gt;
每个 kv 文件都必须包含第一行。它应该以 &amp;lt;code&amp;gt;#:kivy&amp;lt;/code&amp;gt; 开头。 然后是一个空格，以及它所针对的 Kivy 版本（以便 Kivy 可以创建它）。 请确保您拥有至少所需的版本，或者处理向后兼容性问题。 &lt;br /&gt;
&lt;br /&gt;
之后，我们开始定义适用于所有 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 规则。 实例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
与 Python 类似，kv 文件使用缩进来定义嵌套代码块。用 &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; 字符括起来的类名定义的代码块是一个 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; 规则。它将应用于指定类的任何实例。例如，如果将示例中的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 替换为 &amp;lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; ，则所有 Widget 实例都将包含垂直线和两个 Label 组件，因为该规则将应用于所有 Widget 实例。&lt;br /&gt;
&lt;br /&gt;
在规则部分，您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以：&lt;br /&gt;
&lt;br /&gt;
* 设置属性值&lt;br /&gt;
* 添加子控件&lt;br /&gt;
&lt;br /&gt;
* 定义一个区域，您可以在其中添加定义小部件渲染方式的图形指令。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;&amp;lt;/code&amp;gt; 规则中的第一个块是一个 canvas块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个 canvas 块表示 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件应该绘制一些图形图元。在本例中，我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素，y 坐标设置为 0。矩形的宽度设置为 10 像素，高度设置为组件的高度。这样定义图形的好处在于，当值表达式中使用的任何组件的属性发生变化时，渲染的矩形也会自动更新。&lt;br /&gt;
&lt;br /&gt;
最后添加的两个部分看起来非常相似。它们都向 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 组件添加了一个 Label 组件作为子组件。目前，这两个组件的文本都设置为 &#039;&#039;“0”&#039;&#039; 。我们将在实现逻辑后将其与实际分数关联起来，但由于我们设置了更大的 font_size 并将其相对于根组件定位，因此标签看起来已经不错了。可以在子组件块中使用 &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; 关键字来引用规则所应用的父/根组件（在本例中为 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; ）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 添加球 ====&lt;br /&gt;
我们已经有了一个基本的乒乓球场地，但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球，并让它弹跳起来。&lt;br /&gt;
&lt;br /&gt;
==== PongBall类 ====&lt;br /&gt;
&amp;lt;code&amp;gt;class PongBall(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # velocity of the ball on x and y axis&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_x = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity_y = NumericProperty(0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # referencelist property so we can use ball.velocity as&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # a shorthand, just like e.g. w.pos for w.x and w.y&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    velocity = ReferenceListProperty(velocity_x, velocity_y)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    # ``move`` function will move the ball one step. This&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    #  will be called in equal intervals to animate the ball&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    def move(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        self.pos = Vector(*self.velocity) + self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是用于将球画成白色圆圈的 kv 规则：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongBall&amp;gt;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    size: 50, 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Ellipse:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.pos&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: self.size&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
为了使一切正常运行，您还需要添加以下导入语句： Property classes使用以及 &amp;lt;code&amp;gt;Vector&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以下是此步骤的完整更新后的 Python 代码和 kv 文件：&lt;br /&gt;
&lt;br /&gt;
; main.py：&lt;br /&gt;
; &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import NumericProperty, ReferenceListProperty&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        return PongGame()&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;pong.kv:&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: &amp;quot;0&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;不仅添加了控件规则，还在控件规则中添加了子控件&lt;br /&gt;
&lt;br /&gt;
==== 添加球体动画 ====&lt;br /&gt;
&amp;lt;code&amp;gt;Clock.schedule_interval(game.update, 1.0/60.0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这行代码将导致游戏对象的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 函数每 1/60 秒调用一次&lt;br /&gt;
&lt;br /&gt;
==== 对象属性/引用（Object Properties/References） ====&lt;br /&gt;
还有另一个问题。我们想确保乒乓球拥有它的 &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt; 函数会被定期调用，但由于我们只是通过 KV 文件中 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的 KV 规则添加了球对象，因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。&lt;br /&gt;
&lt;br /&gt;
既然我们要做的功能不只是移动球（例如让球反弹到墙壁上，以及之后反弹到玩家的球拍上），那么我们的 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类可能无论如何都需要一个 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法。此外，鉴于 我们已经有了对游戏对象的引用，我们可以轻松地通过执行&amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法让它执行新任务。 &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        # call ball.move and other stuff&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0/60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However, that still doesn’t change the fact that we don’t have a reference to the &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; child widget created by the kv rule.  To fix this, we can add an &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; method and even make it bounce off the edges:&lt;br /&gt;
&lt;br /&gt;
然而，这仍然无法改变一个事实就是，我们没有由 kv 规则创建的 &amp;lt;code&amp;gt;PongBall&amp;lt;/code&amp;gt; 子组件的引用。为了解决这个问题，我们可以添加一个 &amp;lt;code&amp;gt;ObjectProperty&amp;lt;/code&amp;gt; 将其添加到 PongGame 类，并将其连接到在kv规则中创建的控件。完成之后，我们就可以轻松地引用球的属性了。 在 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法内部，甚至可以使其从边缘反弹：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off top and bottom&lt;br /&gt;
        if (self.ball.y &amp;lt; 0) or (self.ball.top &amp;gt; self.height):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # bounce off left and right&lt;br /&gt;
        if (self.ball.x &amp;lt; 0) or (self.ball.right &amp;gt; self.width):&lt;br /&gt;
            self.ball.velocity_x *= -1&lt;br /&gt;
            &lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    # ... (canvas and Labels)&lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;别忘了在 kv 文件中进行连接，给子控件指定一个 id，并将 PongGame 的 &amp;lt;code&amp;gt;ball&amp;lt;/code&amp;gt; ObjectProperty 设置为该 id。&lt;br /&gt;
&lt;br /&gt;
至此，所有组件都已连接完毕，球可以开始弹跳了。如果您跟着我们一起编写代码，可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中，我们在 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中添加了一个名为 &amp;lt;code&amp;gt;serve_ball&amp;lt;/code&amp;gt; 方法，并在应用程序的 &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度，并重置球的位置，以便我们稍后在玩家得分后重置球的位置。&lt;br /&gt;
&lt;br /&gt;
==== 连接输入事件 ====&lt;br /&gt;
添加玩家并对触摸输入做出反应。&lt;br /&gt;
&lt;br /&gt;
在 Kivy 中，控件可以通过实现以下功能来响应输入： &amp;lt;code&amp;gt;on_touch_down&amp;lt;/code&amp;gt; ， &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 和 &amp;lt;code&amp;gt;on_touch_up&amp;lt;/code&amp;gt; 方法。默认情况下，Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去，直到其中一个子控件返回 &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; 。&lt;br /&gt;
&lt;br /&gt;
Pong 游戏非常简单。球拍只需要上下移动。事实上，它非常简单，我们甚至不需要让玩家组件自己处理事件。我们只需要实现 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 函数即可。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类，并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。&lt;br /&gt;
&lt;br /&gt;
检查 &amp;lt;code&amp;gt;on_touch_move&amp;lt;/code&amp;gt; 处理程序：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def on_touch_move(self, touch):&lt;br /&gt;
    if touch.x &amp;lt; self.width/3:&lt;br /&gt;
        self.player1.center_y = touch.y&lt;br /&gt;
    if touch.x &amp;gt; self.width - self.width/3:&lt;br /&gt;
        self.player2.center_y = touch.y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们会记录每个玩家的得分。 &amp;lt;code&amp;gt;NumericProperty&amp;lt;/code&amp;gt; 。 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 的得分标签 通过更改 NumericProperty &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; 来保持分数更新，这反过来又会更新 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 子标签的 text 属性。这种绑定是因为 Kivy &amp;lt;code&amp;gt;properties&amp;lt;/code&amp;gt; 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时，我们将通过更改 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类中的 &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt; 方法来更新分数并再次发球。PongPaddle 的属性是 &amp;lt;code&amp;gt;PongPaddle&amp;lt;/code&amp;gt; 。 该类还实现了一个 &amp;lt;code&amp;gt;bounce_ball&amp;lt;/code&amp;gt; 方法，以便球能够反弹。 根据击球位置的不同，效果也不同。以下是代码PongPaddle类：&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            speedup  = 1.1&lt;br /&gt;
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)&lt;br /&gt;
            ball.velocity =  speedup * (offset - ball.velocity)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;附全代码:&lt;br /&gt;
&lt;br /&gt;
main.py: &amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
from kivy.app import App&lt;br /&gt;
from kivy.uix.widget import Widget&lt;br /&gt;
from kivy.properties import (&lt;br /&gt;
    NumericProperty, ReferenceListProperty, ObjectProperty&lt;br /&gt;
)&lt;br /&gt;
from kivy.vector import Vector&lt;br /&gt;
from kivy.clock import Clock&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongPaddle(Widget):&lt;br /&gt;
    score = NumericProperty(0)&lt;br /&gt;
&lt;br /&gt;
    def bounce_ball(self, ball):&lt;br /&gt;
        if self.collide_widget(ball):&lt;br /&gt;
            vx, vy = ball.velocity&lt;br /&gt;
            offset = (ball.center_y - self.center_y) / (self.height / 2)&lt;br /&gt;
            bounced = Vector(-1 * vx, vy)&lt;br /&gt;
            vel = bounced * 1.1&lt;br /&gt;
            ball.velocity = vel.x, vel.y + offset&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongBall(Widget):&lt;br /&gt;
    velocity_x = NumericProperty(0)&lt;br /&gt;
    velocity_y = NumericProperty(0)&lt;br /&gt;
    velocity = ReferenceListProperty(velocity_x, velocity_y)&lt;br /&gt;
&lt;br /&gt;
    def move(self):&lt;br /&gt;
        self.pos = Vector(*self.velocity) + self.pos&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongGame(Widget):&lt;br /&gt;
    ball = ObjectProperty(None)&lt;br /&gt;
    player1 = ObjectProperty(None)&lt;br /&gt;
    player2 = ObjectProperty(None)&lt;br /&gt;
&lt;br /&gt;
    def serve_ball(self, vel=(4, 0)):&lt;br /&gt;
        self.ball.center = self.center&lt;br /&gt;
        self.ball.velocity = vel&lt;br /&gt;
&lt;br /&gt;
    def update(self, dt):&lt;br /&gt;
        self.ball.move()&lt;br /&gt;
&lt;br /&gt;
        # bounce off paddles&lt;br /&gt;
        self.player1.bounce_ball(self.ball)&lt;br /&gt;
        self.player2.bounce_ball(self.ball)&lt;br /&gt;
&lt;br /&gt;
        # bounce ball off bottom or top&lt;br /&gt;
        if (self.ball.y &amp;lt; self.y) or (self.ball.top &amp;gt; self.top):&lt;br /&gt;
            self.ball.velocity_y *= -1&lt;br /&gt;
&lt;br /&gt;
        # went off to a side to score point?&lt;br /&gt;
        if self.ball.x &amp;lt; self.x:&lt;br /&gt;
            self.player2.score += 1&lt;br /&gt;
            self.serve_ball(vel=(4, 0))&lt;br /&gt;
        if self.ball.right &amp;gt; self.width:&lt;br /&gt;
            self.player1.score += 1&lt;br /&gt;
            self.serve_ball(vel=(-4, 0))&lt;br /&gt;
&lt;br /&gt;
    def on_touch_move(self, touch):&lt;br /&gt;
        if touch.x &amp;lt; self.width / 3:&lt;br /&gt;
            self.player1.center_y = touch.y&lt;br /&gt;
        if touch.x &amp;gt; self.width - self.width / 3:&lt;br /&gt;
            self.player2.center_y = touch.y&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class PongApp(App):&lt;br /&gt;
    def build(self):&lt;br /&gt;
        game = PongGame()&lt;br /&gt;
        game.serve_ball()&lt;br /&gt;
        Clock.schedule_interval(game.update, 1.0 / 60.0)&lt;br /&gt;
        return game&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    PongApp().run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;pong.kv:&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#:kivy 1.0.9&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongBall&amp;gt;:&lt;br /&gt;
    size: 50, 50 &lt;br /&gt;
    canvas:&lt;br /&gt;
        Ellipse:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size          &lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongPaddle&amp;gt;:&lt;br /&gt;
    size: 25, 200&lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.pos&lt;br /&gt;
            size: self.size&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PongGame&amp;gt;:&lt;br /&gt;
    ball: pong_ball&lt;br /&gt;
    player1: player_left&lt;br /&gt;
    player2: player_right&lt;br /&gt;
    &lt;br /&gt;
    canvas:&lt;br /&gt;
        Rectangle:&lt;br /&gt;
            pos: self.center_x - 5, 0&lt;br /&gt;
            size: 10, self.height&lt;br /&gt;
    &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player1.score)&lt;br /&gt;
        &lt;br /&gt;
    Label:&lt;br /&gt;
        font_size: 70  &lt;br /&gt;
        center_x: root.width * 3 / 4&lt;br /&gt;
        top: root.top - 50&lt;br /&gt;
        text: str(root.player2.score)&lt;br /&gt;
    &lt;br /&gt;
    PongBall:&lt;br /&gt;
        id: pong_ball&lt;br /&gt;
        center: self.parent.center&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_left&lt;br /&gt;
        x: root.x&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
        &lt;br /&gt;
    PongPaddle:&lt;br /&gt;
        id: player_right&lt;br /&gt;
        x: root.width - self.width&lt;br /&gt;
        center_y: root.center_y&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=11</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=11"/>
		<updated>2025-12-23T13:51:47Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
=== 3. 第一个应用 ===&lt;br /&gt;
&lt;br /&gt;
==== 入门 ====&lt;br /&gt;
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录，并在其中创建一个名为 &#039;&#039;main.py 的&#039;&#039;文件。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.app import App&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;from kivy.uix.widget import Widget&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongGame(Widget):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;class PongApp(App):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;def build(self):&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;return PongGame()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;if __name__ == &#039;__main__&#039;:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PongApp().run()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; ，它创建了 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; Widget 类的一个实例，并将其作为应用程序 UI 的根元素返回。此时，您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步，我们将通过定义 &amp;lt;code&amp;gt;PongGame widget&amp;lt;/code&amp;gt; 外观来绘制 Pong 的背景和分数。&lt;br /&gt;
&lt;br /&gt;
==== 添加简单图形 ====&lt;br /&gt;
我们将使用 .kv 文件来定义 &amp;lt;code&amp;gt;PongGame&amp;lt;/code&amp;gt; 类的外观和风格。由于我们的 &amp;lt;code&amp;gt;App&amp;lt;/code&amp;gt; 类名为 &amp;lt;code&amp;gt;PongApp&amp;lt;/code&amp;gt; ，我们可以直接在应用程序运行时自动加载同一目录下的 &amp;lt;code&amp;gt;pong.kv&amp;lt;/code&amp;gt; 文件。因此，请创建一个名为 &#039;&#039;``pong.kv``&#039;&#039; 的新文件，并添加以下内容。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#:kivy 1.0.9&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;PongGame&amp;gt;:   &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    canvas:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        Rectangle:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            pos: self.center_x - 5, 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;            size: 10, self.height&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;           &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;       &amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;    Label:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        font_size: 70  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        center_x: root.width * 3 / 4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        top: root.top - 50&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;        text: &amp;quot;0&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;kv 文件的名称（例如 pong.kv）必须与应用程序的名称（例如 PongApp）匹配（App 结尾之前的部分）。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 解释 Kv 文件语法 ====&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=10</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=10"/>
		<updated>2025-12-23T13:35:46Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​/* 1、引言 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 创建虚拟环境 ====&lt;br /&gt;
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 &#039;&#039;&#039;，但强烈建议这样做&#039;&#039;&#039; ：&lt;br /&gt;
&lt;br /&gt;
在当前目录下创建名为 &amp;lt;code&amp;gt;kivy_venv&amp;lt;/code&amp;gt; 虚拟环境：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m venv kivy_venv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 激活虚拟环境 ====&lt;br /&gt;
&amp;lt;code&amp;gt;source kivy_venv/bin/activate&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;（linux)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 安装Kivy ====&lt;br /&gt;
最简单的方法是安装当前稳定版本的 &amp;lt;code&amp;gt;kivy&amp;lt;/code&amp;gt; ，还可以选择 &amp;lt;code&amp;gt;kivy_examples&amp;lt;/code&amp;gt; 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 通过源安装 ====&lt;br /&gt;
略&lt;br /&gt;
&lt;br /&gt;
==== 安装预发布版、预编译的 wheel 文件 ====&lt;br /&gt;
要安装 Kivy 最新&#039;&#039;&#039;预发布&#039;&#039;&#039;版本的预编译 wheel 文件，而不是当前稳定版本，请在 pip 命令中添加 &amp;lt;code&amp;gt;--pre&amp;lt;/code&amp;gt; 标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --pre &amp;quot;kivy[base]&amp;quot; kivy_examples&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
只有当 Kivy 发布了开发版本时，此操作才会安装该开发版本。 PyPi 。或者，也可以从 Kivy 服务器安装&#039;&#039;&#039;最新&#039;&#039;&#039;的 Nightly wheel 包：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install kivy --pre --no-deps --index-url  &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install &amp;quot;kivy[base]&amp;quot; --pre --extra-index-url &amp;lt;nowiki&amp;gt;https://kivy.org/downloads/simple/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 开发安装 ====&lt;br /&gt;
略&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=9</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=9"/>
		<updated>2025-12-23T06:58:08Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​Kivy速成教程&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;学习&#039;&#039;&#039;:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;探索&#039;&#039;&#039;:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;创建&#039;&#039;&#039;:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;打包&#039;&#039;&#039;:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=8</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=8"/>
		<updated>2025-12-23T05:54:27Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Kivy速成教程 =&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;&#039;1、引言&#039;&#039;&#039; ===&lt;br /&gt;
Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
遵循本指南以获取您所需的工具,了解主要概念 学习最佳实践。因为这是一个引言,指向更多信息 将在每节末尾提供。&lt;br /&gt;
&lt;br /&gt;
在查看指南时,您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Learn&#039;&#039;&#039;学习:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;Explore&#039;&#039;&#039;探索:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;Create&#039;&#039;&#039;创建:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;Package&#039;&#039;&#039;套餐:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后, &#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
=== 2、安装 Kivy ===&lt;br /&gt;
本文Kivy版本基于Kivy 2.3.1 ，支持 Python 版本 &#039;&#039;&#039;3.8 - 3.&#039;&#039;&#039;13&lt;br /&gt;
&lt;br /&gt;
==== 使用 pip安装 ====&lt;br /&gt;
安装 Kivy 最简单的方法就是&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新&amp;lt;code&amp;gt;pip&amp;lt;/code&amp;gt;以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代&amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;而不是&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;并添加一个&amp;lt;code&amp;gt;--user&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;flag。&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Windows:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;linux:&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;python&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;3 -m pip install --upgrade pip setuptools virtualenv&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=7</id>
		<title>分类:Kivy</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E5%88%86%E7%B1%BB:Kivy&amp;diff=7"/>
		<updated>2025-12-23T05:43:01Z</updated>

		<summary type="html">&lt;p&gt;Xlong：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kivy是用Python编写的开源跨平台GUI框架，用于开发多平台应用程序（如Windows、macOS、Linux、Android、iOS等）。&lt;br /&gt;
&lt;br /&gt;
它基于OpenGL ES 2构建，采用自绘UI的方式替代依赖原生系统组件，因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言，允许开发者将UI布局与业务逻辑分离，简化复杂界面的设计流程；同时原生支持多点触控、手势识别等交互特性，非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外，Kivy拥有活跃的社区生态，提供了丰富的扩展库（如KivyMD等Material Design风格组件库），帮助开发者快速实现美观且功能完善的跨平台应用。&lt;br /&gt;
&lt;br /&gt;
使用 Kivy,您可以创建运行以下应用程序:&lt;br /&gt;
&lt;br /&gt;
* *台式电脑:macOS、Linux、BSD Unix、Windows。&lt;br /&gt;
* iOS 设备:iPad、iPhone。&lt;br /&gt;
* 安卓设备:平板电脑、手机。&lt;br /&gt;
* 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)&lt;br /&gt;
&lt;br /&gt;
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。&lt;br /&gt;
&lt;br /&gt;
遵循本指南以获取您所需的工具,了解主要概念 学习最佳实践。因为这是一个引言,指向更多信息 将在每节末尾提供。&lt;br /&gt;
&lt;br /&gt;
在查看指南时,您将使用 Kivy:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Learn&#039;&#039;&#039;学习:使用Kivy语言编程的基础知识。&lt;br /&gt;
* &#039;&#039;&#039;Explore&#039;&#039;&#039;探索:Kivy 框架。&lt;br /&gt;
* &#039;&#039;&#039;Create&#039;&#039;&#039;创建:一个简单的跨平台应用程序。&lt;br /&gt;
* &#039;&#039;&#039;Package&#039;&#039;&#039;套餐:适用于您选择的平台。&lt;br /&gt;
&lt;br /&gt;
最后,&#039;&#039;&#039;Deploy&#039;&#039;&#039;您将学习如何在您选择的设备上部署。&lt;br /&gt;
&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
</feed>