Serveur de transfert IO multicanal

Serveur de transfert d'E/S multicanal

Le serveur de transfert IO multicanal est également appelé serveur IO multitâche. L'idée principale de ce type d'implémentation de serveur est qu'au lieu que l'application surveille la connexion client elle-même, le noyau surveille le fichier pour l'application.

sélectionner

int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

//委托内核监控该文件描述符对应的读,写或者错误事件的发生.

//nfds: 告诉内核要监控文件描述符的范围,一般取值为最大的文件描述符+1
//readfds: 读集合, 是一个传入传出参数
//传入: 指的是告诉内核哪些文件描述符需要监控
//传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
//writefds: 写文件描述符集合(传入传出参数)
//execptfds: 异常文件描述符集合(传入传出参数)
//timeout: 
//NULL--表示永久阻塞, 直到有事件发生
//0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
//>0--表示阻塞时长,若没有超过时长,则一直阻塞;若在时长内,有事件发生则立刻返回,若超过时长,则立刻返回
	
//成功返回发生变化的文件描述符的个数,失败返回-1, 并设置errno
void FD_CLR(int fd, fd_set *set);
//将fd从set集合中清除

int FD_ISSET(int fd, fd_set *set);
//判断fd是否在集合中
//如果fd在set集合中, 返回1, 否则返回0

void FD_SET(int fd, fd_set *set);
//将fd设置到set集合中

void FD_ZERO(fd_set *set);
//初始化set集合

Utilisez select pour développer le serveur

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<ctype.h>
#include<errno.h>

int main()
{
    
    
    //创建socket
    int lfd=socket(AF_INET,SOCK_STREAM,0);

    //设置端口复用
    int opt=1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

    //绑定
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_family=AF_INET;
    serv.sin_port=htons(8888);
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    bind(lfd,(struct sockaddr*)&serv,sizeof(serv));

    //监听
    listen(lfd,128);

    //定义fd_set
    fd_set readfds;
    fd_set tmpfds;

    //清空fd_Set集合
    FD_ZERO(&readfds);
    FD_ZERO(&tmpfds);

    //将lfd加入到readfds中,委托内核监考
    FD_SET(lfd,&readfds);

    int maxfd=lfd;
    int nready;
    int cfd;
    int i;
    int sockfd;
    int n;
    char buf[1024];
    while(1)
    {
    
    
        tmpfds=readfds;
        nready=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
        if(nready<0)
        {
    
    
            if(errno==EINTR)//被信号中断
            {
    
    
                continue;
            }
            break;
        }

        //有客户端连接请求到来
        if(FD_ISSET(lfd,&tmpfds))
        {
    
    
            //接受新的客户端连接请求
            cfd=accept(lfd,NULL,NULL);

            //将cfd加入到readfds集合中
            FD_SET(cfd,&readfds);

            //修改select的监考范围
            if(maxfd<cfd)
            {
    
    
                maxfd=cfd;
            }

            if(--nready==0)
            {
    
    
                continue;
            }

        }

        //有数据发来情况
        for(i=lfd+1;i<=maxfd;i++)
        {
    
    
            sockfd=i;
            if(FD_ISSET(sockfd,&tmpfds))
            {
    
    
                //读数据
                memset(buf,0x00,sizeof(buf));
                n=read(sockfd,buf,sizeof(buf));
                if(n<=0)
                {
    
    
                    //关闭连接
                    close(cfd);

                    //将sockfd从readfds集合中删除
                    FD_CLR(sockfd,&readfds);
                }
                else
                {
    
    
                    printf("n==[%d],buf==[%s]\n",n,buf);

                    int k;
                    for(k=0;k<n;k++)
                    {
    
    
                        buf[k]=toupper(buf[k]);
                    }
                    write(cfd,buf,n);
                }

                if(--nready==0)
                {
    
    
                    break;
                }
            }
        }

    }


    close(lfd);
    return 0;
}

S'il y a moins de descripteurs de fichiers efficaces, cela fera la boucle trop de fois

Solution : vous pouvez placer des descripteurs de fichiers valides dans un tableau, de sorte que l'efficacité de la traversée soit élevée

