select poll epoll

多路IO转接

可以通过多路IO转接的方式实现并发。其实是通过给每个客户端分配短暂的时间,并不停的进行时间片的切换,以达到并发处理多个客户端的目的。

多路IO模型的核心是监听IO事件,从而实现并发。

多路IO模型只适合完成高并发低活跃的任务。

tcpclient.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>

#define MAXLINE 80
#define PORT 8000

int main()
{
    struct sockaddr_in serveraddr;
    int sockfd;
    int n;
    char buf[MAXLINE];
    bzero(buf,MAXLINE);
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"127.0.0.1",&severaddr.sin_addr);
    serveraddr.sin_port = htobs(PORT);
    connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
    while(fgets(buf,MAXLINE,stdin) != NULL)
    {
        write(sockfd,buf,MAXLINE);
        n = read(sockfd,buf,MAXLINE);
        write(STDOUT_FILENO,buf,n);
        bzero(buf,MAXLINE);
    }
    close(sockfd);

    return 0;
}

select

select通过将socket挂载到IO设备等待队列,然后监听此socket上的事件:读事件/写事件。

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds
: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout:定时阻塞监控时间,3种情况:
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询.
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); 把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); 测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); 把文件描述符集合里所有位清0

selectserver.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>

#define MAXLINE 80
#define PORT 8000

int main()
{
    fd_set set oset,set;
    int clientarr[1023];  //最多能监听1024个套接字
    int serverfd,clientfd;
    int ready;
    int maxfd;
    struct sockaddr_in serveraddr,clientaddr;
    int i,j,len;
    socklen_t client_len;
    for(i=0;i<1023;i++)
    {
        clientarr[i] = -1;
    }
    serverfd = socket(AF_INET,SOCK_STREAM,0);
    maxfd = serverfd;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(PORT);
    bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
    listen(serverfd,128);
    //初始化监听集合
	/*
	   FD_ZERO()//将监听集合初始化为0
	   FD_SET()//将某一个socket在监听集合中置1
	   FD_CLR()//将某一个socket在监听集合中置0
	   FD_ISSET()//判断某一个socket在指定集合中是0还是1
	 */
    FD_ZERO(&oset);
    FD_SET(serverfd,&oset);
    while(1)
    {
        set = oset;
        ready = select(maxfd+1,&set,NULL,NULL,NULL);
        while(ready--)
        {
            if(FD_ISSET(serverfd,&set))
            {
                client_len = sizeof(clientaddr);
                clientfd=accept(serverfd,(struct sockaddr*)&clientaddr,&client_len);
                if(clientfd>0)
                {
                    if(maxfd<clientfd)maxfd=clientfd;
                    FD_SET(clientfd,&oset);
                    for(i=0;i<1023;i++)
                    {
                        if(clientarr[i] == -1)
                        {
                            clientarr[i] = clientfd;
                            break;
                        }
                    }
                }
                
            }
            else
            {
                for(i=0;i<1023;i++)
                {
                    if(clientarr[i] != -1)
                    {
                        if(FD_SET(clientarr[i],&set))
                        {
                            if((len = read(clientarr[i],buf,MAXLINE))==0)
                            {
                                FD_CLR(clientarr[i],&oset);
                                close(clientarr[i]);
                                clientarr[i] = -1;
                                break;
                            }
                            for(j=0;j<len;j++)buf[j]=toupper[buf[j]];
                            write(clientarr[i],buf,len);
                            bzero(buf,sizeof(buf));
                            break;
                        }
                    }
                }
            }
        }
    }

    return 0;
}

poll

poll与select原理相似,只是使用的监听集合不同。

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
int fd; /* 文件描述符*/
short events; /* 监控的事件*/
short revents; /* 监控事件中满足条件返回的事件*/
};
POLLIN普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
POLLRDNORM-数据可读
POLLRDBAND-优先级带数据可读
POLLPRI 高优先级可读数据
POLLOUT普通或带外数据可写
POLLWRNORM-数据可写
POLLWRBAND-优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
nfds 监控数组中有多少文件描述符需要被监控
timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此
pollfd,下次返回时,把revents设置为0。
ppoll GNU定义了ppoll(非POSIX标准),可以支持设置信号屏蔽字,大家可参考poll模型自行实现C/S。

pollserver.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
#include<ctype.h>
#include<sys/poll.h>

#define MAXLINE 80
#define PORT 8000
#define LISTEN 128

