Unix网络IO模型及Linux的IO多路复用模型

近段在看 Kafka 的网络模型时,遇到了很多 Java NIO 的内容,在学习 Java NIO 的过程中,发现需要把 UNIX 的这几种网络 IO 模型以及 Linux 的 IO 多路复用理解清楚,才能更好地理解 Java NIO,本文就是在学习 UNIX 的五种网络 IO 模型以及 Linux IO 多路复用模型后,做的一篇总结。

本文主要探讨的问题有以下两个:

  • Unix 中的五种网络 IO 模型;
  • Linux 中 IO 多路复用的实现。

基本概念

在介绍网络模型之前,先简单介绍一些基本概念。

1、文件描述符 fd

文件描述符(file descriptor,简称 fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

在 Linux 中,内核将所有的外部设备都当做一个文件来进行操作,而对一个文件的读写操作会调用内核提供的系统命令,返回一个 fd,对一个 socket 的读写也会有相应的描述符,称为 socketfd(socket 描述符),实际上描述符就是一个数字,它指向内核中的一个结构体(文件路径、数据区等一些属性)。

2、用户空间与内核空间、内核态与用户态

这个是经常提到的概念,具体含义可以参考这篇文章用户空间与内核空间,进程上下文与中断上下文【总结】,大概内容如下:

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对 linux 操作系统而言(以32位操作系统为例)

  • 将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),供内核使用,称为内核空间;
  • 将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。

每个进程可以通过系统调用进入内核,因此,Linux 内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有 4G 字节的虚拟空间。

  • 当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈;
  • 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

3、上下文切换

当一个进程在执行时,CPU 的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在 Linux 中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。

UNIX 的网络 IO 模型

根据 UNIX 网络编程对 IO 模型的分类,UNIX 提供了以下 5 种 IO 模型。

1、阻塞 IO 模型

最常用的 IO 模型就是阻塞 IO 模型,在缺省条件下,所有文件操作都是阻塞的,以 socket 读为例来介绍一下此模型,如下图所示。

未分类

在用户空间调用 recvfrom,系统调用直到数据包达到且被复制到应用进程的缓冲区中或中间发生异常返回,在这个期间进程会一直等待。进程从调用 recvfrom 开始到它返回的整段时间内都是被阻塞的,因此,被称为阻塞 IO 模型。

2、非阻塞 IO 模型

recvfrom 从应用到内核的时,如果该缓冲区没有数据,就会直接返回 EWOULDBLOCK 错误,一般都对非阻塞 IO 模型进行轮询检查这个状态,看看内核是不是有数据到来,流程如下图所示。

未分类

也就是说非阻塞的 recvform 系统调用调用之后,进程并没有被阻塞,内核马上返回给进程。

  • 如果数据还没准备好,此时会返回一个 error。进程在返回之后,可以干点别的事情,然后再发起 recvform 系统调用。重复上面的过程,循环往复的进行 recvform 系统调用,这个过程通常被称之为轮询。

轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

在 Linux 下,可以通过设置 socket 使其变为 non-blocking。

3、IO 多路复用模型

Linux 提供 select、poll、epoll,进程通过讲一个或者多个 fd 传递给 select、poll、epoll 系统调用,阻塞在 select 操作(这个是内核级别的调用)上,这样的话,可以同时监听多个 fd 是否处于就绪状态。其中,

  • select/poll 是顺序扫描 fd 是否就绪,而且支持的 fd 数量有限;
  • epoll 是基于事件驱动方式代替顺序扫描性能更高。

这个后面详细讲述,具体流程如下图所示。

未分类

多路复用的特点是通过一种机制一个进程能同时等待 IO 文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll 函数就可以返回,它最大的优势就是可以同时处理多个连接。

4、信号驱动 IO 模型

首先需要开启 socket 信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(非阻塞,立即返回)。当数据就绪时,会为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环喊出处理数据,流程如下图所示。

未分类

5、异步 IO 模型

告知内核启动某个事件,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通过我们,流程如下图所示。

未分类

与信号驱动模式的主要区别是:

  • 信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;
  • 异步 IO 操作由内核通知我们 IO 何时完成。

内核是通过向应用程序发送 signal 或执行一个基于线程的回调函数来完成这次 IO 处理过程,告诉用户 read 操作已经完成,在 Linux 中,通知的方式是信号:

  • 当进程正处于用户态时,应用需要立马进行处理,一般情况下,是先将事件登记一下,放进一个队列中;
  • 当进程正处于内核态时,比如正在以同步阻塞模式读磁盘,那么只能先把这个通知挂起来,等内核态的事情完成之后,再触发信号通知;
  • 如果这个进程现在被挂起来了,比如 sleep,那就把这个进程唤醒,等 CPU 空闲时,就会调度这个进程,触发信号通知。

6、几种 IO 模型比较

未分类

Linux 的 IO 多路复用模型

IO 多路复用通过把多个 IO 阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下,可以同时处理多个 client 请求,与传统的多线程/多进程模型相比,IO 多路复用的最大优势是系统开销小,系统不需要创建新的额外的进程或线程,也不需要维护这些进程和线程的运行,节省了系统资源,IO 多路复用的主要场景如下:

  • Server 需要同时处理多个处于监听状态或者连接状态的 socket;
  • Server 需要同时处理多种网络协议的 socket。

