1.启动容器

启动容器有两种方式:

  • 基于镜像新建一个容器并启动
  • 将在终止状态(stopped)的容器重新启动

1)新建并启动——docker run

比如在启动ubuntu:14.04容器,并输出“Hello World”,之后终止容器:

userdeMBP:~ user$ docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world

如果要启动一个bash终端,并且允许用户进行交互:

userdeMacBook-Pro:~ user$ docker run -t -i ubuntu:14.04 /bin/bash
root@db3bc701340a:/#

-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上

-i 则让容器的标准输入保持打开

然后在交互模式上就能够通过所创建的终端对ubuntu系统进行操作,如:

root@db3bc701340a:/# pwd
/
root@db3bc701340a:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@db3bc701340a:/#

当利用 docker run来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

2) 启动已终止容器——docker start

容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。

除此之外,并没有其它的资源。可以在伪终端中利用 ps或 top来查看进程信息。

root@db3bc701340a:/# ps
PID TTY TIME CMD
pts/ :: bash
pts/ :: ps
root@db3bc701340a:/# top top - :: up min, users, load average: 0.36, 0.29, 0.15
Tasks: total, running, sleeping, stopped, zombie
%Cpu(s): 1.7 us, 1.8 sy, 0.0 ni, 96.2 id, 0.2 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem: total, used, free, buffers
KiB Swap: total, used, free. cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
root S 0.0 0.2 :00.04 bash
root R 0.0 0.1 :00.00 top

可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率 极高,是货真价实的轻量级虚拟化。

2.后台(background)运行

很多时候,需要让 Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。

此时,可以通过添加 -d 参数来实现。

  -d, --detach     Run container in background and print container ID 即在后台运行容器,并打印出容器ID

比如,当你不使用-d 参数时:

userdeMacBook-Pro:~ user$ docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
hello world
.....

容器会把输出的结果(STDOUT)打印到宿主机上面

但是如果使用 -d 参数:

userdeMacBook-Pro:~ user$ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
3c091389b4ffb583b4cca578b751e0a4b4e1d556852fe58a5f3d50003cb95737

此时容器会在后台运行并不会把输出的结果(STDOUT)打印到宿主机上面

输出结果可以用docker logs 查看:

userdeMacBook-Pro:~ user$ docker logs 3c091389b4ff
hello world
hello world
hello world
hello world
hello world
hello world
hello world
....

注: 容器是否会长久运行,是和docker run指定的命令有关,和 -d 参数无关。

使用docker ps查看容器信息:

userdeMacBook-Pro:~ user$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c091389b4ff ubuntu:14.04 "/bin/sh -c 'while t…" About a minute ago Up seconds gallant_franklin

3.终止容器 ——docker stop

终止状态的容器可以使用docker ps -a命令查看:

userdeMacBook-Pro:~ user$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c091389b4ff ubuntu:14.04 "/bin/sh -c 'while t…" minutes ago Up minutes gallant_franklin

对于这些终止了的容器,可以通过docker start来重启;

docker restart命令会将一个运行态的容器终止,然后重新启动

4.进入容器

某些时候需要进入容器进行操作,有很多种方法,包括使用 docker attach命令或 nsenter工具等

1) attach 命令

使用方法:

  • 首先以后台方式打开一个容器
  • 然后使用docker attach +容器名 来进入该容器
userdeMacBook-Pro:~ user$ docker run -idt ubuntu:14.04
3adcf64dd30067011f15ef1c8b341f505f9900f382252da27d963c81aee4ba10 userdeMacBook-Pro:~ user$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3adcf64dd300 ubuntu:14.04 "/bin/bash" seconds ago Up seconds elastic_easley
3c091389b4ff ubuntu:14.04 "/bin/sh -c 'while t…" minutes ago Up minutes gallant_franklin userdeMacBook-Pro:~ user$ docker attach elastic_easley
root@3adcf64dd300:/#

但是使用 attach 命令有时候并不方便。

当多个窗口同时 attach 到同一个容器的时候,所有窗口都会同步显示。

当某个窗口因命令阻塞时,其他窗口也无法执行操作了。

2) nsenter 命令

工具在 util-linux 包2.23版本后包含,如果没有,本地的安装方法为:

$ cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-; cd util-linux-2.24;
$ ./configure --without-ncurses
$ make nsenter && sudo cp nsenter /usr/local/bin

使用方法:

nsenter启动一个新的shell进程(默认是/bin/bash), 同时会把这个新进程切换到和目标(target)进程相同的命名空间,这样就相当于进入了容器内部。

