K8S之网络深度剖析(一)(持续更新ing)

news/2024/7/7 16:26:49 标签: kubernetes, 网络, 容器

K8S之网络深度剖析

一 、关于K8S的网络模型

在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为IP-per-Pod模型。

Kubernetes对集群网络有如下要求:

  • 所有容器都可以在不用NAT的方式下同别的容器通讯。
  • 所有节点都可以在不用NAT的方式下同别的容器通讯。
  • 容器的地址和别人看到的地址是同一个地址。

二、Docker 网络

Docker 使用到的与Linux网络有关的主要技术:

  • 网络命名空间(Network Namespace)
  • Veth 设备对
  • 网桥
  • Iptables
  • 路由

2.1 网络命名空间

为了支持网络协议栈中的多个实例,Linux 2.6.24内核版本的网络协议栈中引入了网络命名空间,这些独立的协议栈被隔离到不用的命名空间中。处于不用命名空间中的网络栈是完全隔离的,彼此之间无法通讯,就好像两个"平行宇宙"。通过对于网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境。Docker 正是利用了网络的命名空间特性,实现了不同容器之间的网络隔离。

在Linux的网络命名空间中可以有自己独立的路由表,及独立的Iptables设置来提供包转发、NAT、及IP包过滤等功能。

为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建的套接字必须属于某个网络命名空间,套接字的操作也必须在命名空间中进行。同样,网络设备也必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空间之间移动。当然,是否允许移动,和设备的特征有关系。

2.1.1 网络命名空间的实现

Linux的网络协议栈是十分复杂的,为了支持独立的协议栈,相关的这些全局变量都必须被修改为协议栈私有,最好的办法就是让这些全局变量成为一个 Net NameSpace变量的成员,然后修改协议栈的函数调用,在加入一个NameSpace的参数,这就是Linux 实现网络命名空间的核心。

同时,为了保证对已经开发的应用程序,以及内核代码的兼容性,内核代码隐式地使用了网络命名空间中的变量,程序如果没有对网络命名空间有特殊需求,就不需要编写额外的代码,网络命名空间对应用程序而言是透明的。

在建立了新的网络命名空间,并将某个进程关联到这个网络命名空间后,就出现了如下图的数据结构,所有的网络栈变量都没放到了私有的命名空间,和其他进程组并不冲突。

在这里插入图片描述
上图来自网络命名空间深度好文

在新生成的理由命名空间中,只有回环设备,(名为"lo" 且是停止状态),其他设备默认都不存在,如果我们需要,则要一一手工建立,Docker容器中的各类网络栈设备都是Docker Daemon 在启动时自动创建和配置的。

所有的网络设备(物理的或虚拟接口、桥等在内核里都叫做Net Device)都只能属于一个命名空间、当然,物理设备(连接实际硬件的设备)通常只能关联到root 这个命名空间。虚拟的网络设备(虚拟的以太网接口或者虚拟网口对)则可以被关联到一个给定的命名空间,而且可以在这些命名空间中移动。

前面提到,由于网络命名空间代表的是一个独立的协议栈,所以它们之间是相互隔离的,彼此无法通讯,在协议栈内部都看不到对方, 那么有没有办法打破这种限制,让处于不同网络命名空间的网络互相通讯,甚至和外部的网络通讯呢? 答案就是“有”,应用 “Veth设备对即可”, Veth 设备对一个重要的作用就是打通互相看不到的协议栈的协议栈之间的壁垒,他就像一条管子,一端连着这个网络命名空间的协议栈,一端连着另一个网络命名空间协议栈,所以想要在两个命名空间之间通讯,就必须有一个 Veth 设备对。

2.1.2 网络命名空间的操作

我们可以使用 Linux iproute2 系列配置工具中的IP 命令来操作网络命名空间,注意: 这个命令需要使用root 用户来执行。

安装:

# alphine
apk add iproute2

# ubuntu
sudo apt install iproute2

# centos
sudo yum -y install iproute2

创建一个网络命名空间:

ip netns add <name>

创建一个名为test1 的网络命名空间