IO 多路复用实际上就是通过一种机制,一个进程可以监视多个描 fd,一旦某个 fd 就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,目前支持 IO 多路复用的系统有 select、pselect、poll、epoll,但它们本质上都是同步 IO。

在 Linux 网络编程中,最初是选用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择 epoll。

1、select

select 函数监视的 fd 分3类,分别是 writefds、readfds、和 exceptfds。调用后select 函数会阻塞,直到有 fd 就绪(有数据 可读、可写、或者有 except),或者超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。当select函数返回后,可以通过遍历 fdset,来找到就绪的 fd。

select 目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select 的一个最大的缺陷就是单个进程对打开的 fd 是有一定限制的,它由 FD_SETSIZE 限制,默认值是1024,如果修改的话,就需要重新编译内核,不过这会带来网络效率的下降。

select 和 poll 另一个缺陷就是随着 fd 数目的增加,可能只有很少一部分 socket 是活跃的,但是 select/poll 每次调用时都会线性扫描全部的集合,导致效率呈现线性的下降。

2、poll

poll 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有 fd 后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历 fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样以下两个缺点:

  • 大量的 fd 的数组被整体复制于用户态和内核地址空间之间;
  • poll 还有一个特点是【水平触发】,如果报告了 fd 后,没有被处理,那么下次 poll 时会再次报告该 fd;
  • fd 增加时,线性扫描导致性能下降。

3、epoll

epoll 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些 fd 变为就绪态,并且只会通知一次。还有一个特点是,epoll 使用【事件】的就绪通知方式,通过 epoll_ctl 注册 fd,一旦该 fd 就绪,内核就会采用类似 callback 的回调机制来激活该 fd,epoll_wait 便可以收到通知。

epoll的优点:

  • 没有最大并发连接的限制,它支持的 fd 上限受操作系统最大文件句柄数;
  • 效率提升,不是轮询的方式,不会随着 fd 数目的增加效率下降。epoll 只会对【活跃】的 socket 进行操作,这是因为在内核实现中 epoll 是根据每个 fd 上面的 callback 函数实现的,只有【活跃】的 socket 才会主动的去调用 callback 函数,其他 idle 状态的 socket 则不会。epoll 的性能不会受 fd 总数的限制。
  • select/poll 都需要内核把 fd 消息通知给用户空间,而 epoll 是通过内核和用户空间 mmap 同一块内存实现。

epoll 对 fd 的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT 模式是默认模式,LT 模式与 ET 模式的区别如下:

  • LT 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件,下次调用 epoll_wait 时,会再次响应应用程序并通知此事件;
  • ET 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件,如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。

4、三种模型的区别

未分类

介绍完 IO 多路复用之后,后续我们看一下 Java 网络编程中的 NIO 模型及其背后的实现机制。

Linux路由基础知识及配置方法

关于路由的基础知识

1、路由概念

  • 路由: 跨越从源主机到目标主机的一个互联网络来转发数据包的过程
  • 路由器:能够将数据包转发到正确的目的地,并在转发过程中选择最佳路径的设备
  • 路由表:在路由器中维护的路由条目,路由器根据路由表做路径选择
  • 直连路由:当在路由器上配置了接口的IP地址,并且接口状态为up的时候,路由表中就出现直连路由项
  • 静态路由:是由管理员手工配置的,是单向的。
  • 默认路由:当路由器在路由表中找不到目标网络的路由条目时,路由器把请求转发到默认路由接口 。

2、静态路由和默认路由的特点

静态路由特点:

  • 路由表是手工设置的;
  • 除非网络管理员干预,否则静态路由不会发生变化;
  • 路由表的形成不需要占用网络资源;

适用环境:一般用于网络规模很小、拓扑结构固定的网络中。

默认路由特点:

  • 在所有路由类型中,默认路由的优先级最低

适用环境:一般应用在只有一个出口的末端网络中或作为其他路由的补充

浮动静态路由:

  • 路由表中存在相同目标网络的路由条目时,根据路由条目优先级的高低,将请求转发到相应端口;
  • 链路冗余的作用;

3、路由器转发数据包时的封装过程

源IP和目标IP不发生变化,在网络的每一段传输时,源和目标MAC发生变化,进行重新封装,分别是每一段的源和目标地址

4、要完成对数据包的路由,一个路由器必须至少了解以下内容:

  • 目的地址
  • 相连路由器,并可以从哪里获得远程网络的信息
  • 到所有远程网络的可能路由
  • 到达每个远程网络的最佳路由
  • 如何维护并验证路由信息
  • 路由和交换的对比

路由工作在网络层

  • 根据“路由表”转发数据
  • 路由选择
  • 路由转发

交换工作在数据链路层

  • 根据“MAC地址表”转发数据
  • 硬件转发

linux运维中关于路由的一些操作

1、使用route -n命令查看Linux内核路由表