nsenter 要正常工作需要有 root 权限。 很不幸,Ubuntu 14.04 仍然使用的是 util-linux 2.20。安装最新版本的 util-linux(2.29)版,请按照以下步骤:

$ wget https://www.kernel.org/pub/linux/utils/util-linux/v2.29/util-linux-2.29.tar.xz; tar xJvf util-linux-2.29.tar.xz
$ cd util-linux-2.29
$ ./configure --without-ncurses && make nsenter
$ sudo cp nsenter /usr/local/bin

运行时有错:

userdeMacBook-Pro:util-linux-2.29 user$ ./configure --without-ncurses && make nsenter
...省略
warnings: -fno-common -Wall -Werror=sequence-point -Wextra -Wextra-semi -Wembedded-directive -Wmissing-declarations -Wmissing-prototypes -Wno-missing-field-initializers -Wredundant-decls -Wsign-compare -Wtype-limits -Wuninitialized -Wunused-parameter -Wunused-result -Wunused-variable -Wnested-externs -Wpointer-arith -Wstrict-prototypes -Wformat-security -Wimplicit-function-declaration Type 'make' or 'make <utilname>' to compile.
CCLD nsenter
clang: error: no input files
make: *** [nsenter] Error

说是找不到nsenter文件

后面分开运行:

$./configure --without-ncurses
$make

又有问题:

login-utils/login.c::: fatal error: 'sys/sendfile.h' file not found
#include <sys/sendfile.h>

查找资料后在发现上面的安装方法是linux系统的,sys/sendfile.h是Linux自带的文件,而我使用的是mac系统,所以打算使用别的方法来使用nsenter

后面发现其实不是这个问题,不应该运行mask,就是要运行make nsenter

后面就换了一个版本看看,下载2.24版本,也没有成功

后面使用github的jpetazzo/nsenter也没能成功

安装nsenter到/usr/local/bin:

userdeMacBook-Pro:~ user$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
Unable to find image 'jpetazzo/nsenter:latest' locally
latest: Pulling from jpetazzo/nsenter
5c90d4a2d1a8: Pull complete
c6c4c486dd77: Pull complete
0ed6ac9f06ed: Pull complete
404416bec766: Pull complete
4bf954ba4ae2: Pull complete
23f698ff1fd0: Pull complete
b39fba43fbdb: Pull complete
7889943d47f6: Pull complete
446df4bc8efe: Pull complete
6074415f722e: Pull complete
72024cea4c47: Pull complete
6c4b4f4219d3: Pull complete
93da7ec1688f: Pull complete
0c4337c5a938: Pull complete
Digest: sha256:a30e7da907a9abb715027677c21468005beee06251b7737c86f84fa148d572b0
Status: Downloaded newer image for jpetazzo/nsenter:latest
Installing nsenter to /target
Installing docker-enter to /target
Installing importenv to /target

-v, --volume list    Bind mount a volume 绑定装入卷

jpetazzo/nsenter容器将检测到/target是一个挂载点,并将nsenter二进制文件复制到其中

运行时有错误:/usr/local/bin/nsenter: /usr/local/bin/nsenter: cannot execute binary file

如果有小伙伴有解决的办法,希望能告知

为了连接到容器,你还需要找到容器的第一个进程的 PID,可以通过下面的命令获取:

 PID=$(docker inspect --format "{{ .State.Pid }}" <container>)

通过这个 PID,就可以连接到这个容器:

$ nsenter --target $PID --mount --uts --ipc --net --pid

3)exec命令

userdeMBP:~ user$ docker run -idt ubuntu:14.04
2084e92eea8c494023db35709d3d13054359ba71bca533371d04463652272a7b
userdeMBP:~ user$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2084e92eea8c ubuntu:14.04 "/bin/bash" seconds ago Up seconds boring_jackson
b4a512f0230f registry "/entrypoint.sh /etc…" days ago Up About an hour 0.0.0.0:->/tcp registry
userdeMBP:~ user$ docker exec -it 2084e92eea8c /bin/bash
root@2084e92eea8c:/#

还可以使用其来打印容器中的ip:

userdeMBP:~ user$ docker exec -it 2084e92eea8c ip a
: lo: <LOOPBACK,UP,LOWER_UP> mtu qdisc noqueue state UNKNOWN group default qlen
link/loopback ::::: brd :::::
inet 127.0.0.1/ scope host lo
valid_lft forever preferred_lft forever
: tunl0@NONE: <NOARP> mtu qdisc noop state DOWN group default qlen
link/ipip 0.0.0.0 brd 0.0.0.0
: ip6tnl0@NONE: <NOARP> mtu qdisc noop state DOWN group default qlen
link/tunnel6 :: brd ::
: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu qdisc noqueue state UP group default
link/ether ::ac::: brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/ brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

