select,epoll 多路IO复用
select和epoll是最常用的两种多路IO复用技术,select是跨平台的函数,而epoll是linux独有的。
select特点:
1.监听的文件描述符有限制,最多只能监听1024个描述符.
2.在监听到客户端事件发生时,处理每个事件,都需要循环遍历,才能确认是哪个客户端产生读事件。效率比较低。
3.适合并发连接数较少的服务器。
相关函数:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
epoll特点:
1.只要内存足够,能监听足够多的文件描述符,由自己设定
2.当监听到客户端事件发生时,会将监听到的时间存在数组中,并且该数组存有客户端fd的信息,无需循环遍历确认。
3.适合高并发的服务器
相关函数:
int epoll_create(int size);
size:只要大于0即可
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op:
EPOLL_CTL_ADD 添加监听描述符
EPOLL_CTL_MOD 修改监听描述符
EPOLL_CTL_DEL 删除监听描述符
typedef union epoll_data {
void *ptr;
int fd;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:
EPOLLIN 读事件
EPOLLOUT 写事件
EPOLLERR 错误事件
EPOLLET 边沿触发
EPOLLONESHOT 一次触发(注意:触发完如果还想被监听到,需要重新mod一下监听描述符)
示例代码:
先贴上自定义分装的sock.h 函数
select服务器模型:
#include<arpa/inet.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
int create_server(unsigned short port, const char* ip, int backlog)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return fd;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip); // 监听的ip地址
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -2;
}
listen(fd, backlog);
return fd;
}
int doAccept(int fd, struct sockaddr* addr, socklen_t* addrlen){
while(1)
{
int newfd = accept(fd, addr, addrlen);
if(newfd < 0 && errno == EINTR)
{
continue;
}
return newfd;
}
return -1;
}
int main()
{
int server = create_server(9988, "0.0.0.0", 250);
int maxfd = server;
list<int> clients; // 保存所有的客户连接socket
while(1)
{
fd_set set;
struct timeval tv;
maxfd = server;
FD_ZERO(&set);
FD_SET(server, &set);
for(list<int>::iterator it = clients.begin(); it!=clients.end(); ++it)
{
FD_SET(*it, &set);
if(*it > maxfd) maxfd = *it; // 寻找最大的文件描述符
}
tv.tv_sec = 2;
tv.tv_usec = 0;
int ret = select(maxfd+1, &set, NULL, NULL, &tv);
if(ret > 0)
{
if(FD_ISSET(server, &set))
{
// 下一次监听时,需要将它放入集合
int newfd = doAccept(server, NULL, NULL);
clients.push_back(newfd);
}
for(list<int>::iterator it = clients.begin(); it!=clients.end(); )
{
int newfd = *it;
if(FD_ISSET(newfd, &set))
{
char buf[1024];
ret = read(newfd, buf, sizeof(buf));
if(ret > 0)
{
printf("%s, fd=%d\n", buf, newfd);
}
else if(ret < 0 && errno == EINTR)
{
//被打断暂时不处理
}
else // ==0 或者 <0并且错误吗不是EINTR
{
// 当对方关闭socket,或者socket有问题时,应该关闭对应的socket
close(newfd);
it = clients.erase(it);
continue;
}
}
it++;
}
}
}
}
epoll服务器模型
#include<arpa/inet.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
//封装epoll_ctl函数
void epoll_add(int epollfd, int fd, int events)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = events;
epoll_ctl(epollfd, EPOLL_CTL_ADD, server, &ev);
}
int create_server(unsigned short port, const char* ip, int backlog)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return fd;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip); // 监听的ip地址
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -2;
}
listen(fd, backlog);
return fd;
}
int main()
{
int server = create_server(9988, "0.0.0.0", 250);
// epollfd也是一个fd,那能不能把epollfd放到另外一个epollfd中去
int epollfd = epoll_create(512);
epoll_add(epollfd, server, EPOLLIN);
struct epoll_event ev_out[8];
while(1)
{
int ret = epoll_wait(epollfd, ev_out, 8, 2000);
if(ret > 0)//监听事件发生
{
int i;
for(i=0; i<ret; ++i)
{
int fd = ev_out[i].data.fd;
if(fd == server)
{
int newfd = accept(fd, NULL, NULL);
epoll_add(epollfd, newfd, EPOLLIN);
}
else //
{
char buf[1024];
int r = read(fd, buf, sizeof(buf));
if(r > 0)
{
printf("%s\n", buf);
}
else if(r < 0 && errno == EINTR)
{
}
else // r == 0 || (r< 0 && errno != EINTR)
{
close(fd);
}
}
}
}
}
}