sélectionnerAvantages

  • Un processus peut prendre en charge plusieurs clients
  • select prend en charge plusieurs plates-formes

sélectionner les inconvénients

  • Difficulté à écrire du code
  • Cela impliquera de copier dans les deux sens depuis la zone utilisateur vers la zone du noyau
  • Lorsque le client a plusieurs connexions, mais que quelques-unes sont actives, select est moins efficace

Par exemple : Dans une situation extrême, tous les descripteurs de fichiers 3-1023 sont ouverts, mais seulement 1023 ont envoyé des données, select est inefficace

  • Prise en charge maximale de 1024 connexions client

Le nombre de descripteurs de fichiers que select peut surveiller est limité par FD_SETSIZE

Select prend en charge un maximum de 1024 connexions client, non limité par la table de descripteurs de fichiers peut prendre en charge jusqu'à 1024 descripteurs de fichiers, mais limité par FD_SETSIZE=1024

FD_SETSIZE=1024 fd_set utilise cette macro, vous pouvez modifier le noyau, puis recompiler le noyau, généralement déconseillé

  • Il est très approprié d'utiliser select pour résoudre les clients en dessous de 1024, mais s'il y a trop de clients connectés, select utilise un modèle d'interrogation, ce qui réduira considérablement l'efficacité de la réponse du serveur, et plus d'efforts ne doivent pas être investis dans select

sondage

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//跟select类似, 监控多路IO, 但poll不能跨平台

//fds: 传入传出参数, 实际上是一个结构体数组
//fds.fd: 要监控的文件描述符
//fds.events: 
//POLLIN---->读事件
//POLLOUT---->写事件
//fds.revents: 返回的事件
//nfds: 数组实际有效内容的个数
//timeout: 超时时间, 单位是毫秒.
//-1:永久阻塞, 直到监控的事件发生
//0: 不管是否有事件发生, 立刻返回
//>0: 直到监控的事件发生或者超时
//成功:返回就绪事件的个数,失败: 返回-1
//若timeout=0, poll函数不阻塞,且没有事件发生, 此时返回-1, 并且errno=EAGAIN, 这种情况不应视为错误

struct pollfd 
{
    
    
   int   fd;        /* file descriptor */   //监控的文件描述符
   short events;     /* requested events */  //要监控的事件---不会被修改
   short revents;    /* returned events */   //返回发生变化的事件 ---由内核返回
};

//当poll函数返回的时候, 结构体当中的fd和events没有发生变化, 究竟有没有事件发生由revents来判断, 所以poll是请求和返回分离
//struct pollfd结构体中的fd成员若赋值为-1, 则poll不会监控
//相对于select, poll没有本质上的改变; 但是poll可以突破1024的限制
//在/proc/sys/fs/file-max查看一个进程可以打开的socket描述符上限
//如果需要可以修改配置文件: /etc/security/limits.conf
//加入如下配置信息, 然后重启终端即可生效
//soft nofile 1024
//hard nofile 100000
//soft和hard分别表示ulimit命令可以修改的最小限制和最大限制

epoll

//将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的事件返回给应用程序

int epoll_create(int size);
//创建一个树根
//size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数
//成功: 返回一个大于0的文件描述符, 代表整个树的树根
//失败: 返回-1, 并设置errno值

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//将要监听的节点在epoll树上添加, 删除和修改
//epfd: epoll树根
//op:
//EPOLL_CTL_ADD: 添加事件节点到树上
//EPOLL_CTL_DEL: 从树上删除事件节点
//EPOLL_CTL_MOD: 修改树上对应的事件节点
//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 */
};

//event.events常用的有:
//EPOLLIN: 读事件
//EPOLLOUT: 写事件
//EPOLLERR: 错误事件
//EPOLLET: 边缘触发模式
//event.fd: 要监控的事件对应的文件描述符


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//等待内核返回事件发生
//epfd: epoll树根
//events: 传出参数, 其实是一个事件结构体数组
//maxevents: 数组大小
//timeout:
//-1: 表示永久阻塞
//0: 立即返回
//>0: 表示超时等待事件
//成功: 返回发生事件的个数
//失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值, 

//epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<ctype.h>
#include<errno.h>
#include<sys/epoll.h>

