close并非立即关闭一个连接,而是将fd的引用数减1。
零拷贝函数:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
零拷贝函数:
```
ssize_t splice(int in_fd, loff_t* off_in, int out_fd, loff_t* off_out, size_t len, unsigned int flags);
//使用splice实现零拷贝反射服务器
int pipefd[2];
int ret = pipe(pipefd);
ret = splice(connectfd, NULL, pipefd[1], NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
ret = splice(pipefd[0], NULL, connectfd, NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
close(connectfd);
- 针对**非阻塞IO**执行的系统调用总是立即返回,而不管事情是否已经发生。如果事情没有立即发生,这些系统调用就立即返回-1,就和出错返回一样,此时我们必须根据返回时设置的errno来判断具体发生了什么情况。对**Accept,Send,Recv**而言,事件发生未发生时**errno**通常被设置为**EAGAIN**(意味着“再来一次”)或者**EWOULDBLOCK**(意味着“期望阻塞”);对于**Connect**而言,**errno**则被设置为**EINPEOCESS**(意味着“在处理中”)。
- IO复用函数本身是阻塞的,他们可以提高程序效率的原因在于他们具有同事监听多个IO事件的能力。
- 服务器程序通常需要处理三类事件:**IO事件**,**定时器事件**,**信号事件**。
- 两种事件处理模式:**Reactor**和**Proator**
- Reactor
- 主线程往Epoll内核事件表中注册**可读事件**
- 主线程等待**可读事件**发生
- 可读事件发生时,主线程将**可读事件**放入请求队列
- 主线程将睡眠在请求队列中的工作线程唤醒,处理**可读事件**。处理完毕之后,向Epoll注册**可写事件**
- 主线程等待**可写事件**发生
- **可写事件**发生时,主线程将**可写事件**放入请求队列
- 如此往复循环
- Proator
- 主线程向内核注册读完成事件,完成时,通过信号通知应用程序
- 主线程继续处理其他逻辑
- 主线程收到读完成事件后,将读到的数据封装成一个事件对象送入工作线程。工作线程处理完毕之后,向内核注册读完成事件,并告诉内核用户缓冲区的位置
- 主线程继续处理其他逻辑
- 主线程收到读完成事件,做善后处理
- 如此往复循环
- 三个IO复用机制
- **select**:每次事件发生之后,之前注册事件都会被修改,需要用FD_ISSET进行判断,然后重新注册。
- **poll**:同select,使用轮询机制
- **epoll**:**epoll**对文件描述符的操作有两种模式:**LT**(电平触发)和**ET**(边沿触发)。**LT模式**是默认的工作模式。当设置**EPOLLET**时,epoll会以**ET模式**来操作文件描述符,**ET**是高效工作模式。
- ET:当epoll_wait检测到其上有事件发生并将此次的事件通知给应用程序后,应用程序必须立刻处理该事件,因为后续的epoll_wait不会再次向应用程序通知这一事件。降低了epoll事件被触发的次数,因此效率高于LT。
- LT:epoll_wait检测到有事件发生并将此次的事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait的时候,epoll_wait还会再次向应用通知该事件,直到该事件被处理。
- **EPOLLONESHOT**:对于注册EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个事件(可读,可写或者异常),并且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
FD_ZERO(fd_set* fdset);
FD_SET(int fd, fd_set* fdset);
FD_CLR(int fd,fd_set* fdset);
int FD_ISSET(int fd,fd_set* fdset);
struct timeval
{
long tv_sec; //秒数
long tv_usec; //微秒数
}
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd
{
int fd;
short event; //注册的事件
short revent; //实际发生的事件,内核填充,记得每次判断后归零
}
POLLIN:数据可读
POLLOUT:数据可写
POLLREHUP:TCP连接被对方关闭,或者对方关闭了写操作
POLLERR:错误
POLLHUP:挂起
POLLNVAL:文件描述符没有打开
int epoll_create(int size);
int epoll_ctl(int epollfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
op参数的类型:
EPOLL_CTL_ADD:向事件注册表中注册fd事件
EPOLL_CTL_MOD:修改fd上注册的事件
EPOLL_CTL_DEL:删除fd上注册的事件
struct epoll_event
{
_uint32_t events;
epoll_data_t data;
};
union epoll_data
{
void* ptr;
int fd; //指定事件所从属的描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
- 服务器端处理信号的方式:
void sig_handler(int sig)
{
int save_errno = errno;
int msg = sig;
send(pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}
void addsig(int sig)
{
struct sigaction sa;
memset(&sa, ‘\0‘, sizeof(sa));
sa.sa_handleer = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
assrt(sigaction(sig, &sa, NULL) != -1);
}
```
原文:https://www.cnblogs.com/ukernel/p/9191185.html