项目学习地址:【牛客网C++服务器项目学习】
day16
- Linux的信号机制及其使用方式
信号是什么?信号是Linux中一种古老的消息通知机制。其主要的作用是告知进程有一个事件结束了。是一种在软件层面对中断的模拟。
信号的产生,可分为硬件产生和软件产生。硬件信号不是我们关注的重点,有Ctrl+c触发SIGINT信号,实现终止进程的功能;软件上的产生可以通过kill函数/命令、alarm闹钟函数、abort函数(功能不清楚)
重点应该是在【信号注册】上。
signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数中,有两个形参,分别代表需要处理的信号编号值和处理信号函数的指针。它主要是用于前32种非实时信号的处理,不支持信号的传递信息。但是由于使用简单,易于理解,因此在许多场合被程序员使用。
函数返回值:成功返回先前的信号处理函数指针,出错则返回SIG_ERR(-1)
sigaction函数
- 项目开发的规范组织方式
项目开发之【文件夹】命名规范
// 代码相关
src - 源代码
__test__ - 测试文件
lib - 库文件
core - 核心文件
demo - 示例
// 文档、资源相关
docs - 文档
images - 图片
misc - 杂项,miscellaneous的缩写
// 其他
bin - 命令脚本
- EPOLL的两种模式——ET和LT模式
所谓的ET边缘触发模式和LT水平触发模式,并没有那么神秘,其主要的内容如下:
-
- 边缘触发模式ET:使用该模式时,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。
-
-
- 所以,在使用ET模式的时候,为了保证数据都能被读取,一般要搭配【while】循环去读取数据。同时,为了不让读取函数在没有数据可以读时,阻塞住当前进程/线程,一般要将read/write函数设置为非阻塞模式,也即O_NONBLOCK
- 为什么在ET模式具有缺点的情况下,还在强调ET模式的读写,这是因为ET模式一般情况下会比LT模式效率更高。这是因为ET模式避免了多次调用epollwit函数,可以避免epoll上的资源消耗,从而提高了执行效率。
-
-
- 水平触发模式LT:使用该模式时,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,会继续通知你去处理未完成的读写事件。
- setsockopt函数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
做【端口复用】的。这个函数可以让相同地址的主机使用同一个端口。如果服务器因为断电意外退出,此时服务器一端在与客户端的TCP连接中会处于【FIN_WAIT_2】状态,此时立马再次启动服务器的话,【bind】函数也许会失败。所以我们使用这个函数,让sockfd对应的服务端程序能够重复使用这个端口号。
- epoll_ctl函数的参数宏EPOLLRDHUP,EPOLLONESHOT
EPOLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut down writing half of connection. (This flag is especially
useful for writing simple code to detect peer shutdown when using Edge Triggered monitoring.)
这是在Linux2.6.17之后新加入的一个特性。在之前的版本中,epoll监听的socket对端如果调用close主动关闭连接,epoll在这条连接上会检测到一个EPOLLIN事件,并交给上层程序去执行读。但是读一个关闭了写入端的socket通信来说,调用read函数会返回一个错误号。一般情况下我们是通过if( read(fd, readbuff, sizeof(readbuff) < 0)
来判断read函数调用是否会发生错误
为了进一步提高程序的效率,我们可以在底层就将对端关闭连接这种情况处理了,所以自Linux2.6.17之后新加入了一个EPOLLRDHUP 宏,通过将epoll事件设置为event.events = EPOLLIN | EPOLLRDHUP
便能在底层进行处理
EPOLLONESHOT:设置EPOLLONESHOT可以保证一个socket只能被一个线程占用,避免了多个线程收到了一个socket在前后时间发来的不同数据,造成数据混乱,程序运行出错的情况。EPOLLONESHOT的作用是对加入到epollfd中的socket,该socket日后接收到数据后,只会响应一次,无论日后有多少数据再次发来,在epoll_wait中都不再对这个socket作出响应,也就避免了其他线程会获取到这个socket上的数据。
- extern关键字
在【牛客网-高老师】的视频中我们可以看到,对epoll的几个包装函数,采用了在main.cpp中通过extern关键字进行声明,在http_conn.cpp中进行定义的操作。extern的功能就是声明一个变量或者函数,声明之后在其他源文件中进行定义,只要在主程序中包含了相应的源文件,就不怕编译器在编译的时候找不到。
为什么要这么做?我猜测是【高老师】懒得重新写一个头文件,一种偷懒的方式。