linux 系统服务器编程学习总结
前言
学习linux系统服务器编程学习总结,把要点写成记录,包括读书后感和自我实践的记录提示:以下是本篇文章正文内容
第九章 io复用技术?
select , poll , epoll 技术
一、select 系统调用
1.select
函数select()的原型为:
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
各参数含义为:
nfds:整型变量,它比所有文件描述符集合中的最大值大1。使用select的时候必须计算最大值的文件描述符的值,将值通过nfds传入。
readfds:这个文件描述符集合监视文件集合中的任何文件是否有数据可读,当select()函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符,即可以被recv()函数、read()函数等进行读数据的操作。
writefds:这个文件描述符集合监视文件集合中的任何文件是否有数据可写,当select()函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符,即可以被send()函数、write()函数等进行写数据的操作。
exceptfds:这个文件描述符集合监视文件集合中的任何文件是否发生错误,其实它可以用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select()函数返回的时候,readfds将清除其中的其他文件描述符,只留下可读OOB数据。
timeout:用来描述等待描述符就绪需要的事件。设置在select()函数所坚实的文件集合中的事件没有发生时,最长的等待时间,当超过此时间时,函数会返回。当超时时间为NULL时,表示阻塞操作,会一直等待,直到某个监视的文件集合中的某个文件描述符符合返回条件。当timeout的值为0时,select()会立即返回。timeout告知系统内核等待指定描述符中的任何一个就绪可花费多少时间。其timeval结构体用于指定这段时间的秒数和微秒数。
FD_ZERO(fd_set* fdset):将指定的文件描述符集合清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于系统在分配内存空间后通常不作清空处理,所以结果是不可知的。
FD_SET(fd_set* fdset):用于在文件描述符集合中增加一个新的文件描述符
FD_CLR(fd_set* fdset):用于在文件描述符集合中删除一个文件描述符
FD_ISSET(int fd,fd_set* fdset):用于检测指定的文件描述符是否在该文件描述符集合中
有个缺点,select 需要每次遍历所有的客户端文件标识符,无法精准知道谁有数据
static void recv_client_msg(fd_set *readfds)
{
int i = 0, n = 0;
int clifd;
char buf[MAXLINE] = {0};
for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {
clifd = s_srv_ctx->clifds[i];
if (clifd < 0) {
continue;
}
/*判断客户端套接字是否有数据*/
if (FD_ISSET(clifd, readfds)) {
//接收客户端发送的信息
n = read(clifd, buf, MAXLINE);
if (n <= 0) {
/*n==0表示读取完成,客户都关闭套接字*/
FD_CLR(clifd, &s_srv_ctx->allfds);
close(clifd);
s_srv_ctx->clifds[i] = -1;
continue;
}
handle_client_msg(clifd, buf);
}
}
}
2.poll
代码如下(示例):
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
poll没有最大文件描述符数量的限制
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
3.epoll
epoll 把关心的文件描述符放在一个事件表里面
epoll 需要创建一个单独的文件描述符来管理唯一标识事件表
int epoll_create(int size); //创建描述符号
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //注册关注描述符
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); //等待需要处理的事件
-
int epoll_create(int size);
创建返回一个epoll的句柄,
size用来告诉内核这个监听的数目一共有多大。 (目前本质不起作用) -
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:- EPOLL_CTL_ADD:注册新的fd到epfd中;
- EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是关注的文件描述符号
第四个参数市event结构体
epoll_event oEvent;
oEvent.events = EPOLLIN | EPOLLET;
oEvent.data.fd = iSockFd;
if (epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iSockFd, &oEvent) < 0)
{
cerr << "fail to add listen fd to epoll, err: " << strerror(errno) << endl;
return -1;
}
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 阻塞等待事件的产生,参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个
maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。 - 该函数返回需要处理的事件数目,如返回0表示已超时。
while (true)
{
int iFdCnt = epoll_wait(iEpollFd, aoEvents, 1024, -1);
if (iFdCnt < 0)
{
cerr << "epoll wait error, err: " << strerror(errno) << endl;
return -1;
}
for (int i = 0; i < iFdCnt; i++)
{
if (aoEvents[i].data.fd == iSockFd)
{
sockaddr_in oClientAddr;
socklen_t iAddrLen = sizeof(oClientAddr);
int iAcceptFd = accept(iSockFd, (sockaddr *)&oClientAddr, &iAddrLen);
if (iAcceptFd < 0)
{
cerr << "fail to accpet, err: " << strerror(errno) << endl;
continue;
}
cout << "recv connection from " << inet_ntoa(oClientAddr.sin_addr) << ":" << ntohs(oClientAddr.sin_port) << endl;
oEvent.events = EPOLLIN | EPOLLET;
oEvent.data.fd = iAcceptFd;
if (epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iAcceptFd, &oEvent) < 0)
{
close(iAcceptFd);
cerr << "fail to add fd to epoll, err: " << strerror(errno) << endl;
continue;
}
}
else
{
int iCurFd = aoEvents[i].data.fd;
ssize_t iRecvLen = recv(iCurFd, acRecvBuf, sizeof(acRecvBuf), 0);
if (iRecvLen < 0)
{
cerr << "fail to recv, close connection, err: " << strerror(errno) << endl;
if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
{
cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
}
close(iCurFd);
continue;
}
if (iRecvLen == 0)
{
cout << "connection closed by client" << endl;
if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
{
cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
}
close(iCurFd);
continue;
}
cout << "recv data len: " << iRecvLen << endl;
ssize_t iSendLen = send(iCurFd, acRecvBuf, iRecvLen, 0);
if (iSendLen < 0)
{
cerr << "fail to send, err: " << strerror(errno) << endl;
if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
{
cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
}
close(iCurFd);
break;
}
cout << "echo to client, len: " << iSendLen << endl;
}
}
LT 和 ET 模式
LT (水平模式)
- 默认
- 通知应用程序,可用不立即处理
- 下次调用epoll_wait 还会通知
ET 边缘模式
- 通知必须马上处理,不会再通知
- 降低出发次数
- 高效率
- 必须非阻塞
- 接受需要做while循环
第十一章 定时器
链表定时器参靠这个文章
https://my.oschina.net/helpylee/blog/357485
总结
提示:这里对文章进行总结:
后面会逐渐更新我的文章