JDK源码-IO系列:linux IO模型以及多路复用epoll

2 篇文章 0 订阅

Linux Epoll模型

epoll前世今生

linux系统产生了下面五种网络模式的方案:
最常见的是前三种。

1、阻塞IO(blocking IO):
对用户态应用程序来说,当调用read()系统调用的时候。如果待读取的fd没有数据的时候,就回阻塞,直到有数据回返回。

2、非阻塞IO(nonblocking IO)
对用户态程序来说,当调用read()系统调用的时候,如果待读取的fd没有数据的时候,就返回error,用户态程序可以根据错误码判断,根据程序设计选择继续循环调用,来获取数据。

3、IO多路复用(IO multiplexing)
epoll模型就是IO多路复用的一种,另外两个是select,poll。IO多路复用,多个网络连接socket复用一个processor。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

如果处理的连接数不是很高的话,使用select/epoll的服务器不一定比使用“多线程” + “阻塞IO”的服务器性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更好,而是在于性能更多的连接。

4、信号驱动IO(signal driven IO)不常用
5、异步IO (asynchronous IO)

select,poll,epoll

IO多路复用,就是一个processor,处理多个IO连接。

select:

调用select后,阻塞。直到有socket有数据的时候,返回。这个时候需要遍历所有管理的fd。通过查看是否置位的方式,查看是否有数据,如果有数据,则读取数据处理。

时间复杂度:O(n)

实现细节:
通过维护fd_set来管理待处理的fd的状态。
fd_set简单地理解为一个长度是1024的比特位,每个比特位表示一个需要处理的FD,如果是1,那么表示这个FD有需要处理的I/O事件,否则没有

缺点:
1、管理的fd有上限
2、需要便利所有fd,通过置位判断才能判断出来到底是那个fd有数据
3、用户空间和内核空间的复制非常消耗资源

poll:

和select类似,只是用于管理待处理的fd的状态,从fd_set,调整成一个链表。见实现细节。

时间复杂度:O(n)
实现细节:
其和select不同的地方:采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制。

系统调用如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds是一个pollfd数据结构的数组,pollfd数据结构如下。nfds代表fds的长度。

struct pollfd {
	int fd;          // 需要监视的文件描述符
	short events;    // 需要内核监视的事件
	short revents;   // 实际发生的事件
};

缺点;
1、需要便利所有fd,判断到底那个fd有数据,之后进行数据处理

epoll实现原理

epollevent对象是epoll管理多个网络连接的数据结构。两个重要的属性如下:

  • struct list_head rdllist; 双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件。 也就是待处理事件。
  • struct rb_root_cached rbr; 红黑树,存储通过epoll_ctl增删改的fd树

也就是当有fd有数据的时候,rdllist就回存储待处理的fd列表。那对于应用程序来说,就有了不需要遍历所有管理的fd来判断那些fd有数据了。解决了select/poll需要遍历fds来判断那些fd就绪了。

内核源码如下:

/*
 * This structure is stored inside the "private_data" member of the file
 * structure and represents the main data structure for the eventpoll
 * interface.
 */
struct eventpoll {
	/*
	 * This mutex is used to ensure that files are not removed
	 * while epoll is using them. This is held during the event
	 * collection loop, the file cleanup path, the epoll file exit
	 * code and the ctl operations.
	 */
	struct mutex mtx;

	/* Wait queue used by sys_epoll_wait() */
	wait_queue_head_t wq;

	/* Wait queue used by file->poll() */
	wait_queue_head_t poll_wait;

	/* List of ready file descriptors */
	struct list_head rdllist;

	/* Lock which protects rdllist and ovflist */
	rwlock_t lock;

	/* RB tree root used to store monitored fd structs */
	struct rb_root_cached rbr;

	/*
	 * This is a single linked list that chains all the "struct epitem" that
	 * happened while transferring ready events to userspace w/out
	 * holding ->lock.
	 */
	struct epitem *ovflist;

	/* wakeup_source used when ep_scan_ready_list is running */
	struct wakeup_source *ws;

	/* The user that created the eventpoll descriptor */
	struct user_struct *user;

