select和epoll

来源:互联网 发布:中文域名在线转码 编辑:程序博客网 时间:2024/05/05 14:21

如何解决服务器多并发问题?

1、每个进程处理一个客户端

2、每个线程处理一个客户端

3、io复用模型

首先我们想要了解select和epoll的区别,首先应该了解下什么是io复用模型概念。

io复用模型概念?

只需要一个进程就够了。之所以能够同时处理多个客户端的请求,原因是可以查询哪个客户端准备好了,对于准备好的客户端(例如客户端已经发了信息过来,本服务器用read读取数据的时候不会阻塞;另外,客户端已经关闭了连接,那么本服务read的时候,返回0,也不会阻塞),则和它进行通信,而未准备好的,就暂时先不理会。



select函数:

select可以实现这个轮询功能。

select函数原型:

int select(intnfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, structtimeval *timeout);

nfds:最大的那个fd整数加1。

fd_set:fd的集合。三个集合,分别是读、写、异常的集合,当置为空时表示用不到。

可以将关心的fd放到对应的集合里去,然后交给内核去轮询。

fd集合的操作:

void FD_CLR(int fd,fd_set *set);//将集合里对应的fd清掉

vid FD_SET(int fd,fd_set *set);//将fd加到集合里

int FD_IS SET(int fd,fd_set *set);//是否准备好了,好了就返回非零值

void FD_ZERO(fd_set*set);//集合全部清空

timeout:代表超时时间,是struct timeval类型的结构体,参数有:long tv_sec(秒) 和long tv_usec(微秒),为空表示永不超时。

select代码实现:

//server.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h>  //套接字接口#include <arpa/inet.h>   //网络地址的转换#include <time.h>#include <pthread.h>int main(void){    int listenfd=socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in hostaddr;    hostaddr.sin_family=AF_INET;    hostaddr.sin_addr.s_addr=INADDR_ANY;    hostaddr.sin_port=htons(3017);    if(bind(listenfd,(struct sockaddr *)&hostaddr,sizeof(hostaddr))<0)    {        perror("bind");    }    if(listen(listenfd,10)<0)    {        perror("listen");    }    int fd1=accept(listenfd,NULL,NULL);        int fd2=accept(listenfd,NULL,NULL);    //创建一个fd集合    fd_set rdfdset;            char buffer[1024];    while(1)    {        //记得要清空这个集合        FD_ZERO(&rdfdset);        //将感兴趣的fd加进去        FD_SET(fd1,&rdfdset);        FD_SET(fd2,&rdfdset);        //算出来哪个fd最大        int maxfd=fd1>fd2?fd1:fd2;        //将轮询工作委托给内核,让它找出哪个fd准备好了.        select(maxfd+1,&rdfdset,NULL,NULL,NULL);        //如果fd1准备好了        if(FD_ISSET(fd1,&rdfdset))        {            bzero(buffer,sizeof(buffer));            read(fd1,buffer,sizeof(buffer));            write(fd2,buffer,strlen(buffer));        }        //如果fd2准备好了        if(FD_ISSET(fd2,&rdfdset))        {            bzero(buffer,sizeof(buffer));            read(fd2,buffer,sizeof(buffer));            write(fd1,buffer,strlen(buffer));        }           }}


//client.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h>  //套接字接口#include <arpa/inet.h>   //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"int sockfd;//从键盘读数据void *readfromkeyboard(void *arg){    while(1)    {/*4, 读取信息*/    char buffer[1024]={0};     printf("\n>>");    //从键盘读取    fgets(buffer,sizeof(buffer),stdin);        //发给服务器    if(write(sockfd,buffer,sizeof(buffer))<0)    {        perror("write");    }    }}//从服务器读数据void *readfromserver(void *arg){    char buffer[1024]={0};     while(1)    {    bzero(buffer,sizeof(buffer));    //从服务读回来    if(read(sockfd,buffer,sizeof(buffer))<0)    {        perror("read");    }    //将信息输出到屏幕上    write(STDOUT_FILENO,buffer,strlen(buffer));    }}//argv[1]是ip地址,argv[2]是端口int main(int argc, char *argv[]){    if(argc<3)    {        printf("usage:filename ipaddress port");        return -1;    }    /*1, 创建套接字*/    sockfd=socket(AF_INET,SOCK_STREAM,0);    /*2, 设置网络地址*/    struct sockaddr_in sockin;    sockin.sin_family=AF_INET;    inet_pton(AF_INET,argv[1],&sockin.sin_addr);    sockin.sin_port=htons(atoi(argv[2]));    /*3, 连接服务器    int connect(int sockfd, const struct sockaddr *addr,                   socklen_t addrlen);          */    if(connect(sockfd,(struct sockaddr *)&sockin, sizeof(sockin))<0)    {        perror("connet");        exit(EXIT_FAILURE);    }    pthread_t thread1,thread2;    pthread_create(&thread1,NULL,readfromkeyboard,NULL);    pthread_create(&thread2,NULL,readfromserver,NULL);while(1){        }pthread_join(thread1,NULL);pthread_join(thread2,NULL);close(sockfd);}



epoll函数

