为什么需要io多路复用
io可以分为阻塞和非阻塞io
io多路复用的前提是使用非阻塞io
通过fcnt函数控制,如下设置成非阻塞;
int fd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(fd, F_SETFL, O_NONBLOCK);
阻塞和非阻塞的区别在于无数据的时候是否返回;
在以前的网络使用阻塞io,服务器处理的时候,是采用一问一答的方式,这种方式虽然可行但是由于阻塞,效率低下,而且想要同时处理多个链接的时候就需要多线程或多进程实现(一个链接一个线程或进程),这样的代价太大,性能低,但是逻辑简单。io多路复用能解决这些问题。
io多路复用的优点
仅用一个线程就能处理多个链接,成本小。
事件和处理分开,可以从面向fd的方式转变为面向事件的模式,更加的灵活。
对于事件的处理可拓展性更强,如果要求变动不需要改动结构。
io多路复用的原理
我认为是一个线程管理多个io、有事件就通知、非阻塞io以及循环机制
io多路复用的实现
实现主流为select、poll、epoll。
首先介绍select,其使用包括(fd_set、select、FD_ZERO、FD_SET、FD_ISSET)。其中fd_set可以理解是一个的int数组,大小(与内核有关)足够存32个int型数据,换算成bit为1024个bit。由于select底层是用bit位来表示事件是否存在,所以select只能表示1024个文件描述符的状态。select底层是用循环判断的事件是否触发,也就意味着select判断是否有事件,就要从最小的文件描述符到已有的最大的文件描述符遍历一次,在这个过程中还要将内核中的fd_set拷贝出来。
这里poll不过多的赘述,底层原理使用的select,优化了接口,文件描述符的数量也得到了提升。
最后是epoll,epoll有3个接口和两个结构(epoll_create、epoll_ctl、epoll_wait、struct epoll_event ev,events[EPOLLSUB])管理epoll。epoll底层维护了一个红黑树一个就绪队列,用来存储事件和就绪队列。当事件到来时,将红黑树中的事件放到就绪队列当需要处理的时候直接从就绪队列中取出。epoll能管理的文件描述符为65535个。
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/fcntl.h>
#include<sys/select.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAXLNE 4096
#define POLL_SIZE 1024
//8m * 4G = 128 , 512
//C10k
void *client_routine(void *arg) { //
int connfd = *(int *)arg;
char buff[MAXLNE];
while (1) {
int n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
break;
}
}
return NULL;
}
int main(int argc, char **argv)
{
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
#if 0
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("========waiting for client's request========\n");
while (1) {
n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
}
//close(connfd);
}
#elif 0
printf("========waiting for client's request========\n");
while (1) {
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;
}
n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
}
//close(connfd);
}
#elif 0
while (1) {
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;
}
pthread_t threadid;
pthread_create(&threadid, NULL, client_routine, (void*)&connfd);
}
#elif 0
//
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);
FD_CLR(i, &rfds);
//reactor
//send(i, buff, n, 0);
} else if (n == 0) { //
FD_CLR(i, &rfds);
//printf("disconnect\n");
close(i);
}
if (--nready == 0) break;
} else if (FD_ISSET(i, &wset)) {
send(i, buff, n, 0);
FD_SET(i, &rfds);
FD_CLR(i, &wfds);
}
}
}
#elif 0
struct pollfd fds[POLL_SIZE] = {0};
fds[0].fd = listenfd;
fds[0].events = POLLIN;
int max_fd = listenfd;
int i = 0;
for (i = 1;i < POLL_SIZE;i ++) {
fds[i].fd = -1;
}
while (1) {
int nready = poll(fds, max_fd+1, -1);
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;
fds[connfd].events = POLLIN;
if (connfd > max_fd) max_fd = connfd;
if (--nready == 0) continue;
}
//int i = 0;
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) { //
fds[i].fd = -1;
close(i);
}
if (--nready == 0) break;
}
}
}
#else
//poll/select -->
// epoll_create
// epoll_ctl(ADD, DEL, MOD)
// epoll_wait
int epfd = epoll_create(1); //int size
struct epoll_event events[POLL_SIZE] = {0};
struct epoll_event ev;
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);
if (nready == -1) {
continue;
}
int i = 0;
for (i = 0;i < nready;i ++) {
int clientfd = events[i].data.fd;
if (clientfd == listenfd) {
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");
ev.events = EPOLLIN;
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
} 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) { //
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
close(clientfd);
}
}
}
}
#endif
close(listenfd);
return 0;
}