题外话,PA里面也有很不错的Linux基础基础

传送门:https://nju-projectn.github.io/ics-pa-gitbook/ics2019/linux.html

静态库与动态库

什么玩意?

库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量函数

​ 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行

​ 库文件有两种,静态库动态库(共享库)。

静态库与动态库的区别

复制代码到程序的时机不同

​ 静态库在程序的链接阶段被复制到了程序中;

​ 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用

库的好处

1.代码保密

2.方便部署和分发

静态库的制作与使用

命名规则

Linux : libxxx.a(库文件的名字)

lib : 前缀(固定)

xxx : 库的名字,自己起(注意,在使用gcc编译时需要的是xxx,不含lib前缀

.a : 后缀(固定)

Windows : libxxx.lib

静态库的制作

◆ gcc 获得 .o 文件

◆将 .o 文件打包,使用 ar 工具(archive)

ar rcs libxxx.a xxx.o xxx.o

​ r –将文件插入备存文件中

​ c –建立备存文件

​ s –索引

root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c 0 directories, 6 files
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# gcc -c add.c div.c mult.c sub.c
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# ls
add.c div.c head.h mult.c sub.c
add.o div.o main.c mult.o sub.o
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── head.h
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o 0 directories, 10 files
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# ar rcs libcalc.a add.o sub.o mult.o div.o
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── head.h
├── libcalc.a
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o 0 directories, 11 files

静态库的使用

在写项目的时候,一般会进行分包操作

一个常见的项目树如下:

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# tree
.
├── include //放头文件
│ └── head.h
├── lib //放库文件
├── main.c //测试文件
└── src //源代码
├── add.c
├── div.c
├── mult.c
└── sub.c 3 directories, 6 files

把之前制作的静态库文件libcalc.a复制到 lib 目录下即可,然后去编译

ps:使用库文件时,还有同时有其依赖的头文件

编译文件

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app
main.c:2:10: fatal error: head.h: No such file or directory
#include "head.h"
^~~~~~~~
compilation terminated.

这里报错的原因是:编译时,需要把头文件的代码拷贝到main.c中,但是我们include的时候用的是相对路径,main.c与head.h并不在统一目录下,因此是识别不到的

解决方法1:让编译文件(main.c)与头文件(head.h)在同一目录下

解决方式2(优雅):使用gcc提供的参数选项 -I 在编译时查找指定目录下的头文件

gcc main.c -o app -I ./include

又报错

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app -I ./include
/tmp/cct81thC.o: In function `main':
main.c:(.text+0x3a): undefined reference to `add'
main.c:(.text+0x5c): undefined reference to `subtract'
main.c:(.text+0x7e): undefined reference to `multiply'
main.c:(.text+0xa0): undefined reference to `divide'
collect2: error: ld returned 1 exit status

原因:main中使用了上述函数,但是编译时只找到了 .h 中的声明,没有找到库文件中的定义

还需要追加gcc参数,使用 -l 指定使用哪个库文件(只需要库名字,前缀lib不需要)进行编译, -L 指定到某个目录下找

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app -I ./include -l calc -L ./lib
root@ubuntu:/home/ag/cpp/Linux/lesson05/library# ls
app include lib main.c src

./app可以正常执行

动态库的制作与使用

命名规则

Linux : libxxx.so(库文件的名字)

lib : 前缀(固定)

xxx : 库的名字,自己起(注意,在使用gcc编译时需要的是xxx,不含lib前缀

.so : 后缀(固定,在linux下是一个可执行文件)

Windows : libxxx.dll

动态库的制作

◆ gcc 获得 .o 文件,得到和位置无关的代码

gcc -c –fpic/-fPIC axx.c bxx.c

◆ gcc得到动态库

gcc -shared a.o b.o -o libcalc.so
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# gcc -c -fpic add.c div.c sub.c mult.c
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# ls
add.c add.o div.c div.o head.h main.c mult.c mult.o sub.c sub.o
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# gcc -shared add.o sub.o mult.o div.o -o libcalc.so
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# ls
add.c div.c head.h main.c mult.o sub.o
add.o div.o libcalc.so mult.c sub.c

然后把 .so 拷贝到要使用的地方

root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# cd ..
root@ubuntu:/home/ag/cpp/Linux/lesson06# cd library/
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# cp ../calc/libcalc.so ./lib/
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# tree
.
├── app
├── include
│ └── head.h
├── lib
│ └── libcalc.so
├── main.c
└── src
├── add.c
├── div.c
├── mult.c
└── sub.c 3 directories, 8 files

编译main.c

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main
main.c:2:10: fatal error: head.h: No such file or directory
#include "head.h"
^~~~~~~~
compilation terminated.

报错,和静态库使用的时候的原因一样

需要指定头文件位置、库文件位置、库文件名字

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main -I include/
/tmp/ccOgCtBV.o: In function `main':
main.c:(.text+0x3a): undefined reference to `add'
main.c:(.text+0x5c): undefined reference to `subtract'
main.c:(.text+0x7e): undefined reference to `multiply'
main.c:(.text+0xa0): undefined reference to `divide'
collect2: error: ld returned 1 exit status
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main -I include/ -L lib/ -l calc
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ls
include lib main main.c src

可见,成功编译了,但是运行编译后的文件又报错

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ./main
./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
动态库加载失败原因

造成上述错误的原因与动态库加载原理有关

库文件工作原理

静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中

动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中

程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ldd main
linux-vdso.so.1 (0x00007fff808bb000)
libcalc.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efca544e000)
/lib64/ld-linux-x86-64.so.2 (0x00007efca5a41000)

