图像检索服务器编写问题记录——服务器端模型选择+epoll和非阻塞IO

0.服务器端模型选择

大体思路是:在主线程中使用epoll承受高并发连接,在主线程中开辟工作线程用来处理就绪的描述符。嗯,这个原来有官名,叫反应者模式。事件驱动编程。另外工作线程处理的就绪的文件描述符最好是非阻塞的。

1.阻塞与非阻塞

【概念】

阻塞与非阻塞都是对于IO而言的,即阻塞式IO和非阻塞式IO。没有什么阻塞的read和非阻塞的read。以read(fd,MAXnum)为例,read阻塞式IO(文件描述符),表示只能完全读完该文件描述符的MAXnum个字符才能从read调用中返回;而read非阻塞式IO,表示当文件描述符中没有信息时,可以立即返回。

【区别】

使用阻塞式IO的问题在于:如果缓冲区大小只有100bytes,但是用户有500bytes要发送,当需要read读这个阻塞式的文件描述符A时,先读了100bytes,发现缓冲区空了,但是剩下的400bytes还在路上,那么read阻塞IOA时,就只能等在这个调用上了,只有完完整整的将500bytes读完才能从read返回。而其中等剩余400bytes的过程中,文件描述符B中即使有数据可读,也只能傻看着,没法读!!这样等的时间就浪费掉了。这段时间大家一起等,有数据也得等。换句话说:如果read(设备A)是阻塞的,那么只要设备A没有数据到达就会一直阻塞在设备A的read调用上,即使设备B有数据到达也不能处理,使用非阻塞I/O就可以避免设备B得不到及时处理。

而使用非阻塞式IO时:文件描述符A中读完100bytes,剩下的400bytes没来,那么可以先从read A返回;这时B描述符如果缓冲区有数据,可以先去读B,这样就谁都不耽误了。

while(leftchnum>0)
{
       n=read(非阻塞描述符A);
       if(n<0&&errno==EAGAIN)
       		continue;
       else 
           leftchnum-=n;
}

2.epoll分水平触发和边缘触发两种方式。

Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!

Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!

所以最好使用边缘触发,减少不关心的就绪文件描述符。在线程中使用非阻塞描述符,不妨碍其他就绪文件描述符的操作

3.epoll的使用

【epoll的使用三步走】

1.int epoll_create(int size):生成一个epoll专用的描述符,size是epoll想要监听的最大的描述字数,这里我们设置为服务器监听的最大连接数+1

int epollfd=epoll_create(LISTENQ+1);

2.epoll_ctl(epoll的描述字,注册的事件的类型,注册的文件描述符,注册的其他信息)

struct epoll_event ev;
ev.data.fd=listenfd;
ev.events=EPOLLIN|EPOLLET;//套接字缓冲区可读触发,触发方式边缘触发
epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev);//注册事件
3.int epoll_wait(epollfd的描述字,事先申请好的存放事件的事件数组,前面那数组的大小,超时等待时间)

EPOLLMAX即事先用于申请的,用于保存从内核态拷贝的就绪事件信息的,epoll_event数组。这个大小限制了epoll一次性从内核缓冲区拷贝回的事件数。它不同于create中epoll能够监听的事件数!!!

epoll_wait(epollfd,events,EPOLLMAX,-1);

【epoll的使用实例】

大致思路是:先把监听套接字注册到epoll中,然后epoll_wait,有连接时分辨是否是listenfd,如果是,那么accept连接,并将连接注册到epoll中。该连接的套接字设置成非阻塞。

epoll会将事件按注册时的事件类型分门别类的返回给我们,让我们的处理更加有目的性

int main(int argc, char **argv)
{
	int listenfd;
	struct sockaddr_in servaddr;	
	listenfd=socket(AF_INET,SOCK_STREAM,0);
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family=AF_INET;
	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
	servaddr.sin_port=htons(SERV_PORT);
	bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	listen(listenfd,LISTENQ);//LISTENQ:服务器同时可接受的TCP连接数

	int epollfd=epoll_create(LISTENQ+1);
	struct epoll_event ev;
	ev.data.fd=listenfd;
	ev.events=EPOLLIN|EPOLLET;//套接子缓冲区有内容时触发,触发方式边缘触发
	epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev);	
	
	struct epoll_event events[EPOLLMAX];
	while(1)
	{
		int fdnum=epoll_wait(epollfd,events,EPOLLMAX,-1);
		for(int i=0;i0)
				{
					unpluo_setnoblocking(connfd);
					ev.data.fd=connfd;
					ev.events=EPOLLIN|EPOLLET;
					epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&ev);
				}
			}
			else if(events[i].events&EPOLLIN)
			{
				int sockfd=events[i].data.fd;
				if(sockfd>0)
				{
				//	servluo_match(sockfd);
				}
			}
		}
	}
}




你可能感兴趣的:(图像检索服务器编写记录)