ip netns add test1

在命名空间中执行命令:

ip netns exec <name> <command>

在命名空间test1 执行 ip a s 命令

root@test:~# ip netns exec test1 ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

也可以先通过切换到对应的网络命名空间,执行各种命令:

ip netns exec <name> bash

执行 exit 或Ctrl +d 退出当前网络命名空间。
上面的操作相当于网络命名空间进行了切换,文件系统还是当前,在Docker 中,其实也做了类似操作,并且将文件系统通过chroot 进行了切换,通过cgroup 对资源进行了隔离。

2.1.3 网络命名空间的实用技巧

操作系统网络命名空间的一些技巧如下:

我们可以在不同的网络命名空间之间转移设备,列如下面会提到的Veth 设备对的转移,因为一个设备只能属于一个命名空间,所以转移后在这个网络命名空间内就看不到这个设备了,具体哪些设备能被转移到不同的网络命名空间, 在设备里面有一个属性: NETIF_F_NETNS_LOCAL,如果这个属性为 on,就不能被转移到其他命名空间中。Veth 设备属于可转移设备,而其他设备如,lo 设备,vxlan,ppp 设备,bridge设备等都是不可转移的。将无法转移的设备移动到别的命名空间时,会得到无效的参数错误提示。

root@test1:~# ip link set lo netns test1
RTNETLINK answers: Invalid argument

如何知道这些设备是否可以转移呢? 可以使用ethtool 工具查看:

root@test1:~#  apt install ethtool
root@test1:~# ethtool -k docker0  | grep netns
netns-local: on [fixed]
root@test1:~# ethtool -k veth276e185  | grep netns
netns-local: off [fixed]
2.1.4 使用nsenter 工具来切换网络命名空间

执行lsns 查看当前主机的namespace

root@test1:~# lsns 
        NS TYPE   NPROCS     PID USER             COMMAND
4026531834 time      314       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531835 cgroup    312       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531836 pid       312       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531837 user      313       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531838 uts       308       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531839 ipc       312       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531840 net       308       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531841 mnt       297       1 root             /lib/systemd/systemd --system --deserialize 35 splash
4026531862 mnt         1      62 root             kdevtmpfs
4026532391 mnt         1     296 root             /lib/systemd/systemd-udevd
4026532392 uts         1     296 root             /lib/systemd/systemd-udevd
4026532421 mnt         1     471 systemd-resolve  /lib/systemd/systemd-resolved
4026532422 mnt         1     472 systemd-timesync /lib/systemd/systemd-timesyncd
4026532423 mnt         1 2540333 systemd-oom      /lib/systemd/systemd-oomd
4026532424 uts         1 2540333 systemd-oom      /lib/systemd/systemd-oomd
4026532425 uts         1     472 systemd-timesync /lib/systemd/systemd-timesyncd
4026532426 mnt         1   10214 root             /lib/systemd/systemd-logind
4026532427 uts         1   10214 root             /lib/systemd/systemd-logind
4026532430 mnt         1  995086 fwupd-refresh    /usr/bin/fwupdmgr refresh
4026532448 mnt         1 2435299 root             sh
4026532449 uts         1 2435299 root             sh
4026532450 ipc         1 2435299 root             sh
4026532451 pid         1 2435299 root             sh
4026532452 net         1 2435299 root             sh
4026532483 mnt         1   30455 root             /usr/libexec/accounts-daemon
4026532484 net         1   30455 root             /usr/libexec/accounts-daemon
4026532485 net         1   10330 rtkit            /usr/libexec/rtkit-daemon
4026532539 mnt         1     525 root             /usr/sbin/irqbalance --foreground
4026532542 mnt         2 1469053 root             /snap/snapd-desktop-integration/157/usr/bin/snapd-desktop-integration
4026532545 mnt         1   10629 root             /usr/libexec/upowerd
4026532546 mnt         1   10631 root             /usr/libexec/power-profiles-daemon
4026532547 user        1   10629 root             /usr/libexec/upowerd
4026532549 mnt         1   10715 root             /usr/sbin/ModemManager
4026532628 cgroup      1 2435299 root             sh
4026532631 mnt         1  380128 root             sh
4026532632 uts         1  380128 root             sh
4026532633 ipc         1  380128 root             sh
4026532634 pid         1  380128 root             sh
4026532635 net         3  380128 root             sh
4026532665 mnt         1   11076 colord           /usr/libexec/colord
4026532692 cgroup      1  380128 root             sh