[root@dev ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.17    0.0.0.0         255.255.255.255 UH    0      0        0 ppp0
10.1.32.14      0.0.0.0         255.255.255.255 UH    0      0        0 tun0
10.1.32.12      0.0.0.0         255.255.255.255 UH    0      0        0 tun0
10.4.8.2        192.168.9.254   255.255.255.255 UGH   0      0        0 eth0
10.4.9.0        0.0.0.0         255.255.255.0   U     0      0        0 tun0
192.168.9.0     0.0.0.0         255.255.255.0   U     1      0        0 eth0
10.2.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
10.0.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
10.1.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tun0
192.168.0.0     0.0.0.0         255.255.0.0     U     0      0        0 tun0
0.0.0.0         192.168.9.254   0.0.0.0         UG    0      0        0 eth0

未分类

2、三种路由类型说明

  • 主机路由

主机路由是路由选择表中指向单个IP地址或主机名的路由记录。主机路由的Flags字段为H。例如,在下面的示例中,本地主机通过IP地址192.168.1.1的路由器到达IP地址为10.0.0.10的主机。

Destination    Gateway       Genmask        Flags     Metric    Ref    Use    Iface
-----------    -------     -------            -----     ------    ---    ---    -----
10.0.0.10     192.168.1.1    255.255.255.255   UH       0    0      0    eth0
  • 网络路由

网络路由是代表主机可以到达的网络。网络路由的Flags字段为N。例如,在下面的示例中,本地主机将发送到网络192.19.12的数据包转发到IP地址为192.168.1.1的路由器。

Destination    Gateway       Genmask      Flags    Metric    Ref     Use    Iface
-----------    -------     -------         -----    -----   ---    ---    -----
192.19.12     192.168.1.1    255.255.255.0      UN      0       0     0    eth0
  • 默认路由

当主机不能在路由表中查找到目标主机的IP地址或网络路由时,数据包就被发送到默认路由(默认网关)上。默认路由的Flags字段为G。例如,在下面的示例中,默认路由是IP地址为192.168.1.1的路由器。

Destination    Gateway       Genmask    Flags     Metric    Ref    Use    Iface
-----------    -------     ------- -----      ------    ---    ---    -----
default       192.168.1.1     0.0.0.0    UG       0        0     0    eth0

3、配置路由route的命令

设置和查看路由表都可以用 route 命令,设置内核路由表的命令格式是:

route  [add|del] [-net|-host] target [netmask Nm] [gw Gw] [[dev] If]

参数解释:

  • add 添加一条路由规则
  • del 删除一条路由规则
  • -net 目的地址是一个网络
  • -host 目的地址是一个主机
  • target 目的网络或主机
  • netmask 目的地址的网络掩码
  • gw 路由数据包通过的网关
  • dev 为路由指定的网络接口

4、route命令使用举例

添加到主机的路由
# route add -host 192.168.1.2 dev eth0:0
# route add -host 10.20.30.148 gw 10.20.30.40

添加到网络的路由
# route add -net 10.20.30.40 netmask 255.255.255.248 eth0
# route add -net 10.20.30.48 netmask 255.255.255.248 gw 10.20.30.41
# route add -net 192.168.1.0/24 eth1

添加默认路由
# route add default gw 192.168.1.1

删除路由
# route del -host 192.168.1.2 dev eth0:0
# route del -host 10.20.30.148 gw 10.20.30.40
# route del -net 10.20.30.40 netmask 255.255.255.248 eth0
# route del -net 10.20.30.48 netmask 255.255.255.248 gw 10.20.30.41
# route del -net 192.168.1.0/24 eth1
# route del default gw 192.168.1.1                  //route del default   删除所有的默认路由

添加一条默认路由
# route add default gw 10.0.0.1      //默认只在内存中生效
开机自启动可以追加到/etc/rc.local文件里
# echo "route add default gw 10.0.0.1" >>/etc/rc.local

添加一条静态路由
# route add -net 192.168.2.0/24 gw 192.168.2.254
要永久生效的话要这样做:
# echo "any net 192.168.2.0/24 gw 192.168.2.254" >>/etc/sysconfig/static-routes

添加到一台主机的静态路由
# route add -host 192.168.2.2 gw 192.168.2.254
要永久生效的话要这样做:
# echo "any  host 192.168.2.2 gw 192.168.2.254 " >>/etc/sysconfig/static-routes
注:Linux 默认没有这个文件 ,得手动创建一个

5、设置包转发

在Linux中默认的内核配置已经包含了路由功能,但默认并没有在系统启动时启用此功能;
开启Linux的路由功能可以通过调整内核的网络参数来实现,方法如下:

  临时开启路由功能:
# echo 1 > /proc/sys/net/ipv4/ip_forward
或者
# sysctl -w net.ipv4.ip_forward=1
  永久开启路由功能
# vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
# sysctl -p

6、静态路由配置

添加静态路由到路由表的语法如下:

ip route [destination_network] [mask] [next-hop_address] administrative_distance]

参数解析:

  • ip route 用于创建静态路由的命令。
  • Destination_network 需要发布到路由表中的网段。
  • Mask 在这一网络上使用的子网掩码。
  • Next-hop_address 下一跳路由器的地址。
  • administrative_distance 默认时,静态路由有一个取值为1 的管理性距离。在这个命令的尾部添加管理权来修改这个默认值。

例如

ip route 172.16.1.0 255.255.255.0 172.16.2.1

查看路由表除了使用route -n命令外,还可以使用ip route

[root@dev ~]# ip route
192.168.1.17 dev ppp0  proto kernel  scope link  src 192.168.1.190
10.1.32.14 dev tun0  scope link
10.1.32.12 dev tun0  scope link
10.4.8.2 via 192.168.9.254 dev eth0  src 192.168.9.200  mtu 1500 advmss 1460
10.4.9.0/24 dev tun0  scope link
192.168.9.0/24 dev eth0  proto kernel  scope link  src 192.168.9.200  metric 1
10.2.0.0/16 dev tun0  scope link
10.0.0.0/16 dev tun0  scope link
10.1.0.0/16 dev tun0  scope link
192.168.0.0/16 dev tun0  scope link
default via 192.168.9.254 dev eth0