5.导出和导入容器

1) 导出容器——docker export

userdeMacBook-Pro:~ user$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd8f619aef03 jpetazzo/nsenter "bash" minutes ago Exited () minutes ago jovial_burnell
3adcf64dd300 ubuntu:14.04 "/bin/bash" hours ago Exited () hours ago elastic_easley
3c091389b4ff ubuntu:14.04 "/bin/sh -c 'while t…" hours ago Up hours gallant_franklin
f7efcb534bb0 ubuntu:14.04 "/bin/sh -c 'while t…" hours ago Exited () hours ago fervent_ritchie
db3bc701340a ubuntu:14.04 "/bin/bash" hours ago Exited () hours ago focused_sanderson
9dc39aa922f4 ubuntu:14.04 "/bin/echo 'Hello wo…" hours ago Exited () hours ago nostalgic_burnell
83fbcec3feda 568c4670fa80 "/bin/sh -c 'apt-get…" hours ago Exited () hours ago ecstatic_ritchie
889e5311532c nginx:v2 "nginx -g 'daemon of…" hours ago Exited () hours ago 0.0.0.0:->/tcp web2

然后导出上面的nginx:v2:

userdeMacBook-Pro:~ user$ docker export 889e5311532c > nginx.tar

然后就会在本地的~目录下生成nginx.tar文件

2)导入容器快照——docker import

userdeMacBook-Pro:~ user$ cat nginx.tar | docker import - test/nginx:v1
sha256:02548ab0445a4490b1801762d2d8c342a8ec4a38f1686165c4e6eb4f10fc87ad
userdeMacBook-Pro:~ user$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/nginx v1 02548ab0445a seconds ago 107MB

也可以通过指定 URL 或者某个目录来导入,如:

docker import http://example.com/exampleimage.tgz example/
imagerepo

⚠️用户既可以使用 docker load来导入镜像存储文件到本地镜像库,也可以使用 docker import来导入一个容器快照到本地镜像库。

这两者的区别在于容器快照文件(docker import)将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件(docker load)将保存完整记录,体积也要大。

此外,从容器快照文件导入时可以重新指定标签等元数据信息。

6) 删除容器——docker rm

删除一个处于终止状态(先docker stop)的容器

如果要删除一个运行中的容器,可以添加 -f参数。Docker 会发送 SIGKILL信号给容器。

清理所有处于终止状态的容器

先使用docker ps -a查看所有查看所有已经创建的包括终止状态的容器

一个个删除会很麻烦,可以使用下面的方法:

userdeMacBook-Pro:~ user$ docker rm $(docker ps -a -q)
dd8f619aef03
3adcf64dd300
f7efcb534bb0
db3bc701340a
9dc39aa922f4
83fbcec3feda
889e5311532c
f97514a2ac93
45104c30f94e
67cf9831aa1b
eb38c07055c8
40d9af4487ce
f44af23ba62c
b6ea66b4a0d8
b9606b2017da
6b94d698640d
60b85b6d1d40
a9905c000180
90dfc4e533af
Error response from daemon: You cannot remove a running container 3c091389b4ffb583b4cca578b751e0a4b4e1d556852fe58a5f3d50003cb95737. Stop the container before attempting removal or force remove
Error response from daemon: You cannot remove a running container f6a9a111a55d08f44da9b0a41755618e23baf3298f77b64f1c7375667721648b. Stop the container before attempting removal or force remove
userdeMacBook-Pro:~ user$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c091389b4ff ubuntu:14.04 "/bin/sh -c 'while t…" hours ago Up hours gallant_franklin
f6a9a111a55d kumavis/zeroclient "/bin/sh -c 'npm sta…" weeks ago Up hours 0.0.0.0:->/tcp zero-client_zeroClient_1
userdeMacBook-Pro:~ user$ docker stop 3c091389b4ff
3c091389b4ff
userdeMacBook-Pro:~ user$ docker stop f6a9a111a55d
f6a9a111a55d
userdeMacBook-Pro:~ user$ docker rm $(docker ps -a -q)
3c091389b4ff
f6a9a111a55d

上面可见其实docker rm默认是不会清除正在运行中的容器的,你需要先docker stop他们,再删除即可