int main()
{
    
    
    int ret,n,i,nready,lfd,cfd,sockfd,epfd,k;
    char buf[1024];
    socklen_t socklen;
    struct sockaddr_in svraddr;
    struct epoll_event ev;
    struct epoll_event events[1024];

    //创建socket
    lfd=socket(AF_INET,SOCK_STREAM,0);

    //设置端口复用
    int opt=1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

    //绑定
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_family=AF_INET;
    serv.sin_port=htons(8888);
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    bind(lfd,(struct sockaddr*)&serv,sizeof(serv));

    //监听
    listen(lfd,128);

    //创建一棵epoll数
    epfd=epoll_create(1024);
    if(epfd<0)
    {
    
    
        perror("create epoll error");
        return -1;
    }

    //将lfd上epoll树
    ev.data.fd=lfd;
    ev.events=EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

    while(1)
    {
    
    
        nready=epoll_wait(epfd,events,1024,-1);
        if(nready<0)
        {
    
    
            perror("epoll wait error");
            if(errno==EINTR)
            {
    
    
                continue;
            }
            break;
        }

        for(i=0;i<nready;i++)
        {
    
    
            //有客户端连接请求
            sockfd=events[i].data.fd;
            if(sockfd==lfd)
            {
    
    
                cfd=accept(lfd,NULL,NULL);
                //将新cfd上epoll树
                ev.data.fd=cfd;
                ev.events=EPOLLIN;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
                continue;
            }

            //有数据发生过来
            memset(buf,0x00,sizeof(buf));
            n=read(sockfd,buf,sizeof(buf));
            if(n<=0)
            {
    
    
                close(sockfd);
                //将sockfd对应的事件就节点从epoll树上删除
                epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
            }
            else
            {
    
    
                for(k=0;k<n;k++)
                {
    
    
                    buf[k]=toupper(buf[k]);
                }
                write(sockfd,buf,n);
            }
        }
    }
    close(epfd);
    close(lfd);
    return 0;
}

Deux modes de fonctionnement d'epoll ET et LT

Déclencheur de niveau : niveau haut signifie 1

  • Continuer à notifier tant qu'il y a des données dans le tampon

Déclenchement sur front : un changement de niveau signifie 1

  • S'il y a des données dans le tampon, il ne sera notifié qu'une seule fois, puis il sera notifié lorsqu'il y a des données (si les données ne sont pas lues, les données restantes ne seront pas notifiées jusqu'à ce que de nouvelles données arrivent)

Mode Edge non bloquant : amélioration de l'efficacité

La condition par défaut d'epoll est le mode LT. Dans ce mode, si les données lues ne sont pas lues en même temps et qu'il y a encore des données lisibles dans la mémoire tampon, epoll_wait notifiera à nouveau

Si epoll est réglé sur le mode ET, si les données ne sont pas lues en même temps, epoll_wait ne notifiera pas jusqu'à la prochaine fois que de nouvelles données seront envoyées 6

réacteur epoll

Réacteur : Un petit événement déclenche une série de réactions.

L'idée du réacteur epoll : l'idée de l'encapsulation c++ (encapsulation des données et des opérations ensemble)

​ --Encapsuler ensemble les descripteurs, les événements et les méthodes de traitement correspondantes

​ --Lorsque l'événement correspondant au descripteur se produit, la méthode de traitement est automatiquement appelée (en fait, le principe est la fonction callback)

L'idée centrale du réacteur epoll est la suivante : lors de l'appel de la fonction epoll_ctl, lorsque des événements sont ajoutés à l'arborescence, utilisez le membre ptr de epoll_data_t pour encapsuler un descripteur de fichier, un événement et une fonction de rappel dans une structure, puis laissez pointer ptr à cette structure , puis lors de l'appel de la fonction epoll_wait pour revenir, vous pouvez obtenir les événements spécifiques, puis obtenir les événements dans la structure des événements. pointeur data.ptr, il existe une fonction de rappel dans la structure pointée par le pointeur ptr, et cette fonction de rappel peut éventuellement être appelée

Je suppose que tu aimes

Origine blog.csdn.net/blll0/article/details/121618409
conseillé
Classement