—————————————-实例1——————————————–

未分类

如上图所示,PC0机器和PC1机器之间经过两个路由器,要想使这两台机器通信,路由设置如下:

  • Route0路由器设置:
ip add 192.168.1.1 255.255.255.0
ip add 192.168.2.1 255.255.255.0
ip route 192.168.3.0 255.255.255.0 192.168.2.2
  • Route1路由器设置:
ip add 192.168.2.2 255.255.255.0
ip add 192.168.3.1 255.255.255.0
ip route 192.168.1.0 255.255.255.0 192.168.2.1

—————————————-实例2——————————————–

未分类

如上图所示,使用A主机192.168.1.2能够ping通E主机192.168.4.2,这两台机能够通信。

操作思路:

  • 在主机B上设置默认路由下一跳为192.168.2.2,并开启路由转发功能;
  • 在主机C上设置2条静态路由,分别去192.168.1.0/24网段的下一跳为192.168.2.1,去192.168.4.0/24网段的下一跳为192.168.3.2,并开启路由转发功能;
  • 在主机D上设置默认路由下一跳为192.168.3.1,并开启路由转发功能。

操作记录:

1)A主机上操作:ip为192.168.1.2,设置网关为192.168.1.1
# route add default gw 192.168.1.1

2)B主机上操作:第一块网卡为192.168.1.1,第二块网卡为192.168.2.1
# ifconfig eth0 192.168.1.1
# ifconfig eth1 192.168.2.1   //可以在一块网卡上设置两个ip,比如是eth0,eth0:0

B主机设置默认路由,下一跳为192.168.2.2
# route add default gw 192.168.2.2

B主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward   //临时转发,可以在/etc/sysctl.conf里设置永久转发

3)C主机上操作:第一块网卡为192.168.2.2,第二块网卡为192.168.3.1
# ifconfig eth0 192.168.2.2
# ifconfig eth1 192.168.3.1   //如果就一块网卡,可以设置ifconfig eth0:0 192.168.3.1

C主机设置2条默认路由
# route add -net 192.168.1.0/24 gw 192.168.2.1
# route add -net 192.168.4.0/24 gw 192.168.3.2

C主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward

4)D主机上操作:第一块网卡为192.168.3.2,第二块网卡为192.168.4.1
# ifconfig eth0 192.168.3.2
# ifconfig eth1 192.168.4.1

D主机设置默认路由,下一跳为192.168.3.1
# route add default gw 192.168.3.1

D主机开启路由转发功能
# echo 1 > /proc/sys/net/ipv4/ip_forward

5)E主机上操作:ip为192.168.4.2,设置网关为192.168.4.1
# route add default gw 192.168.4.1

Linux下配置安卓开发环境和安装apktool

1、安装依懒源

yum install glibc.i686 zlib.i686 mesa-libglapi.i686 libstdc++-4.4.7-4.el6.i686 gitk.noarch gcc-c++.x86_64 bison.x86_64 flex.x86_64 gperf.x86_64 unix2dos.x86_64 mingw32-gcc-c++.x86_64 mingw32-binutils.x86_64 mingw32-w32api.noarch  

2、安装JDK

wget http://download.oracle.com/otn-pub/java/jdk/8u121-b13/e9e7ea248e2c4826b92b3f075a80e441/jdk-8u121-linux-x64.tar.gz?AuthParam=1491548745_9847eef52601a85fbf542c74583e1454  
tar zxf jdk-8u121-linux-x64.tar.gz -c /usr/local  
ln -s /usr/local/jdk1.8.0_121/ /usr/local/jdk

cat >> /etc/profile <<EOF  
export JAVA_HOME=/usr/local/jdk  
export JAVA_BIN=/usr/local/jdk/bin  
export PATH=$PATH:$JAVA_HOME/bin  
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar  
export JAVA_HOME JAVA_BIN PATH CLASSPATH  
EOF

source /etc/profile  

3、安装 android-sdk-linux

wget http://dl.gmirror.org/android/android-sdk_r24.4.1-linux.tgz  
tar zxf android-sdk_r24.4.1-linux.tgz -c /usr/local  
cd /usr/local/android-sdk-linux  
tools/android update sdk --no-ui  
cat >> /etc/profile <<EOF  
export ANDROID_SDK_HOME=/usr/local/android-sdk-linux  
export PATH=$PATH:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools  
EOF  
source /etc/profile  

4、apktool

wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.2.2.jar  
wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool  
mv apktool /usr/local/bin  
mv apktool_2.2.2.jar /usr/local/bin/apktool.jar  

其他

GCC报错:
https://blog.imdst.com/lib64-libc-so-6-version-glibc_2-14-not-found-jie-jue-ban-fa/

忘记linux登陆密码重置的方法

之前克隆了别人的一台虚拟机,问机主要了密码试了一下居然报错,机主自己也搞不清楚密码是对是错,嘤嘤嘤~

未分类

遇到这种情况,其实可以进入single模式去修改root密码,下边的方法适用于ubuntu12.04版本

