Linux下的网络并发处理

多线程实现并发访问

可以采用一用户一线程的方式来解决阻塞问题,针对每个用户的连接请求,主线程用于接受连接,而recv与send这种业务需求就开一个线程来解决,以下代码实现了为每一个访问的用户分配一个线程长期为他服务:

void proccess_client(void* arg){
	int fd = *(int*)arg
	while(1){
		char buff[1024] = {0}
		int count = recv(fd,buff,1024,0);
		send(fd,buff,count,0);
	}
}
while (1) {
	printf("accept\n");
	int clientfd = accept(sockfd, NULL,NULL);
	printf("accept finshed: %d\n", clientfd);

	pthread_t thid;
	pthread_create(&thid, NULL, proccess_client, &clientfd);

}

select轮询

可以使用select函数进行操作,使用select主要流程为,创建一个fd_set集合,然后将serverfd放进去,然后执行select函数,每次执行select会将fd集合拷贝到内核中,如果说有就绪的FD,则返回一个有多少FD就绪的值,然后,使用FD_ISSET判断目标FD是否在就绪队列里面。代码如下:

fd_set rfds, rset;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
int maxfd = sockfd;

while (1) {
	rset = rfds;
	int nready = select(maxfd+1, &rset, NULL, NULL, NULL);

	if (FD_ISSET(sockfd, &rset)) { // accept

		int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
		printf("accept finshed: %d\n", clientfd);
		FD_SET(clientfd, &rfds); // 
		if (clientfd > maxfd) maxfd = clientfd;
	}

	int i = 0;
	for (i = sockfd+1; i <= maxfd;i ++) { // i fd

		if (FD_ISSET(i, &rset)) {
			// recv
		}
	}
}

poll轮询

poll与select类似,不过其参数相比较select更少,使用起来更加方便,其原理同样是将集合中的fd通过内核调用copy_from_user拷贝到内核中,然后进行轮询查看是否有就绪的fd。代码如下:

	struct pollfd fds[1024] = {0};
	fds[serverfd].fd = serverfd;
	fds[serverfd].events = POLLIN;

	int maxfd = serverfd;
	while (1) {
	int nready = poll(fds, maxfd+1, -1);
	if (fds[serverfd].revents & POLLIN) {
		//如果说serverfd是就绪状态,可以执行accept获取连接
	}

	int i = 0;
	for (i = serverfd+1; i <= maxfd;i ++) { // i fd
		if (fds[i].revents & POLLIN) {
			//用户触发事件,在这里进行处理
		}
	}

epoll

在高并发的情况下,如果使用select或poll,那么如果有百万级连接,就需要每次将百万数量的FD资源拷贝到内核中并进行遍历,但实际上,一个网络服务中,即使有百万用户,同一时刻正在发包的用户也绝对不会有这么多,那么,轮询遍历所有监听FD的方式就造成了极大的浪费,但来不及为性能浪费而哀悼,随之而来的是epoll,与select与poll使用轮询的方式来收集就绪FD不一样,epoll采用了事件驱动的形式,这也是reactor的理念,epoll使用一个FD整集与一个FD就绪队列,其中,所有就绪的FD都会被存放进就绪队列中而不是去遍历监听队列,其中,这个FD整集使用了红黑树,因为红黑树是一种强查找的数据结构。就绪队列使用了双向链表,因为对于每一个就绪的节点,只需要执行就可以了,并不依赖于查找,epoll代码如下:

	int epfd = epoll_create(1);
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = sockfd;
	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
	while (1) {
		struct epoll_event events[1024] = {0};
		int nready = epoll_wait(epfd, events, 1024, -1);
		int i = 0;
		for (i = 0;i < nready;i ++) {
			int connfd = events[i].data.fd;
			if (connfd == sockfd) {
				int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
				ev.events = EPOLLIN;
				ev.data.fd = clientfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
			} else if (events[i].events & EPOLLIN) {
				//处理就绪可读状态的FD
			}
		}
	}

以上代码实现了一个epoll的server服务器,当有客户端进行连接时返回FD就绪数量,并且为每一个clientFD添加EPOLLIN事件,当fd可读时进入就绪队列。

epoll原理解析

首先我们需要知道,EPOLL存在两种触发机制:

EPOLLLT(水平触发):水平触发的机制为,只要数据没有处理完,就一直触发,直到处理完为止。

EPOLLET(边沿触发):边沿触发,只会在就绪时触发一次,比如说用户向服务器发送了3个包,那么此时就只会触发一次,想要读取完数据就需要使用循环。

//EPOLLLT
if (events[i].events & EPOLLIN) {
	recv(...)
}
//EPOLLET
if (events[i].events & EPOLLIN) {
	while(1){
		int count = recv(...)
		if(count == 0){
			break;
		}
	}
}

在了解了epoll触发机制之后,我们需要知道epoll的数据结构,分别是eventpoll、epitem、epoll_entry

eventpoll

eventpoll是调用epoll_create之后内核创建的epoll实例,当执行epoll_ctl时,会分配一个红黑树节点epitem,并添加等待事件到socket的等待队列,并将epitem插入到红黑树中,对象内核代码结构体如下:

struct eventpoll {
	spinlock_t lock;
	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 */
	//这个链表存放了所有就绪的FD资源
	struct list_head rdllist;
	/* RB tree root used to store monitored fd structs */
	//所有被加入进epoll监视的fd都会被存放进这个红黑树中
	struct rb_root rbr;
	struct epitem *ovflist;
	struct wakeup_source *ws;
	struct user_struct *user;
	struct file *file;
	int visited;
	struct list_head visited_list_link;
};

从代码中可知,epoll中被监视的FD存放在红黑树结构中,相比于select与poll将FD整集拷贝到内核中进行遍历,epoll则不需要遍历,而是将就绪的fd放入就绪链表中,然后对就绪链表中的资源进行操作即可。

epitem

当调用epoll_ctl添加FD时,内核会创建一个epitem,并将这个item作为红黑树中的一个节点,epitem内核源码如下:

struct epitem {
	/* RB tree node used to link this structure to the eventpoll RB tree */
	struct rb_node rbn;

	/* List header used to link this structure to the eventpoll ready list */
	struct list_head rdllink;

	/*
	 * Works together "struct eventpoll"->ovflist in keeping the
	 * single linked chain of items.
	 */
	struct epitem *next;

	/* The file descriptor information this item refers to */
	struct epoll_filefd ffd;

	/* Number of active wait queue attached to poll operations */
	int nwait;

	/* List containing poll wait queues */
	struct list_head pwqlist;

	/* The "container" of this item */
	struct eventpoll *ep;

	/* List header used to link this item to the "struct file" items list */
	struct list_head fllink;

	/* wakeup_source used when EPOLLWAKEUP is set */
	struct wakeup_source __rcu *ws;

	/* The structure that describe the interested events and the source fd */
	struct epoll_event event;
};

epoll_entry

每次当一个 fd 关联到一个 epoll 实例,就会有一个 eppoll_entry 产生。eppoll_entry 的结构如下:

/* Wait structure used by the poll hooks */
struct eppoll_entry {
	/* List header used to link this structure to the "struct epitem" */
	struct list_head llink;

	/* The "base" pointer is set to the container "struct epitem" */
	struct epitem *base;

	/*
	 * Wait queue item that will be linked to the target file wait
	 * queue head.
	 */
	wait_queue_t wait;

	/* The wait queue head that linked the "wait" wait queue item */
	wait_queue_head_t *whead;
};
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值