使用poll实现聊天服务

一、概述

  poll函数介绍:

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 */   返回发生变化的事件 ---由内核返回
   };


说明: 
   1 当poll函数返回的时候, 结构体当中的fd和events没有发生变化, 究竟有没有事件发生由revents来判断, 所以poll是请求和返回分离.
   2 struct pollfd结构体中的fd成员若赋值为-1, 则poll不会监控.
   3 相对于select, poll没有本质上的改变; 但是poll可以突破1024的限制.

   在/proc/sys/fs/file-max查看一个进程可以打开的socket描述符上限.
   如果需要可以修改配置文件: /etc/security/limits.conf
   加入如下配置信息, 然后重启终端即可生效.
   * soft nofile 1024  软限制不能超过硬件限制,即:soft nofile的值不能超过hard nofile的值
   * hard nofile 100000 
   soft和hard分别表示ulimit命令可以修改的最小限制和最大限制

  案例:使用poll函数实现高并发服务器

二、代码示例

//IO多路复用技术poll函数的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include "wrap.h"

int main(){
    int i;//for循环的索引
    int n;//读取数据的个数
    int lfd;//监控文件描述符
    int cfd;//通讯文件描述符
    int ret;
    int nready;//就绪的时间个数
    int maxfd;//最大文件描述符
    char buf[1024];//读写缓冲区大小
    socklen_t len;//
    int sockfd;//通信文件描述符
    fd_set tmpfds,rdfs;
    struct sockaddr_in svraddr,cliaddr;

    //创建socket,并返回监听文件描述符
    lfd = Socket(AF_INET,SOCK_STREAM,0);

    //允许端口复用
    int opt;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

    //绑定地址和端口
    svraddr.sin_family = AF_INET;
    svraddr.sin_port = htons(8888);
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));

    //监听
    ret = Listen(lfd,128);

    //填充要监控的结构体数组client[i].fd=-1表示内核不监控
    struct pollfd client[1024];
    for(i=0;i<1024;i++){
        client[i].fd = -1;
    }


    //将监听文件描述符委托给内核监控----监控读事件
    client[0].fd = lfd;
    client[0].events = POLLIN;//表示读事件

    maxfd = 0;//maxfd表示内核监控的范围

    while(1){
        nready = poll(client,maxfd+1,-1);//监控多路IO,返回就绪的事件个数
        if(nready<0){//如果事件个数小于0说明出错了,直接退出进程即可
            perror("poll error");
            exit(1);
        }

        //有客户端连接请求(监听文件描述符=lfd,并且事件类型是读事件)
        if(client[0].fd = lfd &&(client[0].revents&POLLIN)){
            //接收客户端连接并返回一个通讯文件描述符
            cfd = Accept(lfd,NULL,NULL);

            //寻找client数组中可用位置(找到位置后将cfd加入数据交给内核监控)
            for(i=1;i<1024;i++){
                if(client[i].fd ==-1){
                    client[i].fd = cfd;
                    client[i].events = POLLIN;
                    break;
                }
            }

            //如果没有可用位置则关闭连接
            if(i==1024){
                Close(cfd);
                continue;
            }
            if(maxfd<i){//可用就绪的文件描述符的最大索引
                maxfd = i;
            }
            if(--nready==0){//如果就一个就绪的文件描述符,那就不用往下执行了
                continue;
            }
        }
        //下面是有数据到来的情况(只用循环可用的文件描述符的最大索引即可)
        for(i=1;i<=maxfd;i++){
            //若fd为-1 表示连接已经关闭或者没有连接
            if(client[i].fd == -1){
                continue;
            }
            sockfd = client[i].fd;
            memset(buf,0x00,sizeof(buf));
            n = Read(sockfd,buf,sizeof(buf));//从内核中读取缓冲区数据
            if(n<=0){
                printf("read error or client closed,n==[%d]\n",n);
                Close(sockfd);
                client[i].fd = -1;//fd为-1,表示不再让内核监控
            }else{
                printf("read over ,n=[%d],buf=[%s]\n",n,buf);
                write(sockfd,buf,n);//给客户端回复数据
            }
            if(--nready==0){
                break;
            }

        }
    }
    Close(lfd);//最后要关闭监听文件描述符
    return 0;
}

 

posted on 2021-12-15 10:00  飘杨......  阅读(17)  评论(0编辑  收藏  举报