1、重启ubuntu,随即长按shirft进入grub菜单,选择recovery-mode,不要按回车哦;

未分类

2、按”e”键进入编辑页面;如下(恢复模式)

未分类

3、将recovery nomodeset替换为rw single init=/bin/bash;

未分类

未分类

(注:如果是非恢复模式,是将界面中位置的ro single改为rw single init=/bin/bash)

4、按ctrl+x进入单用户模式,当前用户即为root;

未分类

5、到/etc目录下修改sudoers权限:chmod 0440 sudoers,搞定;同时passwd root修改root密码,下次就不这么麻烦了;

未分类

6、按ctrl+alt+del重启;

重启完成就可以用设置好的密码进入linux啦~

技术贴完成,谢谢观赏。

Linux权限管理使用总结

1、linux中的权限分组

linux中权限由10位字母组成,就是下图中标注出的权限部分(-代表没有取得该权限)
大家进入linux后输入命令ls -al即可查看到如下内容

未分类

在linux中权限中,每个用户属于一个权限组,其中包括几个概念即:文件所有者、所在组、其他组。

  • 文件所有者:一般来说可以简单的理解为文件创建者,但是创建后可以通过chown(change owner)命令来更改文件的所有者
  • 所在组:文件创建时所在组为创建者所在组,后期可通过chgrp(change group)命令来修改文件所在组
  • 其他组:除文件所在组以外的组称为其他组

2、具体权限介绍

linux权限的10位字母具体可以分为4段:

未分类

  • 第一段:表示文件是否为目录,若为目录第一个字母则为d,否则为-
  • 第二段:表示文件所有者权限,第一个r(read)表示为读权限,第二个w(write)表示为写权限,第三个x(execute)表示为可执行权限,若没有某个权限则权限出用-代替
  • 第三段:表示文件所在组权限,具体rwx的含义同上
  • 第四段:表示其他用户组权限,具体rwx的含义同上

3、权限的修改

其实linux中对于权限的修改十分简单,大家可以将权限理解为二进制的对应关系(每段rwx为一组),例如你想给一个文件读权限,而不给写权限和可执行权限,那么对应的就是100(二进制),也就是4,同理如果给rwx权限那么就是7。

那么我们如果想要给一个文件test.sh文件赋予rwxr-xr-x权限那么我们就应该执行chmod 755 test.sh命令,其中第一个7对应了文件所有者所持有的权限,第二个5对应了文件所在组的权限,第三个5对应了其他用户的权限。

但是!!!如果你想要更直观的看出给一个文件的具体权限可以使用下面的命令chmod u=rwx,g=rx,o=rx test.sh即可给予test.sh和上面一样的权限,其中u代表文件所有者权限,g代表文件所在组权限,o代表其他用户权限。

chmod u-x,g+w,o+w test.sh该命令表示对该文件去除文件所有者的执行权限,加上文件所在组和其他用户的写权限,以上两条命令可以更加直观的看出我们具体对文件权限做出了哪些修改。

从Unix到Linux的发展关系

一张图概括全世界最重要的操作系统Unix,以及后续Linux的发展过程,他们的版本关系。如果Unix不商业化,可能没有Linux的发展空间,现在已经是Linux的天下。

Unix的诞生

1965年时,贝尔实验室(Bell Labs)加入一项由通用电气(General Electric)和麻省理工学院(MIT)合作的计划;该计划要建立一套多使用者、多任务、多层次(multi-user、multi-processor、multi-level)的MULTICS操作系统。直到1969年,因MULTICS计划的工作进度太慢,该计划被停了下来。当时,Ken Thompson(后被称为UNIX之父)已经有一个称为”星际旅行”的程序(应该是一个游戏程序)在GE-635的机器上跑,但是反应非常慢,正巧被他发现了一部被闲置的PDP-7(Digital的主机),Ken Thompson和Dernis Ritchie就将”星际旅行”的程序移植到PDP-7上。而这部PDP-7就此在整个计算机历史上留下了芳名。

MULTICS其实是”Multiplexed Information and Computing Service”的缩写,在1970年时,那部PDP-7却只能支持两个使用者,当时,Brian Kernighan就开玩笑地称他们的系统其实是:”UNiplexed Information and Computing Service”,缩写为”UNICS”,后来,大家取其谐音,就称其为”UNIX”了。1970年可称为”UNIX元年”。

从Unix到Linux的发展关系

未分类

后来Unix被商业化,后来出现Linux,这后来的故事我们都知道了。

使用Django commands构建命令行做定时(crontab)任务

需求: 我想调用django里models.py或者views.py的函数对数据库做些增删改查的操作,可以加入crontab做管理,找了半天才发现可以用django commands来搞,就简单记录下来怎么用。

这个app的内容如下:

tree weblog/
weblog/
├── __init__.py
├── __init__.pyc
├── admin.py
├── admin.pyc
├── apps.py
├── management
│   ├── __init__.py
│   ├── __init__.pyc
│   └── commands
│       ├── __init__.py
│       ├── __init__.pyc
│       ├── delete_author.py
│       └── delete_author.pyc
├── migrations
│   ├── 0001_initial.py
│   ├── 0001_initial.pyc
│   ├── __init__.py
│   └── __init__.pyc
├── models.py
├── models.pyc
├── tests.py
└── views.py

3 directories, 19 files

简单写一个函数,功能是删除weblog_author表的一条记录:

cat weblog/management/commands/delete_author.py
from django.core.management.base import BaseCommand, CommandError
from weblog.models import Author

class Command(BaseCommand):
    help = 'delete author'

    def add_arguments(self, parser):
        parser.add_argument('author_id', nargs='+', type=int)

    def handle(self, *args, **options):
        for author_id in options['author_id']:
            try:
                author = Author.objects.get(pk=author_id)
            except Author.DoesNotExist:
                raise CommandError('Author "%s" does not exist' % author_id)

            author.delete()

            self.stdout.write(self.style.SUCCESS('Successfully delete author "%s"' % author_id))

执行

python manage.py delete_author  2
Successfully delete author “2"

加入到crontab里:

*/15 * * * * python /var/www/poll_mysite/manage.py delete_author  2 >/dev/null 2>&1

在docker swarm集群中使用HEALTHCHECK解决服务更新不可用的问题

这是我们使用自建 docker swarm 集群后在部署时遇到的一个问题,使用 docker service update 命令更新服务时,

docker service update -d=false --force service_name

在更新的过程中服务有短暂的时间不能访问。

该服务中运行的是 asp.net core web api ,所使用的 Dockerfile 如下:

FROM microsoft/aspnetcore:1.1.2
ARG PROJECT
WORKDIR /app
COPY ${PROJECT}/publish .
RUN echo "dotnet ${PROJECT}.dll --urls http://*:80" > run.sh

通过在服务更新期间在另外一个容器中运行下面的 curl 命令捕捉这个问题:

while true;do curl -sSf -w '%{http_code}' cloud_api/alive;sleep 2;done

service update 期间不能访问所更新的服务时,curl 会出现下面的输出:

curl: (7) Failed to connect to cloud_api port 80: Connection refused
000

怀疑是容器启动后,asp.net core web api 站点没有立即开始工作,dotnet 命令启动站点也需要一定的时间。

针对这个怀疑点,在 Dockcefile 中添加 HEALTHCHECK 指令,这样可以让 docker 在容器启动后对容器内应用进行健康检查,检查通过才将容器投入使用。

HEALTHCHECK --interval=5s --timeout=20s 
   CMD curl -fs localhost/alive || exit 1

注:localhost/alive 是容器内应用实现的一个健康检查 url 。

添加 HEALTHCHECK 后重新构建镜像并部署,执行 service update 命令问题没有出现,搞定!

docker swarm集群搭建及使用shipyard UI管理

一、规划

1、swarm01作为manager节点,swarm02和swarm03作为worker节点。

# cat /etc/hosts
127.0.0.1   localhost
192.168.139.175  swarm01 
192.168.139.176  swarm02 
192.168.139.177  swarm03

2、配置SSH免密登陆

# ssh-keygen -t rsa -P ''
# ssh-copy-id -i .ssh/id_rsa.pub [email protected]
# ssh-copy-id -i .ssh/id_rsa.pub [email protected]

二、安装docker和ansible

1、安装配置ansible

# yum -y install ansible
# cat /etc/ansible/hosts | grep -v ^# | grep -v ^$
[node]
192.168.139.176
192.168.139.177
# sed -i "s/SELINUX=enforcing/SELINUX=disabled" /etc/selinux/config
# ansible node -m copy -a 'src=/etc/selinux/config dest=/etc/selinux/'
# systemctl stop firewalld
# systemctl disable firewalld
# ansible node -a 'systemctl stop firewalld'
# ansible node -a 'systemctl disable firewalld'

注:这里选择关闭防火墙,实际环境中可自行开放端口。

2、安装docker

  • 在manager节点安装docker
# yum install -y yum-utils device-mapper-persistent-data lvm2
# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# yum list docker-ce --showduplicates | sort -r
# yum -y install docker-ce
# docker --version
Docker version 17.06.0-ce, build 02c1d87
# systemctl start docker
# systemctl status docker
# systemctl enable docker
  • 使用ansible在worker节点安装docker
# ansible node -m copy -a 'src=/etc/yum.repos.d/docker-ce.repo dest=/etc/yum.repos.d/'
# ansible node -m yum -a "state=present name=docker-ce"
# ansible node -a 'docker --version'
192.168.139.173 | SUCCESS | rc=0 >>
Docker version 17.06.0-ce, build 02c1d87
192.168.139.174 | SUCCESS | rc=0 >>
Docker version 17.06.0-ce, build 02c1d87
# ansible node -a 'systemctl start docker'
# ansible node -a 'systemctl status docker'
# ansible node -a 'systemctl enable docker'

三、配置docker swarm集群

1、创建docker swarm集群

# docker swarm init --listen-addr 0.0.0.0
Swarm initialized: current node (a1tno675d14sm6bqlc512vf10) is now a manager.
To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

2、查看节点

# docker node ls
ID                           HOSTNAME      STATUS      AVAILABILITY   MANAGER STATUS
a1tno675d14sm6bqlc512vf10 *  swarm01        Ready         Active           Leader

3、查看加入集群manager管理节点的命令

# docker swarm join-token manager
To add a manager to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-7tdlpdnkyfl1bnq34ftik9wxw 192.168.139.175:2377

4、查看加入集群worker节点的命令

# docker swarm join-token worker
To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377

5、将前面规划的两个worker节点加入集群