这里可以看到pid 380128 容器的namespace,主要为以下几个类型:

  • mount
  • uts
  • ipc
  • pid
  • network
  • user

我们可以通过如下的方法对容器进行抓包:
获取容器的Pid

root@test1:~# docker inspect  -f "{{.State.Pid}}" a19dee489a47 
380128

可以看到有多个namespace

ls -l /proc/380128/ns/
total 0
lrwxrwxrwx 1 root root 0  7月  2 22:19 cgroup -> 'cgroup:[4026532692]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 ipc -> 'ipc:[4026532633]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 mnt -> 'mnt:[4026532631]'
lrwxrwxrwx 1 root root 0  6月 28 15:56 net -> 'net:[4026532635]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 pid -> 'pid:[4026532634]'
lrwxrwxrwx 1 root root 0  7月  2 22:21 pid_for_children -> 'pid:[4026532634]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0  7月  2 22:21 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0  7月  2 22:19 uts -> 'uts:[4026532632]'

进入对应Pid 的Network Namespace

root@test1:~# nsenter -t 380128 -n 
root@test1:~# ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
76: eth0@if77: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

nsenter 工具详解及 namespace 背景

2.2 Veth 设备对

Veth设备对的出现是为了解决不同的网络命名空间之间通讯,利用它可以将两个网络命名空间进行连接,由于要连接多个网络命名空间,所以Veth 设备基本都是成对出现的,很像一对以太网卡,并且中间有一根直连的网线。既然是一对网卡,我们将其中一端称之为另一端的peer。在veth 另一端发送数据时,它会将数据直接发送到另一端,并触发另一端的接收操作。

veth设备示意图

2.2.1 Veth 设备对的操作命令

创建veth设备对:

root@test1:~# ip link add veth0 type veth peer name veth1

创建后,使用ip link show 查看所有网络接口

root@test1:~# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp6s18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 2e:8d:36:b4:77:40 brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 02:42:ae:2b:65:9a brd ff:ff:ff:ff:ff:ff
69: veth9b5ea1d@if68: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether 7e:29:46:4f:52:9e brd ff:ff:ff:ff:ff:ff link-netnsid 0
77: veth276e185@if76: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether 0e:73:3e:73:b5:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
82: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 6e:3b:cf:dc:10:a6 brd ff:ff:ff:ff:ff:ff
83: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether da:61:bf:d0:bf:25 brd ff:ff:ff:ff:ff:ff

可以看到,上面的命令生成了两个网络接口 一个是veth1 他的peer 是veth0,现在两个设备都在默认的命名空间,我们将其中一个设备加入到另一个命名空间

root@test1:~# ip link set veth1 netns test1

这时在外面看两个设备的情况

root@test1:~# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp6s18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 2e:8d:36:b4:77:40 brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 02:42:ae:2b:65:9a brd ff:ff:ff:ff:ff:ff
69: veth9b5ea1d@if68: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether 7e:29:46:4f:52:9e brd ff:ff:ff:ff:ff:ff link-netnsid 0
77: veth276e185@if76: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether 0e:73:3e:73:b5:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
83: veth0@if82: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether da:61:bf:d0:bf:25 brd ff:ff:ff:ff:ff:ff link-netns test1

只剩下一个veth 设备了,已经看不到另一个设备了,另一个设备已经被转移到了test1的网络命名空间中了。

在test1的网络命名空间中可以看到veth1 设备,符合预期

root@test1:~# ip netns exec test1 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
82: veth1@if83: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 6e:3b:cf:dc:10:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 0

