背景:

本文开始引入一个新的专题——云原生容器化,用于收集云和容器化相关的文章;
以Docker和Kubernates的组成、实现原理、常见操作为主体内容,还会涉及一些云的概念;初步计划会整理出以下文章:
1.云原生容器化-1 Linux虚拟网络介绍
2.云原生容器化-2 Docker网络原理
3.云原生容器化-3 Docker组成与实现原理
4.云原生容器化-4 Kubernates组成介绍
5.云原生容器化-5 Kubernates网络插件:flannel与calico
6.云原生容器化-6 Kubernates挂卷
7.云原生容器化-7 Kubernates常见资源(可细分)
8.云原生容器化-8 Kubernates常见操作(可细分)
9.云原生容器化-9 Kuboard配置日志-代理-远程debug
10.云原生容器化-10 Iaas-Paas-Saas

由于学习和专题整理基本都是在周末进行,预期会持续2个月;欢迎各位道友订阅和指点。

摘要

本文是云原生容器化专题的开篇,将从Linux网络的角度介绍一下常见的概念,包括:bridge, veth-pair, tun/tap, IP隧道, iptables/netfilter和路由表等。其中穿插引入几个Demo,用于加深对这些网络设备的认识。

1.Linux网络结构图

Linux网络整体结构图如上所示,由以下几个部分组成:
[1] socket:包括UnitSocket和InetSocket; 抽象层接口BSD Socket用于与用户程序交互;其中InetSocket可用于网络通讯和本机通讯;UnitSocket仅用于本机通讯;
[2] 内核协议栈:包括TCP/IP协议以及netfilter/iptables框架和路由,该模块具有路由和转发的功能,也是本文的重点部分;
[3] 网卡驱动以及网络管理层:所有的网卡(物理网卡或者虚拟网卡),都需要网卡驱动才能工作,并为网络管理层提供可调用接口;网络管理层的存在使得物理网卡和虚拟网卡对网络协议栈没有区别。
[4] 网卡:包括物理网卡和虚拟网卡,本文涉及tun, veth-pair, bridge等;其中tun使得用户空间拥有操作数据包的能力。
下文将将围绕着该图进行介绍。

2.网络命名空间

Linux通过引入namespace概念以隔离全局资源实现虚拟化,使得处于不同namespace的元素相互不可感知、互不干扰。
如下图所示,Linux中存在以下6种类型的namespace:

  • 进程命名空间:隔离进程,每个空间的进程ID都可以从1开始
  • IPC命名空间:隔离进程间通信 ,与进程命名空间一起使用
  • 挂载命名空间:隔离文件目录
  • UTS命名空间:隔离Hostname和域名
  • 用户命名空间:隔离用户和group ID
  • 网络命名空间:隔离网络,协议栈、路由、IP、端口等;

这些namespace是容器相互隔离的底层原理; 配合cgroup的资源限制,使得容器可以以沙箱的形式存在; 其中,网络namespace可以实现网络模块资源的隔离,包括:网络协议栈网络设备接口IP路由表防火墙规则端口等。
相互隔离的namespace可以通过Linux提供的虚拟网络设备相连,如可以通过veth-pair(虚拟网线)将多个namespace连接到同一bridge(网桥)上进行通讯,veth-pair和bridge在后文介绍。

3.tap/tun

3.1 tap/tun概念:

tap/tun是Linux内核提供的虚拟网络设备(软件模拟),提供了用户空间与内核交换数据包的能力。tap与tun工作原理相似,区别是tap工作在链路层,而tun工作在IP层。
由于后续介绍的IP隧道以及Kubernates的calico插件涉及到tun设备,因此本文以tun为代表进行介绍。
内核通过向用户空间提供一个可读写的字符设备实现信息交换:

当用户空间打开tun字符设备时,内核自动创建一个tun0网卡(打开多次,会创建多个网卡,命名依次为:tun0,tun1,tun2,…),系统自动实现/dev/net/tun和tun0网卡的绑定:
(1) 协议栈向tun0网卡发送数据包时,用户可以从/dev/net/tun中读取数据包;(2) 当用户向/dev/net/tun字符设备写入数据时,数据包会经过tun0网卡发送到网络协议栈;
即:/dev/net/tun是沟通用户态与协议栈的桥梁;通过tun用户空间拥有了直接向内核协议栈发送数据包的能力,内核可以以此为基础继续扩展网络能力。

3.2 案例介绍

案例涉及代码的主体片段如下所示 (完整代码见附录1):