可以看到,我们创建的动态库文件没有被找到,而标准的c库文件是被识别到的

如何定位共享库文件?

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系

统的动态载入器来获取该绝对路径。

对于elf格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的

DT_RPATH段 ——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib//usr/lib目录,找到库文件后将其载入内存。

解决动态库加载失败问题

临时添加环境变量
root@ubuntu:/home/ag# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ag/cpp/Linux/lesson06/library/lib
root@ubuntu:/home/ag# echo $LD_LIBRARY_PATH
:/home/ag/cpp/Linux/lesson06/library/lib

此时可以在当前终端看到,编译后的文件main已经与动态库链接

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ls
include lib main main.c src
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ldd main
linux-vdso.so.1 (0x00007ffd93bf7000)
libcalc.so => /home/ag/cpp/Linux/lesson06/library/lib/libcalc.so (0x00007f32c4b61000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32c4770000)
/lib64/ld-linux-x86-64.so.2 (0x00007f32c4f65000)

关闭终端再打开其他终端,仍然会报错

永久配置环境变量
用户级别

回到home目录下(或者在当前目录下操作~/.bashrc也行)

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# cd
root@ubuntu:~# ls
snap
root@ubuntu:~# ll
total 68
drwx------ 7 root root 4096 Feb 5 21:14 ./
drwxr-xr-x 24 root root 4096 Feb 2 16:50 ../
-rw------- 1 root root 8887 Feb 9 06:36 .bash_history
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
drwx------ 4 root root 4096 Nov 9 04:38 .cache/
drwx------ 3 root root 4096 Nov 7 17:50 .gnupg/
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
drwx------ 6 root root 4096 Nov 8 03:06 snap/
drwx------ 2 root root 4096 Nov 7 18:41 .ssh/
-rw------- 1 root root 9616 Feb 5 21:14 .viminfo
drwxr-xr-x 5 root root 4096 Feb 9 19:51 .vscode-server/
-rw-r--r-- 1 root root 183 Nov 7 18:45 .wget-hsts
-rw------- 1 root root 52 Nov 7 18:38 .Xauthority

找到隐藏文件 .bashrc ,编辑该文件

vim .bashrc

使用shift+g跳转到最后一行,按o往下插入一行

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ag/cpp/Linux/lesson06/library/lib

保存退出,使修改后的bashrc生效:

root@ubuntu:~# . .bashrc
root@ubuntu:~#

注:

1、. .bashrc等于source .bashrc

2、在终端1中配置bashrc后,在另一终端2中也要重新source一次才可以正常链接

系统级别

这时需要配置系统的profile文件

sudo vim etc/profile

同样在最后一行加入export环境变量即可

然后source一下(不用加sudo)

root@ubuntu:/etc# source etc/profile

如果识别不到,就source一下

静态库与动态库对比

程序编译成可执行程序的过程

静态库处理过程

动态库处理过程

静态库的优缺点

优点:

◆静态库被打包到应用程序中加载速度快

◆发布程序无需提供静态库,移植方便

缺点:

◆消耗系统资源,浪费内存

◆更新、部署、发布麻烦

动态库的优缺点

优点:

◆可以实现进程间资源共享(共享库)

◆更新、部署、发布简单

◆可以控制何时加载动态库

缺点:

◆加载速度比静态库慢

◆发布程序时需要提供依赖的动态库

