epoll模型
epoll模型只有linux系统才有
epoll模型只有从linxu内核2.4版本之后才有
epoll从2.4内核到目前的4.X内核,没有更新的模型了,说明epoll模型本身已经很完美了
select的问题:
监视列表无法扩容
监视列表和返回的激活列表混在了一起
效率低下:
select需要自己管理激活的套接字
select查询哪个套接字激活了是一个双重循环,效率低下
select的内核部分,监视的套接字也是一个数组,查询哪个套接字激活了,效率也是低的
poll的问题:
效率低下:
select的内核部分,监视的套接字也是一个数组,查询哪个套接字激活了,效率也是低的
epoll彻底结局了select 和 poll的问题
epoll允许自动扩容
epoll的内核部分是以二叉树存储所有的描述符,所在查看哪个描述符激活的时候,效率很高
epoll会把所有激活的描述符,放在一个数组中,直接提供给用户,编程效率高
原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数调用:int count = epoll_wait(fd,数组,监视的描述符的数量,-1)
功能描述:阻塞并等待监视列表中的描述符激活,并且将所有激活的描述符,放在一个数组中提供给我们
参数解析:
参数 epfd:监视列表
epoll要求先创建一个文件,然后将所有的要监视的描述符,写入这个文件中,并监视
参数 events:用来存放所有激活的描述符的数组
参数 maxevents:最大的监视描述符的数量
参数 timeout:阻塞时长,单位为毫秒,
0:表示不阻塞
-1:表示常阻塞
返回值:激活的描述符的数量
epoll如何创建监视列表
原型:int epoll_create(int size);
调用:int epfd = epoll_create(想要监视的描述符数量)
功能描述:创建一个文件,该文件最多能够监视 size个字节的描述符
参数解析:
参数 size:监视的描述符的最大值
返回值:返回创建出来的文件的描述符
原型:int epoll_create1(int flags);
调用:int epfd = epoll_create1(EPOLL_CLOEXEC )
功能描述:创建一个文件,用来监视描述符,该文件能够监视的描述符数量能够自动扩容
epoll如何将被监视的描述符写入文件中
原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
调用:epoll_ctl();
功能描述:操作监视列表,可以删除,可以添加,可以指定监视类型(监视可读,监视可写)
参数解析:
参数 epfd:监视列表
参数 op:具体针对监视列表的操作行为
EPOLL_CTL_ADD:将参数 fd 描述符,添加进入epfd监视列表,并且由参数event决定,以何种形式进行监视
EPOLL_CTL_DEL:将参数 fd 描述符,从epfd监视列表中移除,此时event参数被忽略,可以直接写NULL
EPOLL_CTL_MOD:根据参数 event 更改 fd描述符的监视类型
参数 fd:等待操作的描述符
参数 event:一个结构体指针,结构如下
struct epoll_event {
uint32_t events; /* Epoll events */ 监视的事件类型
EPOLLIN:监视描述符是否可读
EPOLLOUT:监视描述符是否可写
epoll_data_t data; /* User data variable */
};
data 也是一个结构体,结构如下
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
其中关键数据是:fd
epoll_event 这个结构体,在 epoll_wait的时候,传入的是这个结构体的数组
我们判断哪个描述符激活,就是依靠这个fd来判断的
epoll_ctl函数调用的时候,这个events.data.fd 必须和 参数 fd保持一致
epoll的代码模型
int epfd = epoll_create1(EPOLL_CLOEXEC)
struct epoll_event event
event.events = EPOLLIN(监视类型)
event.data.fd = 想要监视的描述符
epoll_ctl(epfd,EPOLL_CTL_ADD,想要监视的描述符,&event)
struct epoll_event readfds;
while(1){
int signal_count = epoll_wait(epfd,readfds,监视描述符的数量,-1)
for(遍历readfds){
判断哪个描述符激活了{
执行对应的逻辑cv
}
}
}
什么是域套接字
域套接字只能用来在本地中,用在进程与进程之间的通信作用
原本最早,套接字也只有域套接字,套接字也只是用来做进程间通信的
直到后来出现了TCP协议,TCP协议使用套接字来实现的进程间通信,我们就把只能做进程间通信的套接字,改名,称为域套接字
思维方式暂时回到IPC进程间通信里面
不再需要 ip 和 port
反而需要:保证2个进程访问到同一个套接字文件
域套接字的类型
流式套接字:SOCK_STREAM
报文套接字:SOCK_DGRAM
域套接字同时支持上述2种类型
流式域套接字的操作流程
流程概要
int sock = socket(AF_UNIX / AF_LOCAL,SOCK_STREAM,0)
绑定套接字文件:注意域套接字使用的地址信息结构体类型为 struct sockaddr_un
如何查看这个结构体的结构: man 7 unix,查询结果如下
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */ 必须写AF_UNIX
char sun_path[108]; /* pathname */ 域套接字文件的路径名
};
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strpcy(addr.sun_path,"域套接字的路径名");
注意:在绑定域套接字文件之前,一定保证该域套接字文件是不存在,域套接字文件最终由bind函数创建并绑定
if(access("域套接字文件路径名",F_OK)==0){
remove()
ulink()
}
bind(sock,(struct sockaddr*)&addr,sizeof(addr));//确保域套接字不存在的情况下,bind函数会自动创建域套接字文件并绑定。
如果bind函数发现域套接字文件是存在的,则报错
创建套接字
int sock = socket(AF_UNIX / AF_LOCAL,SOCK_STREAM,0)
创建域套接字地址信息结构体
struct sockaddr_un addr;
该结构体结构如下
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */ 必须写AF_UNIX
char sun_path[108]; /* pathname */ 域套接字文件的路径名
};
确保域套接字不存在
access函数判断是否存在
第2个参数:F_OK 表示判断文件是否存在
返回值:文件如果存在返回0,不存在返回-1
删除文件
int unlink(const char *path);
删除一个非目录文件
int rmdir(const char *path);
删除一个目录文件
int remove(const char *pathname);
删除一个任意文件
绑定域套接字
bind(sock,(struct sockaddr*)&addr,sizeof(addr))
总结
整个流程,和TCP服务器不一样的地方就2点
第一点:地址信息结构体不一样TCP是 sockaddr_in,域套接字是 sockadd_un
第二点:域套接字在bind之前,一定确保域套接字文件是不存在的
服务器
客户端
流式域套接字接收端模型(服务器)
int server = socket()
struct sockaddr_un_t addr;
if(access() == 0){
remove()
}
bind()
listen
int client = accept()
while(1){
read())
}
① socket
② 准备套接字地址信息结构体,并确保套接字文件不存在
③ bind(必须有)
④ listen
⑤ accept
⑥ read
流式域套接字发送端模型(客户端)
int client = socket()
struct sockaddr_un_t addr
connect()
while(1){
write()
}
① socket 创建套接字
② 准备地址信息结构体
③ 一定不能有bind,不用确保文件不存在(套接字文件必须存在)
④ connect() 必须有
⑤ write
报文域套接字的操作流程
流程概要
接收端
int sock = socket(AF_UNIX,SOCK_DGRAM,0)
struct sockaddr_un_ t addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path,"套接字文件路径名");
// 服务器一定绑定
bind(sock,(addr_t*)&addr,sizeof(addr))
while(1){
recv()
}
发送端
int sock = socket(AF_UNIX,SOCK_DGRAM,0)
struct sockaddr_un_ t addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path,"套接字文件路径名");
connect(sock,(addr_t*)&addr,sizeof(addr));//可以有,也可以没有
while(1){
sendto(sock,数据,数据长度,0,(struct addrsock*)&addr,sizeof(addr));
}
创建报文域套接字
int sock = socket(AF_UNIX / AF_LOCAL,SOCK_DGRAM,0)
准备地址信息结构体
struct sockaddr_un_t addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path,"套接字文件路径名");
确保域套接子文件一定不存在(客户端没有这一步,只有服务器)
if(access("域套接字文件",F_OK) == 0){
remove("域套接字文件")
}
绑定域套接字文(服务器)/链接域套接字文件(客户端,可选)
服务器
bind(sock,(struct sockaddr*)&addr,sizeof(addr))
客户端
connect(sock,(struct sockaddr*)&addr,sizeof(addr))
使用read 或者 recv 或者 recvfrom 读取发送端发来的消息 / 使用 sendto发送消息
![](https://i-blog.csdnimg.cn/direct/09a4b60030a0489499d3ff80dabf0d58.png)
![](https://i-blog.csdnimg.cn/direct/54eb354a59524a0e92bed64a33f2bdda.png)
模型总结
报文域套接字接收端(服务器)
int server = socket()
struct sockaddr_un_t addr;
if(access()){
remove
}
bind()
while(1){
recv()
}
① socket() 创建报文域套接字
② 准备地址信息结构体,并充实数据,并确保该套接字文件一定不存在
③ bind() 必须的
④ read / recv / recvfrom
报文域套接字发送端(客户端)
int server = socket()
struct sockaddr_un_t addr;
//connect() 可选
while(1){
sendto()
}
① socket() 创建报文域套接字
② 准备域套接字地址信息结构体,并充实数据(套接字文件一定要存在)
③ 一定不能bind(),connect()可选
④ 一定是使用 sendto() 发送数据
安装sqlite3数据库以及sqlite3函数库
1:sudo apt install sqlite3 //安装数据库
2:sudo apt install libsqlite3-dev // 安装数据库的函数库
什么是数据库
一种存放数据的文件,但是该文件拥有特殊的结构
第一层结构:数据库本身
第二层结构体:数据库中存放了若干张表单
每一张表单的字段构成各不相同
第三层结构:一张表单中,所有字段都能存放信息
一组字段中的所有数据,就是一条记录
最终,很多很多条件,格式不同的记录,组成了一个完整的数据库
常用数据库类型
① sqlite3:一个允许部署在本地的轻量级数据库
特别适合用于嵌入式开发
② mysql:是一个部署在服务端的,需要网络连接的数据。
如果一定要部署在本地的话,也是需要安装一个mysql服务器的
适合一些应用层程序开发
如何操作sqlite3数据库
打开数据库
输入指令:sqlite3 数据库名.db,出现以下画面就OK了
sqlite3数据库中的指令规则
1:显示指令
必须以 . 开头,回车确认输入
.table :查看当前数据库中所有表单的名字
.schema 表单名: 查看当前数据库中指定的表单中字段的结构
如果没有写表单名,则表示查看所有表单的字段结构
.head(er) on 在查看数据的时候,打开抬头
.mode column 以字段对齐的形式显示数据
.quit 保存并退出数据库
2:操作指令
必须以 ;结尾,然后再键入回车,如果没有 ; 的话,sqlite3不会认定这条指令结束了
如果忘记敲 ; 了,换一行再补一个 ; 也是没有问题的
3:数据库中的指令 大小写无所谓
创建表单
create table 表单名 (if not exists)(
字段名1 数据类型 约束类型 ,
...... ,
字段名n 数据类型 约束类型
);
字段名:变量名
数据类型:
整形 INTERGER
浮点型 REAL
字符串 TEXT
约束类型:什么是约束?
在添加数据的时候,必要的为这些数据添加一些取值范围,保证这些数据不会过于离谱,添加取值范围这个事,就是依赖约束实现的
① 主键约束 :primary key
什么是键:用于比较大小,从而确定数据存放位置的关键数据。这个数据不能重复,不能修改
被主键约束的字段,成为了表单中的 "键",剩下的都是 "值"
被主键约束的字段,还有一个修饰词: auto increment
功能为:在添加新记录的时候,如果被 auto increment 所修饰的字段,没有填充数据的话,则数据库会根据最近一次填充的数据,自增1后,为当前数据填充
② 默认约束: default 默认值
在添加新纪录的时候,如果没有为拥有默认约束的字段,填充数据的话,数据库会选用默认值填充
③ 非空约束:not null
在添加新记录的时候,如果没有为 非空约束 所修饰的字段,填充数据的话,则本次记录添加失败
④ 检查约束:check(检查条件)
在添加记录的时候,会检查被检查约束的字段所填充的数据,是否满足"检查条件",如果不满足则添加失败
例如:为 字段"成绩" 添加检查约束,约束条件为成绩的取值范围在 0~150分之间
check(成绩>=0 AND 成绩<=150)
再例如:为字段"性别"添加检查约束,要求性别只能是 "男" 或者 "女"
check(性别="男" OR 性别="女")
向表单中添加数据
insert into 表单名(字段1,字段2,....,字段n) values(数据1,数据2,...,数据 n)
为表单中添加一条新的记录,但是只填充 字段1 ~ 字段n 这几个字段的数据
如果有任何字段对应的数据,违反了约束,则添加失败
查看表单中数据
select 字段1,字段2,...,字段n from 表单名
查看指定表单中的指定字段的所有数据
select * from 表单名
查看指定表单中的所有字段的所有数据
insert 指令和 select 指令可以配合使用
Plain Text
自动换行
insert into 表单1(字段1,字段2,....,字段n) select 字段1,字段2,...,字段n from 表单2
先查询出表单2中的所有指定字段的数据,再将这些数据,对应的添加到表单1中的每一个字段中去
查看某个特定的数据
where子句:
只要有任何附加的查询条件的时候,在英语语法应该写条件的地方写上 where 条件
比如说,想要查看 姓名为"张三"的所有信息
select * from stu where 姓名="张三"
修改表单中指定数据
update 表单名 set 字段名=新数据 where条件定位
例如:将姓名为"张三"的成绩,改成50分
update stu set 成绩=50 where 姓名="张三"
删除表单中的指定数据
delete from 表单名 where 条件定位
千万小心:如果不写 where 条件定位的话,会将表单中所有数据全都删除
一般来说,为了防止数据的误删,基本上不用delete指令
一般会在表单中添加一个字段叫做 "delete" ,他的值只有0或者非0
查询的时候,只要在最后加上 where delete=0,这样一来 delete=1也就是所谓的被删除的数据,就查不出来了
排序显示所有数据
order by 字段名
例如:想要排序显示 成绩
select * from stu order by score 默认按照成绩升序排序
想要降序的话,在最后写上关键字 desc 即可
范围查看
limit x offset y
从第y行开始,显示x条记录,y从0开始
如果offset不写,那么默认就是从0开始
如果说:想要显示,成绩最高的3个学生的信息
select * from stu order by score desc limit 3
模糊查询
like 子句 % _ (* ?)
使用like的时候,使用 _ 表示1个字符的站位,使用 % 表示 0~n个字符的占位
例如:
select * from stu where name like "徐_"
查询姓徐的,并且名字只有1个字的
select * from stu where name like "_徐_"
查询名字3个字的,并且中间那个字是 徐
select * from stu where name like "徐%"
查询姓徐的,名字有几个字无所谓,哪怕只有徐,没有名字也行
select * from stu where name like "%徐%"
查询所有名字中带 徐 的
为指定表单添加新的字段
alter table 表单名 add column 字段名 数据类型 约束类型
重命名表单名
alter table 原先的表单名 rename to 新的表单名
删除表单
drop table 表单名
重命名字段名
sqlite3 不支持重命名字段名,mysql是支持的
所以,我们如果想重命名字段名,需要用逻辑去实现
① 将当前表单名修改了,随便改成什么
② 创建一个新的表单,表单名是原先那个表单的名字
除了要修改的字段的名字,改成新的字段名之外
其他所有字段名直接照抄
③ 使用 insert + select 语句,将原先表单中的所有数据,拷贝到新的表单中去
④ 删除已经没有用的表单
删除字段
sqlite3 不支持删除字段,mysql是支持
所以,要通过逻辑去实现
① 重命名表单
② 以原先的表单名创建新表单
不要的字段压根不写,其他字段照抄
③ 使用 insert + select 语句把需要的字段中的所有数据拷贝过去
④ 删除不需要的表单