此时再查看就发现容器已经清空了

userdeMacBook-Pro:~ user$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

7)限制资源:

1》内存

因为虚拟机容量有限,容器的内存如果总是增加,那么就可能会出现容器报错的情况,所以要对容器利用的资源进行限制,避免某个容器因占用太多资源而影响其他容器乃至整个 host 的性能

运行docker run命令时使用以下的参数进行限制即可:

 -m, --memory bytes         Memory limit,限制内存,如--memory=200M
--memory-swap 设置 内存+swap 的使用限额

⚠️如果在启动容器时只指定 -m 而不指定 --memory-swap,那么 --memory-swap 默认为 -m 的两倍

使用 progrium/stress 镜像来学习如何为容器分配内存。该镜像可用于对容器执行压力测试

参考https://www.cnblogs.com/CloudMan6/p/6986499.html

userdeMacBook-Pro:~ user$ docker run -it -m 200M --memory-swap=300M progrium/stress --vm  --vm-bytes 280M
Unable to find image 'progrium/stress:latest' locally
latest: Pulling from progrium/stress
a3ed95caeb02: Pull complete
871c32dbbb53: Pull complete
dbe7819a64dd: Pull complete
d14088925c6e: Pull complete
58026d51efe4: Pull complete
7d04a4fe1405: Pull complete
1775fca35fb6: Pull complete
5c319e267908: Pull complete
Digest: sha256:e34d56d60f5caae79333cee395aae93b74791d50e3841986420d23c2ee4697bf
Status: Downloaded newer image for progrium/stress:latest
stress: info: [] dispatching hogs: cpu, io, vm, hdd
stress: dbug: [] using backoff sleep of 3000us
stress: dbug: [] --> hogvm worker [] forked
stress: dbug: [] allocating bytes ...
stress: dbug: [] touching bytes in strides of bytes ...
stress: dbug: [] freed bytes
stress: dbug: [] allocating bytes ...
stress: dbug: [] touching bytes in strides of bytes ...
--vm :启动  个内存工作线程。
--vm-bytes 280M:每个线程分配 280M 内存

因为设置给每个线程分配的280M内存小于设置的300M内存,所以成功运行

但是如果每个线程分配的内存大于300M的话,就会出错,stress 线程报错,容器退出:

userdeMacBook-Pro:~ user$ docker run -it -m 200M --memory-swap=300M progrium/stress --vm  --vm-bytes 310M
stress: info: [] dispatching hogs: cpu, io, vm, hdd
stress: dbug: [] using backoff sleep of 3000us
stress: dbug: [] --> hogvm worker [] forked
stress: dbug: [] allocating bytes ...
stress: dbug: [] touching bytes in strides of bytes ...
stress: FAIL: [] () <-- worker got signal
stress: WARN: [] () now reaping child worker processes
stress: FAIL: [] () kill error: No such process
stress: FAIL: [] () failed run completed in 0s
userdeMacBook-Pro:~ user$

2》cpu

运行docker run命令时使用以下的参数进行限制即可:

 -c, --cpu-shares int       CPU shares (relative weight)CPU份额(相对权重),如--cpu-shares=;默认为1024

⚠️通过 cpu share 可以设置容器使用 CPU 的优先级

还是使用progrium/stress镜像:

userdeMacBook-Pro:~ user$ docker run --name test1 -it --cpuset-cpus="" -c  progrium/stress --cpu
stress: info: [] dispatching hogs: cpu, io, vm, hdd
stress: dbug: [] using backoff sleep of 3000us
stress: dbug: [] --> hogcpu worker [] forked
userdeMacBook-Pro:~ user$ docker run --name test2 -it --cpuset-cpus="" -c  progrium/stress --cpu
stress: info: [] dispatching hogs: cpu, io, vm, hdd
stress: dbug: [] using backoff sleep of 3000us
stress: dbug: [] --> hogcpu worker [] forked

两个都设置为1024,通过--cpuset-cpus="0"参数设置它们使用的是同一个CPU。因为当 CPU 资源充足时,设置 CPU 的权重是没有意义的。只有在容器争用 CPU 资源的情况下, CPU 的权重才能让不同的容器分到不同的 CPU 用量

然后调用docker stats命令来查看它们的CPU占比情况:

可见基本上是1:1的情况

