select和epoll

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

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);

}

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值