int main()
{
	struct sockaddr_in serveraddr,clientaddr;
	struct pollfd pd[1023];//poll可以监听更多socket
	int serverfd,clientfd;
	int i,j,len;
	socklen_t client_len;
	int ready;//监听的个数
	char buf[MAXLINE];
	for(i=0;i<1023;i++)
	{
		pd[i].fd=-1;
	}
	bzero(&serveraddr,sizeof(serveraddr));
	bzero(buf,MAXLINE);
	serverfd = socket(AF_INET,SOCK_STREAM,0);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(PORT);
	bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
	listen(serverfd,LISTEN);//最多128
	pd[0].fd = serverfd;
	pd[0].events = POLLIN;
	while(1)
	{
		ready = poll(pd,1023,-1);
		while(ready--)
		{
			if(pd[0].revents &POLLIN)
			{
				client_len = sizeof(clientaddr);
				clientfd = accept(serverfd,(struct sockaddr*)&clientaddr,&client_len);
				for(i=1;i<1023;i++)
				{
					if(pd[i].fd == -1)
					{
						pd[i].fd = clientfd;
						pd[i].events=POLLIN;
						break;
					}
				}
			}
			else
			{
				for(i=1;i<1023;i++)
				{
					if(pd[i].fd != -1)
					{
						if(pd[i].revents & POLLIN)
						{
							if((len=read(pd[i].fd,buf,MAXLINE)) ==0)
							{
								close(pd[i].fd);
								pd[i].fd = -1;
								break;
							}
							for(j=0;j<len;j++)
								buf[j] = toupper(buf[j]);
							write(pd[i].fd,buf,len);
							bzero(buf,sizeof(buf));
							break;
						}
					}
				}
			}
		}
	}



	return 0;
}

epoll

epoll是高并发低活跃服务器的首选 模型。

监听集合是红黑树,IO设备等待队列通过mmap传出,并且使用的是自己实现的消息等待队列,提高了效率。

epoll除了提供select/ poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

1.创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关
int epoll_create(int size)
size:告诉内核监听的数目
2.控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd:为epoll_creat的句柄
op:表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fd到epfd),
EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
EPOLL_CTL_DEL(从epfd删除一个fd);
event:告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3.等待所监控文件描述符上有事件的产生,类似于select()调用。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

events:用来从内核得到事件的集合,
maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout:是超时时间
-1:阻塞
0:立即返回,非阻塞
>0:指定微秒
返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

epollserver.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
#include<ctype.h>
#include<sys/epoll.h>

#define MAXLINE 80
#define PORT 8000
#define LISTEN 128
#define EPOLLSIZE 60000

int main()
{
	struct sockaddr_in serveraddr,clientaddr;
	struct epoll_event eparr[EPOLLSIZE]; //传出队列
	struct epoll_event eptemp;//临时节点
	int serverfd,clientfd;
	int i,j,len;
	int ready;
	socklen_t client_len;
	char buf[MAXLINE];
	int epfd = epoll_create(EPOLLSIZE);
	bzero(&serveraddr,sizeof(serveraddr));
	bzero(buf,MAXLINE);
	serverfd = socket(AF_INET,SOCK_STREAM,0);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(PORT);
	bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
	listen(serverfd,LISTEN);//最多128
	eptemp.events=EPOLLIN;
	eptemp.data.fd = serverfd;
	epoll_ctl(epfd,EPOLL_CTL_ADD,serverfd,&eptemp); //添加结点
	while(1)
	{
		ready = epoll_wait(epfd,eparr,EPOLLSIZE,-1);
		while(ready)
		{
			ready--;
			if(eparr[ready].data.fd == serverfd)
			{
				client_len = sizeof(clientaddr);
				clientfd = accept(serverfd,(struct sockaddr*)&clientaddr,&client_len);
				eptemp.events = EPOLLIN;
				eptemp.data.fd = clientfd;
				epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&eptemp);
			}
			else
			{
				if((len=read(eparr[ready].data.fd,buf,MAXLINE)) ==0)
				{
					epoll_ctl(epfd,EPOLL_CTL_DEL,eparr[ready].data.fd,NULL);
					close(eparr[ready].data.fd);
					break;
				}
				for(j=0;j<len;j++)
					buf[j] = toupper(buf[j]);
				write(eparr[ready].data.fd,buf,len);
				bzero(buf,sizeof(buf));
				break;
			}
		}
	}



	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值