Docker技术入门与实战 第二版-学习笔记-5-容器-命令及限制内存与cpu资源的更多相关文章

  1. Docker技术入门与实战 第二版-学习笔记-10-Docker Machine 项目-2-driver

    1>使用的driver 1〉generic 使用带有SSH的现有VM/主机创建机器. 如果你使用的是机器不直接支持的provider,或者希望导入现有主机以允许Docker Machine进行管 ...

  2. Docker技术入门与实战 第二版-学习笔记-8-网络功能network-3-容器访问控制和自定义网桥

    1)容器访问控制 容器的访问控制,主要通过 Linux 上的 iptables防火墙来进行管理和实现. iptables是 Linux 上默认的防火墙软件,在大部分发行版中都自带. 容器访问外部网络 ...

  3. Docker技术入门与实战 第二版-学习笔记-7-数据管理(volume)

    Docker 数据管理 为什么要进行数据管理呢?因为当我们在使用container时,可能会在里面创建一些数据或文件,但是当我们停掉或删除这个容器时,这些数据或文件也会同样被删除,这是我们并不想看见的 ...

  4. Docker技术入门与实战 第二版-学习笔记-3-Dockerfile 指令详解

    前面已经讲解了FROM.RUN指令,还提及了COPY.ADD,接下来学习其他的指令 5.Dockerfile 指令详解 1> COPY 复制文件 格式: COPY  <源路径> .. ...

  5. Docker技术入门与实战 第二版-学习笔记-8-网络功能network-1-单个host上的容器网络

    Docker 中的网络功能介绍 Docker 允许通过外部访问容器或容器互联的方式来提供网络服务 1) 外部访问容器 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -p或 -P参数 ...

  6. Docker技术入门与实战 第二版-学习笔记-8-网络功能network-2-相应配置

    1) 快速配置指南(详细使用下面会讲) 其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效 下面2个命令选项既可以在启动服务时指定,也可以 Docker 容器启动(dock ...

  7. Docker技术入门与实战 第二版-学习笔记-1-镜像

    镜像与容器之间的关系: 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体.容器可以被 创建.启动.停止.删除.暂停 ...

  8. Docker技术入门与实战 第二版-学习笔记-10-Docker Machine 项目-1-cli

    Docker Machine 是 Docker 官方编排(Orchestration)项目之一,负责在多种平台上快速安装 Docker 环境 Docker Machine是一种工具,它允许你在虚拟主机 ...

  9. Docker技术入门与实战 第二版-学习笔记-6-仓库

    仓库(Repository)是集中存放镜像的地方 一个容易混淆的概念是注册服务器(Registry). 实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像. ...

随机推荐

  1. window搭建Tomcat服务

    1.启动cmd 进入到Tomcat目录 D:\xy_ybb\XY\serviceXY\apache-tomcat-7.0.81\bin>cd D:\xy_ybb\XY\tomcat\apache ...

  2. 创建一个jdbc连接

    本文介绍如何建立一个jdbc连接进行数据库查询操作. 创建一个java工程,导入jar包. 作者使用mysql数据库,建立jdbc连接需要mysql数据库驱动jar包和jdbc连接jar包. 建立jd ...

  3. 解决 iframe 后退不是主页面后退(浏览器 history)问题

    前言:项目中的主页面里有 iframe,切换 iframe 的 src 地址之后,再点浏览器的回退之后,会导致 iframe 里面回退,而不是主页面回退. 问题 浏览器机制的原因,在 iframe 导 ...

  4. EF框架的code first

    需要添加EntityFramework的引用,在NuGet(在工具--库程序包管理--程序包管理控制台) 控制台输入Install-Package EntityFramework,目前版本是6.1.3 ...

  5. Donsen法则

    “专才”对越来越少的事物了解得越来越多,直到最后他对不存在的事物无所不知: 然而,“通才”对越来越多的事物了解得越来越少,直到他对一切事物一无所知.

  6. EF CodeFirst(四) 关系

    数据库表之间有一对一  一对多 多对多关系.那同样,CodeFirst也要能分析这些类之间的这些关系. CodeFirst可以自动通过分析类之间的属性导航属性 从而得出类之间的关系,自动确定外键. 一 ...

  7. POJ1458(KB12-L LCS)

    Common Subsequence Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 51319   Accepted: 21 ...

  8. JAVA generic array 泛型数组

    在JAVA中是不支持泛型数组的,不能通过 Z[] array=new Z[10] 这样的方式来创建数组,而是使用反射Aarry.newInstance来创建: 具体代码如下: public Z[][] ...

  9. stylus常用写法

    带参数 border-radius(val) -webkit-border-radius: val -moz-border-radius: val border-radius: val button ...

  10. HTML/CSS学习(二)

    续...... ============================================================================================ ...