# docker swarm join --token SWMTKN-1-3sp9uxzokgr252u1jauoowv74930s7f8f5tsmm5mlk5oim359e-dk52k5uul50w49gbq4j1y7zzb 192.168.139.175:2377
This node joined a swarm as a worker.

6、查看worker节点是否已加入集群

# docker node ls
ID                        HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
7zkbqgrjlsn8c09l3fagtfwre     swarm02  Ready      Active              
a1tno675d14sm6bqlc512vf10 *   swarm01  Ready      Active         Leader
apy9zys2ch4dlwbmgdqwc0pn3     swarm03  Ready      Active

7、查看docker swarm的管理网络

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
05efca714d2f        bridge              bridge              local
c9cd9c37edd7        docker_gwbridge     bridge              local
10ac9e48d81b        host                host                local
n60tdenc5jy7        ingress             overlay             swarm
a9284277dc18        none                null                local

这里,一个docker swarm集群就搭建好了

四、搭建docker swarm的UI—Portainer

Portainer地址:https://portainer.io/。

1、使用该命令部署Portainer

# docker service create 
--name portainer 
--publish 9000:9000 
--constraint 'node.role == manager' 
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock 
portainer/portainer 
-H unix:///var/run/docker.sock
# docker images |grep portainer
portainer/portainer  latest  07cde96d4789   2 weeks ago  10.4MB
# docker service ls                    ###查看集群列表
ID              NAME        MODE     REPLICAS      IMAGE             PORTS
p5bo3n0fmqgz  portainer  replicated    1/1   portainer/portainer:latest   *:9000->9000/tcp

这就部署好了

2、浏览器输入http://localhost:9000进入该UI界面,如下所示,第一次进入Portainer,配置8位数的admin密码

未分类

密码修改完成后点击“validate”验证

未分类

如下图所示,输入admin用户名和密码进入Portainer

未分类

首页如下

未分类

查看swarm节点模块

未分类

这里可以在images模块pull镜像,在这里我pull了nginx

未分类

在Services模块下创建nginx服务,Services > Add service,这里创建三个副本,并将80端口映射出去,最后点击“Create Service”创建服务

未分类

刷新服务列表,查看是否创建成功

未分类

3、使用命令进行确认

# docker images | grep nginx
nginx   latest    b8efb18f159b     7 days ago     107MB
# ansible node -m shell -a 'docker images|grep nginx'
192.168.139.177 | SUCCESS | rc=0 >>
nginx   latest    b8efb18f159b     8 days ago     107MB
192.168.139.176 | SUCCESS | rc=0 >>
nginx   latest    b8efb18f159b     8 days ago     107MB
# docker service ls        ###查看服务的任务列表
ID             NAME     MODE     REPLICAS   IMAGE        PORTS
emrs3rj73bwh  Nginx  replicated   3/3    nginx:latest  *:80->80/tcp
p5bo3n0fmqgz  portainer  replicated  1/1  portainer/portainer:latest   *:9000->9000/tcp
# docker service ps Nginx  
ID                  NAME                IMAGE               NODE         
0smpndfx0bwc        Nginx.1             nginx:latest        swarm03      
werrrzlyfbf1        Nginx.2             nginx:latest        swarm01   
l7puro0787cj        Nginx.3             nginx:latest        swarm02 

DESIRED STATE       CURRENT STATE          ERROR               PORTS      
Running          Running 15 minutes ago                                   
Running          Running 15 minutes ago                                  
Running          Running 15 minutes ago

未分类

五、搭建docker swarm的UI—Shipyard

Shipyard的UI也是比较简单的,但是比较反复,它需要在每个节点都pull相应镜像才能加入Shipyard的UI。

1、先pull相应镜像到本地,这里我使用的是网易蜂巢的镜像,很快而且镜像也是比较新的

# docker pull hub.c.163.com/library/alpine:latest
# docker pull hub.c.163.com/library/rethinkdb:latest
# docker pull hub.c.163.com/longjuxu/microbox/etcd:latest
# docker pull hub.c.163.com/wangjiaen/shipyard/docker.io/shipyard/docker-proxy:latest
# docker pull hub.c.163.com/library/swarm:latest
# docker pull hub.c.163.com/wangjiaen/shipyard/docker.io/shipyard/shipyard:latest

2、给这些镜像新建一个tag标签

# docker tag 7328f6f8b418 alpine
# docker tag 4a511141860c rethinkdb
# docker tag 6aef84b9ec5a microbox/etcd 
# docker tag cfee14e5d6f2 shipyard/docker-proxy
# docker tag 0198d9ac25d1 swarm
# docker tag 36fb3dc0907d shipyard/shipyard

3、使用如下命令搭建Shipyard的UI

# curl -sSL https://shipyard-project.com/deploy | bash -s
Deploying Shipyard
 -> Starting Database
 -> Starting Discovery
 -> Starting Cert Volume
 -> Starting Proxy
 -> Starting Swarm Manager
 -> Starting Swarm Agent
 -> Starting Controller
Waiting for Shipyard on 192.168.139.175:8080
..
Shipyard available at http://192.168.139.175:8080
Username: admin Password: shipyard

4、根据提示输入http://localhost:8080,输入用户名admin,密码shipyard进入shipyard

未分类

5、进入shipyard首页容器界面

未分类

6、进入nodes模块查看,这里现在只有manager节点

未分类