	struct file *file;

	/* used to optimize loop detection check */
	struct list_head visited_list_link;
	int visited;

#ifdef CONFIG_NET_RX_BUSY_POLL
	/* used to track busy poll napi_id */
	unsigned int napi_id;
#endif

#ifdef CONFIG_DEBUG_LOCK_ALLOC
	/* tracks wakeup nests for lockdep validation */
	u8 nests;
#endif
};

epoll流程

服务端:
epoll_create:创建一个epollfd
epoll_ctl:添加一个fd到epollfd中,被epollfd所管理,再细化一下,会将添加的fd添加到红黑树中。
epoll_wati:阻塞等待,当待处理事件列表中存在事件的时候,返回待处理事件的个数。
进而可以对有IO的事件进行处理。

代码示例

服务端代码

#include <iostream>
#include <strings.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts = fcntl(sock, F_GETFL);
    if (opts < 0)
    {
        perror("fcntl(sock, GETFL)");
        exit(1);
    }
    opts = opts | O_NONBLOCK;
    if (fcntl(sock, F_SETFL, opts) < 0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main()
{
    int i, maxi, listenfd, connfd, sockfd, epfd, nfds;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen = sizeof(sockaddr_in);;
    struct epoll_event ev, events[20];
    epfd = epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    setnonblocking(listenfd);
    ev.data.fd = listenfd; 
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); 
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr = "127.0.0.1";
    inet_aton(local_addr, &(serveraddr.sin_addr));
    serveraddr.sin_port = htons(SERV_PORT); 
    bind(listenfd, (sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for (;;)
    {
        nfds = epoll_wait(epfd, events, 20, 1000); 
        for (i = 0; i < nfds; ++i) 
        {
            if (events[i].data.fd == listenfd)
            {
                connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen);
                if (connfd < 0)
                {
                    perror("connfd<0");
                    exit(1);
                }
                setnonblocking(connfd); 
                char *str = inet_ntoa(clientaddr.sin_addr);
                std::cout << "connect from " << str << std::endl;
                ev.data.fd = connfd; 
                ev.events = EPOLLIN | EPOLLET; 
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }
            else if (events[i].events & EPOLLIN) 
            {
                if ((sockfd = events[i].data.fd) < 0)
                {
                    continue;
                }
                if ((n = read(sockfd, line, MAXLINE)) < 0) 
                {
                    if (errno == ECONNRESET)
                    {
                        close(sockfd);
                        events[i].data.fd = -1;
                    }
                    else
                    {
                        std::cout << "readline error" << std::endl;
                    }
                }
                else if (n == 0)
                {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                ev.data.fd = sockfd; 
                ev.events = EPOLLOUT | EPOLLET; 
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
            }
            else if (events[i].events & EPOLLOUT) 
            {
                sockfd = events[i].data.fd;
                write(sockfd, line, n);
                ev.data.fd = sockfd; 
                ev.events = EPOLLIN | EPOLLET; 
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
            }
        }
    }
}

客户端代码:

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 5555
#define BUFSIZE 1024

int getSocket(){
    int fd =socket( AF_INET, SOCK_STREAM, 0 );
    if(-1 == fd){
    }
    return fd;
}

int main()
{
	int fd = getSocket();
	if (-1 == fd)
	{
		return -1;
	}
	struct sockaddr_in remote_addr;						//服务器端网络地址结构体
	memset(&remote_addr, 0, sizeof(remote_addr));		//数据初始化--清零
	remote_addr.sin_family = AF_INET;					//设置为IP通信
	remote_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //服务器IP地址
	remote_addr.sin_port = htons(SERVER_PORT);			//服务器端口号
	int con_result = connect(fd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr));
	if (con_result < 0)
	{
		return -1;
	}
	char buf[32] = {0};
	while (1)
	{
		scanf("%s", buf);
		int ret = send(fd, buf, strlen(buf), 0);
		if (-1 == ret)
		{
			perror("send");
			exit(1);
		}
		if (!strcmp(buf, "bye"))
		{
			break;
		}

		memset(buf, 0, sizeof(buf));
	}

	close(fd);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值