Makefile

什么是 Makefile

​ 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。

​ Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。

Makefile 文件命名和规则

文件命名

makefile 或者 Makefile

Makefile 规则

一个 Makefile 文件中可以有一个或者多个规则

目标 ...: 依赖 ...
命令(Shell 命令)
...

目标:最终要生成的文件(伪目标除外)

依赖:生成目标所需要的文件或是目标

命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进

Makefile 中的其它规则一般都是为第一条规则服务的。

一个简单的Makefile例子

app: sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app

工作原理

命令在执行之前,需要先检查规则中的依赖是否存在

  • 如果存在,执行命令
  • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令

检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

  • 如果依赖的时间比目标的时间晚,需要重新生成目标
  • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被

    执行

升级一下之前的简单例子,检测每个依赖是否存在并且重新生成被修改的目标

app: sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app sub.o : sub.c
gcc -c sub.c -o sub.o
add.o : add.c
gcc -c add.c -o add.o
add.o : div.c
gcc -c div.c -o div.o
mult.o : mult.c
gcc -c mult.c -o mult.o
main.o : main.c
gcc -c main.c -o main.o

但是这么写非常不简洁,文件一多就很麻烦

还得改进

变量

自定义变量

变量名 = 变量值

例如,var = hello

预定义变量
AR : 归档维护程序的名称,默认值为 ar

CC : C 编译器的名称,默认值为 cc

CXX : C++ 编译器的名称,默认值为 g++

$@ : 目标的完整名称

$< : 第一个依赖文件的名称

$^ : 所有的依赖文件
获取变量的值

$ (变量名)

例如,$(var)

模式匹配

' % '是通配符,可以匹配一个字符串

例如:%.o可以匹配出文件结尾为 .o 的所有文件

所以我们可以进行如下改写

add.o : add.c
gcc -c add.c -o add.o
#等于
%.o : %.c
gcc -c add.c -o add.o

gcc -c add.c -o add.o也应该简化掉

于是第三版简单例子可以写为:

src=sub.o add.o mult.o div.o main.o
target=app $(target): $(src)
$(CC) $(src) -o $(target) %.o : %.c
$(CC) -c $< -o $@

解释一下:

%.o : %.c 匹配到了src中的每一个文件

$< 则按顺序往下执行指令

src获取的方式还可以再优化

函数

$(wildcard PATTERN...)

​ 功能:获取指定目录下指定类型的文件列表

​ 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔

​ 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔

​ 示例:

$(wildcard *.c ./sub/*.c)

​ 返回值格式: a.c b.c c.c d.c e.c f.c

$(patsubst <pattern>,<replacement>,<text>)

​ 功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以替换。可以包括通配符%,表示任意长度的字串。如果中也包含%,那么,中的这个%将是中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)

​ 返回:函数返回被替换过后的字符串

​ 示例:

$(patsubst %.c, %.o, x.c bar.c)

​ 返回值格式: x.o bar.o

于是第四版简单例子可以改成:

#定义变量
# sub.o add.o mult.o div.o main.o
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o,$(src))
target=app
$(target): $(objs)
$(CC) $(objs) -o $(target) %.o:%.c
$(CC) -c $< -o $@ .PHONY:clean #伪目标,不生成clean
clean:
rm $(objs) -f

GDB调试

什么是 GDB

GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

一般来说,GDB 主要帮助你完成下面四个方面的功能:

​ 1.启动程序,可以按照自定义的要求随心所欲的运行程序

​ 2.可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)

​ 3.当程序被停住时,可以检查此时程序中所发生的事

​ 4.可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG

准备工作

通常,在为调试而编译时,我们会()关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,-Wall在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。

gcc -g -Wall program.c -o program

-g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。

GDB 命令 –启动、退出、查看代码

启动和退出

gdb 可执行程序
quit

给程序设置参数/获取设置参数

set args 10 20
show args

查看当前文件代码

list/l (从默认位置显示)
list/l 行号 (从指定的行显示)
list/l 函数名(从指定的函数显示)

查看非当前文件代码

list/l 文件名:行号
list/l 文件名:函数名

设置显示的行数

show list/listsize
set list/listsize 行数

GDB命令 –断点操作

设置断点

b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数

查看断点

i/info b/break

删除断点

d/del/delete 断点编号

设置断点无效/生效

dis/disable 断点编号
ena/enable 断点编号

设置条件断点(一般用在循环的位置)

b/break 10 if i==5 //比如有个for循环,这里是循环到i=5才会停在断点上

GDB命令 –调试命令

运行GDB程序

start(程序停在第一行)
run(遇到断点才停)

继续运行,到下一个断点停

c/continue

向下执行一行代码(不会进入函数体)

n/next

变量操作

p/print 变量名(打印变量值)
ptype 变量名(打印变量类型)

向下单步调试(遇到函数进入函数体)

s/step
finish(跳出函数体)

自动变量操作

display 变量名(自动打印指定变量的值)
i/info display
undisplay 编号

其它操作

set var 变量名=变量值 (循环中用的较多)
until (跳出循环)

【webserver 前置知识 01】Linux系统编程入门的更多相关文章

  1. Linux系统编程温故知新系列 --- 01

    1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...

  2. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  3. LINUX系统编程 由REDIS的持久化机制联想到的子进程退出的相关问题

    19:22:01 2014-08-27 引言: 以前对wait waitpid 以及exit这几个函数只是大致上了解,但是看REDIS的AOF和RDB 2种持久化时 均要处理子进程运行完成退出和父进程 ...

  4. Linux网络编程入门 (转载)

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  5. 【转】Linux网络编程入门

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  6. 《转》Linux网络编程入门

    原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...

  7. linux系统编程:cp的另外一种实现方式

    之前,这篇文章:linux系统编程:自己动手写一个cp命令 已经实现过一个版本. 这里再来一个版本,涉及知识点: linux系统编程:open常用参数详解 Linux系统编程:简单文件IO操作 /*= ...

  8. 《Linux系统编程(第2版)》

    <Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...

  9. Linux系统编程博客参考

    通过看前人的博客更易于把握知识要点 http://www.cnblogs.com/mickole/category/496206.html <Linux系统编程> http://www.c ...

  10. linux系统编程之文件与io(一)

    经过了漫长的学习,C语言相关的的基础知识算是告一段落了,这也是尝试用写博客的形式来学习c语言,回过头来看,虽说可能写的内容有些比较简单,但是个人感觉是有史起来学习最踏实的一次,因为里面的每个实验都是自 ...

随机推荐

  1. 使用shell进行简单分析增量更新时间的方法

    使用shell进行简单分析增量更新时间的方法 思路 产品里面更新增量时耗时较久, 想着能够简单分析下哪些补丁更新时间久 哪些相同前缀的补丁更新的时间累积较久. 本来想通过全shell的方式进行处理 但 ...

  2. 谈JVM参数GC线程数ParallelGCThreads合理性设置

    作者:京东零售 刘乐 导读:本篇文章聚焦JVM参数GC线程数的合理配置,从ParallelGCThreads参数含义.参数设置,到参数实验以及修改意见进行解析. 1. ParallelGCThread ...

  3. 玩一玩 golang 1.21 的 pgo 编译优化

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 1.下载镜像 暂时不想替换本机的 golang 版本,于是 ...

  4. TienChin 活动管理-添加活动页面

    后端 ActivityController.java @Resource private IChannelService iChannelService; /** * 获取渠道列表 * * @retu ...

  5. TienChin 活动管理-活动列表展示

    后端 ActivityVO /** * @author BNTang * @version 1.0 * @description 活动管理VO * @since 2023-23-05 **/ publ ...

  6. SqlSugar更新数据

    1.根据实体对象更新 所谓按实体对象更新就是:db.Updateable(参数对象) 有参数的重载 db.Updateable(实体或者集合).ExecuteCommand() //右标题1 下面的所 ...

  7. 21.12 Python 实现网站服务器

    Web服务器本质上是一个提供Web服务的应用程序,运行在服务器上,用于处理HTTP请求和响应.它接收来自客户端(通常是浏览器)的HTTP请求,根据请求的URL.参数等信息生成HTTP响应,并将响应返回 ...

  8. C/C++ 反汇编:分析类的实现原理

    反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解.外挂技术.病毒分析.逆向工程.软件汉化等领域,学习和理解反汇编对软件调试.系统漏洞挖掘.内核原理及理解高级语言代码都有相当大的帮助, ...

  9. C++ Boost 函数与回调应用

    #include <iostream> #include <string> #include <boost\bind.hpp> using namespace st ...

  10. 多路io复用epoll [补档-2023-07-20]

    多路io- epoll 4-1简介 ​ 它是linux中内核实现io多路/转接复用的一个实现.(epoll不可跨平台,只能用于Linux)io多路转接是指在同一个操作里,同时监听多个输入输出源,在其中 ...