7、在worker节点上pull并tag镜像,即是重复如上的第①和第②步,之后,在该worker节点上输入如下命令将其加入shipyard

# curl -sSL https://shipyard-project.com/deploy | ACTION=node DISCOVERY=etcd://192.168.139.175:4001 bash -s 
Adding Node
 -> Starting Cert Volume
 -> Starting Proxy
 -> Starting Swarm Manager
 -> Starting Swarm Agent
Node added to Swarm: 192.168.139.176

未分类

其他节点同理。

对比两种UI,其实都是比较简单的,个人认为Portainer较好,在manager节点pull一个镜像即可搭建UI。

问题:

  • manager:
# docker swarm init --advertise-addr 192.168.139.175
  • worker:
# docker swarm join --token SWMTKN-1-4dwtfbdvjmuf3limglbpy66k85ply2cn66hd0ugsaxfed5fj1d-3rp33pedt9k7ewpfizbzc9bvi 192.168.139.175:2377
Error response from daemon: Timeout was reached before node was joined. The attempt to join the swarm will continue in the background. Use the "docker info" command to see the current swarm status of your node.

出现worker节点无法加入集群的问题,这里需要设置监听地址全零。

Python–Virtualenv简明教程

  • virtualenv是一种工具来创建独立的Python环境。

  • virtualenv通过创建独立Python开发环境的工具, 来解决依赖、版本以及间接权限
    问题. 比如一个项目依赖Django1.3 而当前全局开发环境为Django1.7, 版本跨度过大, 导致不兼容使项目无法正在运行, 使用virtualenv可以解决这些问题.

  • virtualenv创建一个拥有自己安装目录的环境, 这个环境不与其他虚拟环境共享库, 能够方便的管理python版本和管理python库

1. 安装Virtualenv

使用pip安装Virtualenv, 使用过python的都应该知道pip包管理神器吧, 即使不知道, 网站也有大把的教程, 不过推荐查看官方安装指南

$ pip install virtualenv
//或者由于权限问题使用sudo临时提升权限
$ sudo pip install virtualenv

2. virtualenv基本使用

现在开始使用virtualenv管理python环境

➜  Test git:(master) ✗ virtualenv ENV  #创建一个名为ENV的目录, 并且安装了ENV/bin/python, 创建了lib,include,bin目录,安装了pip
New python executable in 
Installing setuptools, pip...done.
➜  Test git:(master) ✗ cd ENV
➜  ENV git:(master) ✗ ll
drwxr-xr-x  14 andrew_liu  staff  476 12  8 08:49 bin
drwxr-xr-x   3 andrew_liu  staff  102 12  8 08:49 include
drwxr-xr-x   3 andrew_liu  staff  102 12  8 08:49 lib
  • lib,所有安装的python库都会放在这个目录中的lib/pythonx.x/site-packages/下
  • bin,bin/python是在当前环境是使用的python解释器

如果在命令行中运行virtualenv –system-site-packages ENV, 会继承/usr/lib/python2.7/site-packages下的所有库, 最新版本virtualenv把把访问全局site-packages作为默认行为
default behavior.

2.1 激活virtualenv

#ENV目录下使用如下命令
➜  ENV git:(master) ✗ source ./bin/activate  #激活当前virtualenv
(ENV)➜  ENV git:(master) ✗ #注意终端发生了变化
#使用pip查看当前库
(ENV)➜  ENV git:(master) ✗ pip list
pip (1.5.6)
setuptools (3.6)
wsgiref (0.1.2) #发现在只有这三个
pip freeze  #显示所有依赖
pip freeze > requirement.txt  #生成requirement.txt文件
pip install -r requirement.txt  #根据requirement.txt生成相同的环境

2.2 关闭virtualenv

使用下面命令

$ deactivate

2.3 指定python版本

可以使用-p PYTHON_EXE选项在创建虚拟环境的时候指定python版本

#创建python2.7虚拟环境
➜  Test git:(master) ✗ virtualenv -p /usr/bin/python2.7 ENV2.7
Running virtualenv with interpreter /usr/bin/python2.7
New python executable in ENV2.7/bin/python
Installing setuptools, pip...done.
#创建python3.4虚拟环境
➜  Test git:(master) ✗ virtualenv -p /usr/local/bin/python3.4 ENV3.4
Running virtualenv with interpreter /usr/local/bin/python3.4
Using base prefix '/Library/Frameworks/Python.framework/Versions/3.4'
New python executable in ENV3.4/bin/python3.4
Also creating executable in ENV3.4/bin/python
Installing setuptools, pip...done.

到此已经可以解决python版本冲突问题和python库不同版本的问题

3. 其他

3.1 生成可打包环境

某些特殊需求下,可能没有网络, 我们期望直接打包一个ENV, 可以解压后直接使用, 这时候可以使用virtualenv -relocatable指令将ENV修改为可更改位置的ENV

#对当前已经创建的虚拟环境更改为可迁移
➜  ENV3.4 git:(master) ✗ virtualenv --relocatable ./
Making script ./bin/easy_install relative
Making script ./bin/easy_install-3.4 relative
Making script ./bin/pip relative
Making script ./bin/pip3 relative
Making script ./bin/pip3.4 relative

3.2 获得帮助

$ virtualenv -h

当前的ENV都被修改为相对路径, 可以打包当前目录, 上传到其他位置使用。

这并不能使虚拟环境跨平台使用。