// 该案例代码来源于网络: [未找到出处,请作者联系标记]:
int main() {tun_fd = open("/dev/net/tun", O_RDWR)while (1) {nread = read(tun_fd, buffer, sizeof(buffer));printf("read %d bytes from %s\n", nread, ifr.ifr_name);//nread = write(tun_fd, buffer, nread);//printf("Write %d bytes to tun/tap device, that's %s\n", nread, buffer);}return 0;
}

操作步骤如下:

step1.修改后重新编译tun.c

gcc tun.c -o tun

step2.执行tun程序

./tun

执行后,系统生成一个tun0网卡:

step3.配置tun的IP以及状态;

ip address add 10.10.10.10/24 dev tun0
ip link set tun0 up

当激活tun0网卡后,系统在路由表中新增一条规则:

该规则表示目标地址为10.10.10.0/24的数据包会通过tun0发送;

step4. ping 10.10.10.11
由于协议栈任务10.10.10.10是本地地址,ping 10.10.10.10时,会走lo网卡,ping流程不经过tun0网卡;当ping 10.10.10.0/24时, 根据上述的路由规则,会将数据包丢给tun0, 这里选择ping 10.10.10.11地址。
ping 10.10.10.11时程序日志如下:

表明:数据包已经从tun0到达了用户空间;

step5.释放上述代码的后续注释部分:
重新编译-执行,发现可以ping通:

程序的日志部分如下所示:

表明:数据包从用户空间发送到了tun0网卡;
*注: 注释部分的内容是手动构建ICMP-reply数据包,并写入到/dev/net/tun字符设备;

step6.停止程序
停止程序后,系统自动删除tun0网卡以及路由信息;

过程分析:
(1)程序以读写方式打开/dev/net/tun字符设备,此时系统会自动创建一个tun0网卡;
(2)程序轮询/dev/net/tun字符设备并读取数据,当tun0收到来自协议栈的数据包时,会通过/dev/net/tun发送到用户空间;
(3)程序构建ICMP-reply包并写入到字符设备/dev/net/tun后,tun0会通过/dev/net/tun收到该数据包,然后tun0会提交给协议栈;协议栈继续向上传递至socket,从而完成PING的整个流程;
上述整个流程可用下图表示:

4.veth-pair

4.1 概念:

veth-pair是Linux提供的虚拟网卡,具有以下性质:(1) 成对出现,;删除一端时,另一个端同时删除;(2)一端连接协议栈,另一端相互连接;一端收到数据包,直接转发至另一端;(3)可以配置IP和MAC.
veth-pair主要用于连接不同的网络命名空间,可以理解为虚拟网线;veth-pair也可以连接在网桥上,作为网桥的从设备,进而通过网桥扩展连接范围。

4.2 案例介绍:

4.2.1 正常场景

使用veth-pair连接两个命名空间,并进行通讯抓包,测试连通性。
案例的模型图如下所示:

操作过程如下:
1.创建命名空间:

ip netns add ns1
ip netns add ns1

2.创建veth-pair

ip link add veth1 type veth peer name veth2

3.veth1移入ns1,veth2移入ns2:

ip link set veth1 netns ns1
ip link set veth2 netns ns2

4.配置veth1和veth2的IP并设置为UP状态

ip netns exec ns1 ip address add 10.10.10.10/24 dev veth1
ip netns exec ns2 ip address add 10.10.10.20/24 dev veth2
ip netns exec ns1 ip link set veth1 up
ip netns exec ns2 ip link set veth2 up

从ns1命名空间向ns2发出PING消息,发现可以正常PING通:

这里需要注意一下,veth1和veth2在同一网段才能PING通;通过以下两个案例进行原因介绍。

4.2.2 异常场景一

设置IP: veth1(10.10.10.10/24), veth2(10.10.20.20/24)

ip netns exec ns1 ip address add 10.10.10.10/24 dev veth1
ip netns exec ns2 ip address add 10.10.20.20/24 dev veth2

发现ping不通:

在veth1和veth2上分别抓包观察数据包传输情况:

veth1上抓包如上图所示,表明veth1已发出ARP-request请求,未收到响应;

veth2上抓包如上图所示:表明veth2已收到ARP-request请求,未进行响应;因此链路断裂原因出现在veth2侧。
简单分析一下:veth2根据10.10.20.20/24计算出自己处于10.10.20.0/24网段;ARP-request的源IP地址为10.10.10.10, 经过veth2的子网掩出结果为10.10.10.0/24;即与veth2处于不同网段,veth2直接丢弃该ARP-request数据包, 从而导致链路不通。

4.2.3 异常场景二

设置IP: veth1(10.10.10.10/16), veth2(10.10.20.20/16)

ip netns exec ns1 ip address add 10.10.10.10/24 dev veth1
ip netns exec ns2 ip address add 10.10.20.20/16 dev veth2

发现仍然ping不通:

在veth1和veth2上分别抓包观察数据包传输情况:

veth1上抓包如上图所示,表明veth1已发出ARP请求且得到响应,发送ICMP-request后未收到响应;
且收到来自veth2的ARP-request消息,未响应ARP请求(原因同4.2.2 异常场景一);

veth2网卡上抓包如上图所示,表明veth2响应veth1的ARP请求后,收到ICMP-request数据包且未响应;
同时发现veth2广播的ARP-request消息未得到回复,无法知道veth1网卡的MAC地址,因此无法将ICMP-reply包发出。
还查看veth1和veth2上的ARP缓存进一步证实:

上图表明:veth1已对veth2的MAC地址进行缓存,但是veth2缓存veth1的MAC地址失败。

5.bridge

5.1 概念

bridge的功能同物理交换机:提供二层转发功能,具有mac学习能力,可配置MAC,起连接和转发作用,是Linux用软件模拟的虚拟网桥;作为虚拟网络接口,bridge同时可以配置IP地址,使其工作在IP层。
以下以案例方式进行介绍bridge的功能;该过程仅用到网桥的二层转发能力,因此不需要对其配置IP地址。网桥在IP层的功能和作用会结合Docker在Docker网络原理文章中介绍。

5.2 案例介绍

使用veth-pair通过bridge连接两个命名空间,网络结构如下所示:

通过ns1和ns2间传输的ICMP包测试命名空间间的联通性,具体操作步骤如下:
[1] 创建命名空间/veth-pair对、迁移网卡以及设置IP方式在 4.veth-pair 中已介绍,不再赘述;
[2] 创建网桥,并设置为UP状态:

ip link add bridge0 type bridge
ip link set bridge0 up

[3] 将veth11与veth22连接到bridge上

ip link set veth11 master bridge0
ip link set veth22 master bridge0

[4] 将veth11与veth22设置为UP状态

ip link set veth11 up
ip link set veth22 up

[5] 从ns1内ping向ns2

6.IP-Tunnels

6.1 IP-Tunnels概念

IP隧道技术定义:

路由器把一种网络协议封装到另一个网络协议内跨过网络传送到另一个路由器的处理过程。

IP隧道有多种实现方式,Linux提供了一种IPIP实现方式(IP IN IP),即将一个IP报文封装在另一个IP报文中。IPIP技术需要内核的tunnel4.ko和ipip.ko模块的支持。

6.2 实现原理

如图所示:IPIP的实现依赖于tun设备,涉及数据包的封装和解封,依赖于物理网卡进行传输。上图可分为两个部分:
发送部分:
【1】用户1中程序向目标地址为10.10.10.89 的主机发送数据包,经过socket到达内核协议栈;
【2】协议栈根据路由信息发现数据包需要交给tun1处理,此时数据包信息为:[dest: 10.10.10.89, source: 10.10.10.215][payload];
【3】tun1收到数据包后,直接将数据包传递到用户侧IPIP程序;
【4】IPIP程序对数据包进行包装后,再次通过socket传递至内核协议栈;
【5】此时内核协议栈根据路由信息将数据包通过eth1网卡发出;
此时数据包信息为:[dest: 192.168.31.89, source: 192.168.31.215][dest: 10.10.10.89, source: 10.10.10.215][payload]

接受部分:
【1】物理网卡eth2接收到来自eth1的数据包后,发现目标IP地址 192.168.31.89 是本机地址,因此上传至上层协议栈处理;
【2】协议栈处理至IPIP层时(ipip运行时会注册IPIP协议以及回调函数至协议栈),调用函数tunnel4_rcv()->ipip_rcv()实现外层IP剥离;
【3】当数据包上传至协议栈的IP层时,根据路由匹配发现数据包需要传递给tun2;
【4】tun2收到数据包后,直接传输到用户空间;

其中,剥离实现方式如下:
先修改指针(把二层地址指向三层数据的起始地址; 三层地址指向数据,也就是其封装的IP报文地址), 再把这个报文重新发给二层缓存,重新分发;详细可参考:linux的tunnel技术实现

6.3 案例介绍

案例涉及的网络结构如下所示:

网卡构建涉及的shell命令如下:

ip tunnel add tun0 mode ipip remote 192.168.31.89 local 192.168.31.215
ip link set tun0 up
ip address add 10.10.10.215 peer 10.10.10.89/24 dev tun0ip tunnel add tun1 mode ipip remote 192.168.31.215 local 192.168.31.89
ip link set tun1 up
ip address add 10.10.10.89 peer 10.10.10.215/24 dev tun1

在seong215主机上ping 10.10.10.89, 发现可以ping通:

这里再seong215上可以抓个包:

发现ICMP包在传输过程中有两层IP, 符合预期。

好奇的读者可以看一下路由信息:

在tun0被赋予IP且被激活后,路由表中会添加如上所示的路由信息,使得所有10.10.10.0/24网段的数据包交给tun0处理。

7.路由

Linux在每个网络命名空间独立维护一份路由表,路由表的功能体现在以下几个方面:
(1) 协议栈收到数据包以及外发数据包时都需要查看路由表,根据路由信息进行处理。(2) 协议栈收到数据包后也需查看路由表信息,判断目标IP是否是本地:不是本机IP,则进行转发或者丢弃(未开始转发模式时);是本机IP,则向上层协议栈传递数据包。(3) 当向外发送数据时(主动发送或者转发),根据路由表中的路由信息,决定数据包从哪个网卡发送出去。本章节会介绍路由表的查看方式以及路由的基本操作。

7.1 路由表:

以下是看Kubernates的master环境的路由表:

直观上不容易看懂,这里先说一下路由表的规则:
[1] Destination:值为default或者0.0.0.0的路由为默认路由,此时gateway为默认网关;当所有路由信息匹配失败时,会使用默认路由;默认路由可存在多条;
[2] metric:表示路由距离,即网路传输的代价或者权重;值越小,表示优先级越高;路由表会自动根据metric排序,值小的记录排在前面; 进行路由匹配时,按照从上向下顺序进行;可手动设置metrics值,范围是 1 ~ 9999;
[3] gateway: 表示网关,值为0.0.0.0的表示Destination为本地地址;有值的路由表示涉及网关,此时Flags上会有一个G标识(gateway缩写);
[4] flags:U(up)表示处于运行状态; H(host)表示路由至指定的机器,而是不网段,此时genmask为255.255.255.255;
[5] genmask:表示掩码,默认路由的genmask为0.0.0.0,使得可以匹配所有IP; 路由至主机时,为255.255.255.255;
[6] ifcase: 表示网卡,协议栈会使用匹配路由的网卡将数据包发送出去;
[7] REF和use:表示引用和使用次数,无关紧要。

因此,路由表中可以分为3类:

default: 默认路由;
host: 主机路由,genmask为255.255.255.255;
net: 网段路由;

7.2 路由表操作

#1.查询路由表
route -n#2.添加路由信息
#(1)添加host路由:
route add -host 10.10.0.0 dev veth1   [metric 100] [gw 1.1.1.1]
#(2)添加网段路由:
route add -net 10.10.0.0/16 dev veth1 [metric 97]  [gw 1.1.1.1]
#(3)添加default网关
route add default gw 10.10.1.1        [metric 1]#3.删除路由(将add改成delete即可)
#注意:每次只能删除一条———即使条件可以匹配多条(从上向下匹配)
#(1)删除默认网关
route delete default gw 10.10.1.1
#(2)删除对应网段
route delete -net 10.10.0.0/16 dev veth1
#(3)删除指定host
route add -host 10.10.0.0 dev veth1

7.3 案例介绍

案例的网路拓扑如下所示:

上图中存在两个网络命名空间、两个网桥和4对veth-pair; 所有网络设备的IP均处于同一网段: 10.10.0.0/16.
上图对应的shell命令如下:

#创建命名空间
ip netns add ns1 && ip netns add ns2#创建veth-pair
ip link add veth1 type veth peer name veth11
ip link add veth2 type veth peer name veth22
ip link add veth3 type veth peer name veth33
ip link add veth4 type veth peer name veth44#迁移veth-pair至不同命名空间
ip link set veth1 netns  ns1 && ip link set veth3 netns  ns1
ip link set veth2 netns  ns2 && ip link set veth4 netns  ns2 #配置veth-pair的IP
ip netns exec ns1 ip address add 10.10.10.10/16 dev veth1 && ip netns exec ns1 ip link set veth1 up
ip netns exec ns2 ip address add 10.10.10.11/16 dev veth2 && ip netns exec ns2 ip link set veth2 up
ip netns exec ns1 ip address add 10.10.20.10/16 dev veth3 && ip netns exec ns1 ip link set veth3 up
ip netns exec ns2 ip address add 10.10.20.11/16 dev veth4 && ip netns exec ns2 ip link set veth4 up#创建bridge
ip link add bridge1 type bridge
ip link add bridge2 type bridge #配置bridge
ip address add 10.10.10.1/16 dev bridge1 && ip link set bridge1 up
ip address add 10.10.20.1/16 dev bridge2 && ip link set bridge2 up#连接veth-pair一端至bridge
ip link set veth11 master bridge1 && ip link set veth11 up
ip link set veth22 master bridge1 && ip link set veth22 up
ip link set veth33 master bridge2 && ip link set veth33 up
ip link set veth44 master bridge2 && ip link set veth44 up

创建这些虚拟网卡后,ns1中的路由表信息如下所示:

上图表明发往10.10.0.0/16网段的数据包可以通过veth1或veth3发送出去。系统会按照metric对路由进行排序,由于veth1.metric=veth3.metric, 此时系统按照创建顺序将veth1排在veth3前面;此时,仅veth1行路由生效。
从ns1内向10.10.10.11(位于ns2)发送ping命令时:在bridge1和bridge2上分别ICMP抓包,发现只有bridge1能抓到数据包:


读者可以尝试一下测试场景:
case1:删除veth3行路由, 再新增veth3行路由:会发现veth3路由排在veth1之前,ICMP包通过路由会走bridge2;
case2:删除veth3行路由,发现ICMP包通过路由会走bridge1;
case3:新增veth3,并修改metric为100,此时发现ICMP包通过路由会走bridge1;

7.4 Docker路由相关

宿主机上:
docker0网桥的IP信息如下:

上图显示:docker0 的IP地址为172.17.0.1/16;
查看宿主机的路由表:

上图显示:目标地址为 172.17.0.0/16网段的数据包直接丢给docker0处理,且gateway是0.0.0.0表明数据包由本机内部处理(不涉及网关);

进入容器内部查看:
网卡和路由信息如下:

IP信息:eth0的IP为172.17.0.3/16;
路由信息:
[1] 默认网关为172.17.0.1(docker0的IP地址),即容器以docker0为网关;
[2] 可以处理的网段为172.17.0.0/16; 即通过docker0形成的二层链路进行数据包的发送;
网络结构图可以表示为:

此时,docker容器-1和docker容器-22通过docker0形成了两层连接。

Final

Linux虚拟网络内容除上述介绍内容外,还涉及iptables/netfilter框架;该部分由于内容较多,后续单独整理成一篇文章。

附录:

附件1:tun.c文件

// 该案例代码来源于网络: [未找到出处,请作者联系标记]:
int main()
{int tun_fd, nread, err;char buffer[1500];struct ifreq ifr;unsigned char ip[4];if ((tun_fd = open("/dev/net/tun", O_RDWR)) < 0) {return tun_fd;}memset(&ifr, 0, sizeof(ifr));ifr.ifr_flags = IFF_TUN | IFF_NO_PI;if ((err = ioctl(tun_fd, TUNSETIFF, (void *) &ifr)) < 0) {close(tun_fd);return err;}printf("open tun device: %s for reading...\n", ifr.ifr_name);while (1) {nread = read(tun_fd, buffer, sizeof(buffer));printf("read %d bytes from %s\n", nread,ifr.ifr_name);/**memcpy(ip, &buffer[12], 4);memcpy(&buffer[12], &buffer[16], 4);memcpy(&buffer[16], ip, 4);buffer[20] = 0;*((unsigned short *)&buffer[22]) += 8;nread = write(tun_fd, buffer, nread);printf("Write %d bytes to tun/tap device, that's %s\n", nread, buffer);*/}return 0;
}

云原生容器化-1 Linux虚拟网络介绍相关推荐

  1. 腾讯云EMR基于YARN针对云原生容器化的优化与实践

    导语 | 传统HADOOP生态系统使用YARN管理/调度计算资源,该系统⼀般具有明显的资源使⽤周期.实时计算集群资源消耗主要在⽩天,而数据报表型业务则安排在离线计算集群中.离在线业务分开部署的首要问题 ...

  2. 学习笔记:云原生容器化技术——Docker

    Docker学习笔记 前言 一.Docker概述 1.1 Docker为什么会出现? 1.2 Docker的历史 1.3 Docker为什么这么火爆? 1.4 虚拟机技术与容器化技术的区别 二.Doc ...

  3. AMS 新闻视频广告的云原生容器化之路

    作者 卓晓光,腾讯广告高级开发工程师,负责新闻视频广告整体后台架构设计,有十余年高性能高可用海量后台服务开发和实践经验.目前正带领团队完成云原生技术栈的全面转型. 吴文祺,腾讯广告开发工程师,负责新闻 ...

  4. Qunar 云原生容器化落地实践

    作者|邹晟 去哪儿网基础平台技术专家 背景 近几年,云原生和容器技术非常火爆,且日趋成熟,众多企业慢慢开始容器化建设,并在云原生技术方向上不断的探索和实践.基于这个大的趋势, 2020 年底 Quna ...

  5. 云原生|Qunar 云原生容器化落地实践

    背景 近几年,云原生和容器技术非常火爆,且日趋成熟,众多企业慢慢开始容器化建设,并在云原生技术方向上不断的探索和实践.基于这个大的趋势, 2020 年底 Qunar 也向云原生迈出了第一步--容器化. ...

  6. 最佳案例 | QQ 相册云原生容器化之路

    作者 吕朋钊,腾讯业务运维高级工程师,曾负责 QQ 相册.小世界业务的存储接入层运维,现负责 AI 业务的运维. 背景 QQ 相册是 QQ 产品中为用户提供图片存储,分享等功能的成熟产品,自上线以来, ...

  7. 最佳案例 | 游戏知几 AI 助手的云原生容器化之路

    作者 张路,运营开发专家工程师,现负责游戏知几 AI 助手后台架构设计和优化工作. 游戏知几 随着业务不断的拓展,游戏知几AI智能问答机器人业务已经覆盖了自研游戏.二方.海外的多款游戏.游戏知几研发团 ...

  8. 在生产环境中,阿里云如何构建高性能云原生容器网络?(含 PPT 下载)

    作者 | 溪恒  阿里云技术专家 直播完整视频回顾:https://www.bilibili.com/video/BV1nC4y1x7mt/ 关注"阿里巴巴云原生"公众号,后台回复 ...

  9. 服务器和交换机物理连接_利用Calico融合物理网络的云原生容器SDN方案

    01 云原生的背景 随着云计算的蓬勃发展,越来越多的IT设施及架构具备了虚拟化.分布式.多租户的云计算能力,这些能力为高速发展的互联网行业插上了腾飞的翅膀.并为互联网业务的开展提供了极大的便利. 当移 ...

最新文章

  1. 深入理解 Laravel Eloquent(三)——模型间关系(关联)
  2. 关于android的几个小知识点
  3. ping不通Linux系统解决方法
  4. python 灰度改二值,Python Image 对验证码进行灰度和二值法处理 去掉边框
  5. linux .i文件,Linux的文件I/O
  6. [CF453A] Little Pony and Expected Maximum【数论】
  7. DCL记忆上次输入值lisp_python-输入和输出
  8. AMOS分析技术:模型的群组分析;AMOS如何对同一个模型分别用男性数据和女性数据拟合?
  9. 数控铣床法兰克系统代码完整版
  10. 【python 监控报警】错误日志监控并钉钉报警
  11. iPhone 计算机 桌面,2分钟学会Windows仿苹果任务栏,你的电脑桌面也可如此炫酷!...
  12. 【python】OpenCV—Brightness and Contrast adjustments
  13. Java学习系列之抽象类和接口的区别和联系
  14. 大数据学习零基础能够学会不?
  15. 解除同居关系时共同财产的分割
  16. django教程day05
  17. Python基础之:Python的数据结构
  18. 未来星计算机模拟考试一级,全国计算机一级模拟考试题库及答案
  19. Android Studio gradle下载依赖包失败的解决办法
  20. 使GridView内容不换行

热门文章

  1. chrome中打开 swf下载的问题
  2. 三星a5000 android,三星SM-A5000是什么型号手机?
  3. 软件测试从业者必备核心素质
  4. 名人名言——摘录自编程随想的博客
  5. 360安全卫士关闭弹窗广告设置大全
  6. IBM X3650 M5 不开机 电源键一闪一闪 主板SYS BRD ERR
  7. 学习笔记 - 预祝CSDN 1024程序猿节日圆满成功
  8. tplink改无线打印服务器,tplinkwifiwdr7500怎么设置usb打印服务器 | tplogin.cn
  9. 如何利用高德地图URI加载区域边界
  10. WINE 安装使用钉钉 Dingtalk