现在我们看到,两个不用的命名空间中有一个veth 设备,在Docker的实现里面,它除了将veth 放入到容器内,还将它的名字改成了eth0

现在我们给他分配一个IP地址,让他们可以通讯

root@test1:~# ip netns exec test1 ip addr add 10.1.1.1/24 dev veth1
root@test1:~# ip addr add 10.1.1.2/24 dev veth0

在启动他们:

root@test1:~# ip netns exec test1 ip link set dev veth1 up
root@test1:~# ip link set dev veth0 up

现在两个网络命名空间可以互相通讯了

root@test1:~# ping 10.1.1.1 
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.074 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.064 ms
root@test1:~# ip netns exec test1  ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.130 ms

在Docker内部,Veth 设备是连通容器与宿主机网络的主要设备。

2.2.2 veth 设备如何查看对端

一旦将veth 设备对的另一端放入另一个命名空间,在本命名空间就看不到它了,那我们如何知道设备的对端在哪里呢?可以使用ethtool 工具来查看(当网络命名空间较多时,这个也不太容器)。

首先,在命名空间test1中查询Veth设备对端接口在设备列表中的序列号:

root@test1:~# ip netns exec test1 ethtool -S veth1
NIC statistics:
     peer_ifindex: 83

获取另一端的接口序列号是83,我们在查看序列号83是什么设备

root@test1:~# ip link | grep 83
83: veth0@if82: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

veth 参考资料

2.3 网桥

Linux可以支持多个不同的网络,他们之间可以相互通讯,如何将这些网络连接起来,并实现各网络中主机的相互通讯? 可以用网桥,网桥是一个二层的虚拟网络设备,把若干个网络接口”连接“起来,使得网络接口之间得报文能够互相转发,网桥能够解析收发得报文,读取目标MAC地址得信息,和自己记录得Mac表结合,来决策报文的转发目标网络接口,为了实现这些功能,网桥会学习源MAC地址(二层网桥转发得依据就是MAC地址),在转发报文时,网桥只需要向特定得网口进行转发,来避免不必要得网络交互,如果他遇到一个自己从未学习过得地址,就无法知道这个报文需要转发给哪个网络接口,就将报文广播给所有得网络接口(报文来源得接口除外)。

在实际得网络中,网络拓扑不可能永远不变,设备如果被移动到另一个端口上,却没有发送任何数据,网桥设备就无法感知这个变化,网桥还是向原来得端口转发数据时,这种情况数据就会丢失。所以网桥还要对学习到的MAC地址表加上超时时间(默认为5min)。如果网桥收到了对应带你看MAC地址回发的数据包,则重置超时时间,否则过了超时时间后,就认为设备已经不再那个端口上,它就会重新发送广播。

Linux内核支持网口的桥接(目前只支持以太网接口),但是与单纯的交换机不同,交换机只是一个二层设备,对于接收到的报文,要么转发,要么丢弃.运行着Linux 内核的机器本身就是一台主机,有可能时网络报文的目的地,其收到的报文除了转发和丢弃,还有可能会被送到网络协议栈的上层(网络层),从而被自己(这台主机本身的协议栈)消化,所以我们既可以把他当作一个二层设备,也可以当作一个三层设备。

2.3.1 Linux 网桥的实现

Linux 内核是通过一个虚拟的网桥设备(Net Device)来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起来, Net Device 和普通的设备,最明显的特征是它还可以有一个IP地址。
在这里插入图片描述
网桥的参考资料
如图所示,网桥设备绑定了 veth0 ,veth1。对于网络协议栈的上层来说,只看得到网桥就可以。因为桥接实在数据链路层实现的,上层不需要关心桥接的细节,所以协议栈上层需要发送的报文被送到 网桥,网桥设备判断报文是被转发到 veth0还是 veth1,或者两者皆转发,反过来从eth0 或从eth1 接收到的报文被提交给网桥的,在这里会判断应该被转发、丢弃、还是协议栈上层。

而有时,veth0,veth1 也可能作为报文的源地址或者目的地址,直接参与报文的发送与接收,从而绕过网桥

2.3.2 网桥的常用操作命令

