五种IO模型
- 阻塞IO
- 非阻塞IO
- 在系统未准备好时立即返回
- 需要轮询
- 在从内核读取数据到用户空间(或写入内核)时任然会阻塞
- fcntl(fd, F_SETFL, O_NONBLOCK)
- 多路复用IO
- select,poll,epoll
- 事件驱动
- 异步IO
- 告诉系统要IO,系统完成IO后通过信号告诉用户
- 在数据IO的期间是非阻塞的
- 信号驱动IO
- 用sigaction注册SIGIO的回调函数
- 在等待数据到达的期间是非阻塞的
- 数据就绪时,系统用SIGIO通知用户,触发回调函数
- 回调函数调用read读取(阻塞IO)
多线程模型
每个线程要8M的栈(可以调整),极限是C10K
while (1) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
connfd = Accept(listenfd, (struct sockaddr *)&client, &len)
pthread_t threadid;
pthread_create(&threadid, NULL, client_routine, (void*)&connfd);
}
select
每次要轮询每个fd看有没有事件,有什么事件(读、写、异常)
不能超过1024个fd,可以调整,但是要改源码重新编译,很麻烦
每个线程/进程一个select,可以到达C1000K(一百万)
因为select需要复制fd set,效率较慢,fd多了就太慢
fd_set rfds, rset, wfds, wset;
FD_ZERO(&rfds);
FD_SET(listenfd, &rfds);
FD_ZERO(&wfds);
int max_fd = listenfd;
while (1) {
rset = rfds;
wset = wfds;
int nready = select(max_fd+1, &rset, &wset, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) { //
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
FD_SET(connfd, &rfds);
if (connfd > max_fd) max_fd = connfd;
if (--nready == 0) continue;
}
int i = 0;
for (i = listenfd+1;i <= max_fd;i ++) {
if (FD_ISSET(i, &rset)) { //
n = recv(i, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
FD_SET(i, &wfds); //reactor的写法,更标准
//send(i, buff, n, 0); //也可以,简单点
} else if (n == 0) { //EOF
FD_CLR(i, &rfds); //一定要去除了,否则会重复触发事件
close(i);
}
if (--nready == 0) break;
} else if (FD_ISSET(i, &wset)) {
send(i, buff, n, 0);
FD_CLR(i, &wfds);
FD_SET(i, &rfds);
}
}
}
poll
不受1024fd的限制,不过原理和select一样,只是接口改变了一下
struct pollfd fds[POLL_SIZE] = {0};
fds[0].fd = listenfd;
fds[0].events = POLLIN;
int max_fd = listenfd;
int i;
for (i = 1;i < POLL_SIZE;i ++) {
fds[i].fd = -1;
}
while (1) {
int nready = poll(fds, max_fd+1, -1); //-1是timeout
if (fds[0].revents & POLLIN) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("accept \n");
fds[connfd].fd = connfd; //也不一定要在下标为connfd的地方存放
fds[connfd].events = POLLIN;
if (connfd > max_fd) max_fd = connfd;
if (--nready == 0) continue;
}
for (i = listenfd+1;i <= max_fd;i ++) {
if (fds[i].revents & POLLIN) {
n = recv(i, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(i, buff, n, 0);
} else if (n == 0) { //EOF
fds[i].fd = -1;
close(i);
}
if (--nready == 0) break;
}
}
}
epoll
把需要监听的fd存在红黑树里,方便查询
每次返回一个队列,队列里面每个fd都有IO事件,不用每次都遍历整个需要监听的fd set
// epoll_create
// epoll_ctl(ADD, DEL, MOD)
// epoll_wait
int epfd = epoll_create(1); //int size, 底层没有用到,以前是定长数组,现在是链表,为了向前兼容
struct epoll_event events[POLL_SIZE] = {0};//每次取event的大小,类似batch size
struct epoll_event ev; //epoll_ctl的输入参数
ev.events = EPOLLIN;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
while (1) {
int nready = epoll_wait(epfd, events, POLL_SIZE, 5); //最后一个参数是timeout, -1则一直等待
if (nready == -1) {
continue; //timeout
}
int i;
for (i = 0;i < nready; i++) {
int clientfd = events[i].data.fd;
if (clientfd == listenfd) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
connfd = Accept(listenfd, (struct sockaddr *)&client, &len);
ev.events = EPOLLIN;
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); //新fd添加到监听的集合中
} else if (events[i].events & EPOLLIN) {
n = recv(clientfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(clientfd, buff, n, 0);
} else if (n == 0) { //EOF
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
close(clientfd);
}
}
}
}