现今用的多的是epoll。

和select相比优点在于:

select是对加进去的所有fd进行轮询,返回之后也要对整个fd进行一次轮询,才能找到准备好的fd。

epoll采用事件触发的方式,当某个fd准备好后会触发事件,这样减少了内核的轮询。同时,epoll返回的是那些准备好的fd,避免程序员进行全部的轮询

另外,select的fd数上限一般是1024.但是epoll没有上限。

epoll函数原型:

1、int epoll_create(int size); 
创建一个epoll接口,返回一个epoll描述符,后面要用到。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用来从epoll接口中添加fd或删除、修改fd。
(1)epfd是接口描述符,
(2)op是添加、修改、删除三者,fd是要操作的对象,event是监视的事件。结构如下:
typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;


           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
3、int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
(1)events是输出型的参数,返回的是准备好的描述符,
(2)maxevents是事件数量的上限
(3)timeout是超时,以毫秒为单位,-1代表用不超时,0代表不等待立即返回。


epoll的水平触发和边沿触发?

水平触发:epoll的事件默认情况下是lt水平触发。例如,只要客户端的数据仍然未读完,那么事件就会一直发生。告诉服务器,请将数据读出来。

边沿触发:加上了events那里加了EPOLLET这个选项后,变成边沿触发。也就是数据可读,则只触发一次事件,服务器必须一直读,直到把数据读完。

epoll代码实现:

//server.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h>  //套接字接口#include <arpa/inet.h>   //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"#include "sys/epoll.h"int main(void){    int listenfd=socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in hostaddr;    hostaddr.sin_family=AF_INET;    hostaddr.sin_addr.s_addr=INADDR_ANY;    hostaddr.sin_port=htons(3017);    if(bind(listenfd,(struct sockaddr *)&hostaddr,sizeof(hostaddr))<0)    {        perror("bind");    }    if(listen(listenfd,10)<0)    {        perror("listen");    }    int fd_array[4];    int i;    for(i=0;i<4;i++)        fd_array[i]=accept(listenfd,NULL,NULL);        int epollfd=epoll_create(10000);      //创建事件结构体,用来输入到函数中的    struct epoll_event ev;    //创建事件结构体数组,用来从函数中带出来数据的    struct epoll_event ev_array[10000];    for(i=0;i<4;i++)    {        //可读事件        ev.events=EPOLLIN|EPOLLET;        //事件对应的fd        ev.data.fd=fd_array[i];        //将fd加入epoll中        epoll_ctl(epollfd,EPOLL_CTL_ADD,fd_array[i],&ev);    }        char buffer[1024];    while(1)    {       //查询有多少个fd准备好了       int nr=epoll_wait(epollfd,ev_array,1000,-1);       for(i=0;i<nr;i++)       {           int fd=ev_array[i].data.fd;            bzero(buffer,sizeof(buffer));            read(fd,buffer,sizeof(buffer));            write(fd,buffer,strlen(buffer));       }      }}

//client.c#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <netdb.h>#include <unistd.h>#include <sys/file.h>#include <sys/mman.h>#include <sys/wait.h>#include <mqueue.h> //消息队列#include <signal.h>#include <semaphore.h>#include <sys/socket.h>  //套接字接口#include <arpa/inet.h>   //网络地址的转换#include <time.h>#include <pthread.h>#include "header.h"int sockfd;//从键盘读数据void *readfromkeyboard(void *arg){    while(1)    {/*4, 读取信息*/    char buffer[1024]={0};     printf("\n>>");    //从键盘读取    fgets(buffer,sizeof(buffer),stdin);        //发给服务器    if(write(sockfd,buffer,sizeof(buffer))<0)    {        perror("write");    }    }}//从服务器读数据void *readfromserver(void *arg){    char buffer[1024]={0};     while(1)    {    bzero(buffer,sizeof(buffer));    //从服务读回来    if(read(sockfd,buffer,sizeof(buffer))<0)    {        perror("read");    }    //将信息输出到屏幕上    write(STDOUT_FILENO,buffer,strlen(buffer));    }}//argv[1]是ip地址,argv[2]是端口int main(int argc, char *argv[]){    if(argc<3)    {        printf("usage:filename ipaddress port");        return -1;    }    /*1, 创建套接字*/    sockfd=socket(AF_INET,SOCK_STREAM,0);    /*2, 设置网络地址*/    struct sockaddr_in sockin;    sockin.sin_family=AF_INET;    inet_pton(AF_INET,argv[1],&sockin.sin_addr);    sockin.sin_port=htons(atoi(argv[2]));    /*3, 连接服务器    int connect(int sockfd, const struct sockaddr *addr,                   socklen_t addrlen);          */    if(connect(sockfd,(struct sockaddr *)&sockin, sizeof(sockin))<0)    {        perror("connet");        exit(EXIT_FAILURE);    }    pthread_t thread1,thread2;    pthread_create(&thread1,NULL,readfromkeyboard,NULL);    pthread_create(&thread2,NULL,readfromserver,NULL);while(1){        }pthread_join(thread1,NULL);pthread_join(thread2,NULL);close(sockfd);}

1 0
原创粉丝点击