Docker自动完成了对网桥的创建和维护。为了进一步理解网桥,下面举几个常用的网桥的例子,对网桥进行手工操作:

bridge-utils包中的 brctl 用来管理以太网桥,在内核中建立、维护、检查网桥配置。一个网桥一般用来连接多个不同的网络,这样这些不同的网络就可以像一个网络那样进行通讯。

网桥是一种在链路层实现中继,对帧进行转发的技术,根据MAC分区块,可隔离碰撞,将网络的多个网段在数据链路层连接起来的网络设备。网桥工作在数据链路层,将两个LAN连起来,根据MAC地址来转发帧,可以看作一个“底层的路由器”。

在网桥上每个以太网连接可以对应到一个物理接口,这些以太网接口组合成一个大的逻辑的接口,这个逻辑接口对应于桥接网络

root@test1:~# apt-get install bridge-utils -y 

http://www.niftyadmin.cn/n/5534630.html

相关文章

智能猫砂盆到底哪家好用?自费实测聚宠、糯雪、CEWEY真实反馈!

快到夏天了&#xff0c;是不是还有人因为没挑选到喜欢的智能猫砂盆而苦恼着&#xff1f;太便宜怕不好用&#xff0c;太贵怕质量比不上价格。来来去去拖到现在还没决定&#xff0c;我作为养了四年猫的资深铲屎官&#xff0c;今天就来给大家传授经验&#xff0c;关于我是怎么从好…

gin框架 HTML 模板加载,渲染 使用详解和总结

gin框架中默认的HTML模板渲染使用 LoadHTMLGlob() 或者 LoadHTMLFiles() &#xff0c; 这个地方如果是使用的LoadHTMLGlob() 这个方法的话是有坑的&#xff0c;即当你的模板文件放在不同的文件夹中时&#xff0c;使用这个方式加载会将文件夹也作为文件加载进去&#xff0c;从而…

实战篇:GY-906红外测温模块 + 万年历(定时器计数中断版本) -STM32篇

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 向上代码兼容GD32F450ZGT6中使用 后续项目主要在下面该专栏中发布&#xff1a; https://blog.csdn.net/qq_62316532/category_12608431.html?spm1001.2014.3001.5482 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转…

农业四情监测设备——提高农业生产的效率和质量

TH-Q1农业四情监测设备是用于实时监测农业领域的四大关键监测内容的设备&#xff0c;这些内容包括土壤墒情、苗情、病虫情和灾情。以下是关于农业四情监测设备的详细介绍&#xff1a; 主要用于实时测量农田土壤的水分状况。包含土壤湿度传感器、土壤温度传感器等&#xff0c;安…

32 - 判断三角形(高频 SQL 50 题基础版)

32 - 判断三角形 select *,if(xy>z and xz>y and zy > x,Yes,No) triangle fromTriangle;

[保姆级教程]uniapp小程序获取右上角胶囊位置信息

文章目录 导文使用uni.getMenuButtonBoundingClientRect();方法实现完整案例 隐藏默认导航栏&#xff1a;全局隐藏当前页面隐藏 导文 uniapp小程序获取右上角胶囊位置信息 使用uni.getMenuButtonBoundingClientRect();方法实现 <script>const menuButtonInfo uni.getMe…

IOS Swift 从入门到精通:闭包 第一部分

文章目录 创建基本闭包在闭包中接受参数从闭包返回值闭包作为参数尾随闭包语法创建基本闭包 Swift 允许我们像使用字符串和整数等其他类型一样使用函数。这意味着您可以创建一个函数并将其分配给一个变量,使用该变量调用该函数,甚至可以将该函数作为参数传递给其他函数。 以…

C++ 结构体对齐详解

目录 前言 一、为什么要对结构体进行对齐操作&#xff1f; 二、基本概念 三、 对齐规则 四、示例讲解 1.简单的变量对齐 2.结构体包含有结构体的对齐 结构体成员详细解析 五、使用指令改变对齐方式 __attribute__((packed)) #pragma pack(push, n) #pragma pack(pop) …