第十章 linux I/O模型 (文件 socket i/o)

 (1) 阻塞模型

  没有数据到达时候程序会一直阻塞直到有数据到达  如tcp 的recv 函数,当然也可以设置超时。

(2)非阻塞I/O模型

  通过fcntl函数设置socket 非阻塞模型  fcntl(sockfd, F_SETFL, O_NONBLOCK);如果没有数据到达会返回一个错误码。一般对于一般都对非阻塞I/O模型进行轮询,就是一直在检查这个状态,查看有无数据到达。

(3)3. I/O复用模型  select模型 (重点)

  利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。

  select参数和返回值意义如下:

  int select (

         IN int nfds,                           //0,无意义

         IN OUT fd_set* readfds,      //检查可读性

         IN OUT fd_set* writefds,     //检查可写性

         IN OUT fd_set* exceptfds,  //例外数据

         IN const struct timeval* timeout);    //函数的返回时间

  返回值:错误:-1,超时:0, 执行成功则返回文件描述词状态已改变的个数。

  参数nfds代表最大的文件描述词加1,   

  参数 timeout为结构timeval,用来设置select()的等待时间,有三种情况,1永远等待,超时参数设置为NULL,2等待一定时间,时间自己设定。3不等待,超时设置为0。

   参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。

  fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:

  FD_CLR( s, *set) 从队列set删除句柄s;

  FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;

  FD_SET( s, *set )把句柄s添加到队列set中;

  FD_ZERO( *set ) 把set队列初始化成空队列.

  Select工作流程:

    1> 用FD_ZERO宏来初始化我们感兴趣的fd_set。

    2> 用FD_SET宏来将套接字句柄分配给相应的fd_set。

    3> 调用select函数。

    4:用FD_ISSET对套接字句柄进行检查。 

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <string.h> 
#include <unistd.h> 
  
int main(int argc, char *argv[]) 
{ 
    //这个服务器同时监听7777和7778两个端口 
      
    //绑定监听7779端口的fd 
    int listenfd1; 
    struct sockaddr_in serv_addr1; 
    listenfd1 = socket(AF_INET, SOCK_STREAM, 0); 
      
    bzero((char *) &serv_addr1, sizeof(serv_addr1)); 
    serv_addr1.sin_family = AF_INET; 
    serv_addr1.sin_port = htons(7777); 
    serv_addr1.sin_addr.s_addr = INADDR_ANY; 
      
    bind(listenfd1, (struct sockaddr *) &serv_addr1, sizeof(serv_addr1)); 
    listen(listenfd1, 5); 
      
    //绑定监听7778端口的fd 
    int listenfd2; 
    struct sockaddr_in serv_addr2; 
    listenfd2 = socket(AF_INET, SOCK_STREAM, 0); 
      
    bzero((char *) &serv_addr2, sizeof(serv_addr2)); 
    serv_addr2.sin_family = AF_INET; 
    serv_addr2.sin_port = htons(7778); 
    serv_addr2.sin_addr.s_addr = INADDR_ANY; 
      
    bind(listenfd2, (struct sockaddr *) &serv_addr2, sizeof(serv_addr2)); 
    listen(listenfd2, 5); 
      
      
    int maxfd; 
    //为什么这里设置两个fd_set?每次select的时候函数会把没有事件发生的描述字清零,所以需要两个集合 
    fd_set allset, rset; 
    maxfd = listenfd1; 
    if(listenfd2 > maxfd) { 
        maxfd = listenfd2; 
    } 
      
    FD_ZERO(&allset); 
    FD_SET(listenfd1, &allset); 
    FD_SET(listenfd2, &allset); 
      
    int clifd, clilen; 
    struct sockaddr_in cli_addr; 
    char buffer[256]; 
    for(;;) { 
        rset = allset; 
        select(maxfd + 1, &rset, NULL, NULL, NULL); 
          
        //如果是listenfd1 获取消息 
        if(FD_ISSET(listenfd1, &rset)) { 
            clilen = sizeof(cli_addr); 
            clifd = accept(listenfd1, (struct sockaddr *) &cli_addr, &clilen); 
              
            bzero(buffer, 256); 
            read(clifd, buffer, 255); 
            printf("Listenfd1 Message is:%s\r\n", buffer); 
        } 
          
        //如果是listenfd1 获取消息 
        if(FD_ISSET(listenfd2, &rset)) { 
            clilen = sizeof(cli_addr); 
            clifd = accept(listenfd2, (struct sockaddr *) &cli_addr, &clilen); 
              
            bzero(buffer, 256); 
            read(clifd, buffer, 255); 
            printf("Listenfd2 Message is:%s\r\n", buffer); 
        } 
        close(clifd); 
    } 
      
    close(listenfd1); 
    close(listenfd2); 
  
    return 0; 
}

 

(4)信号驱动I/O模型

  当socket 有数据到达,内核就会发送 SIGIO 信号,可以调用 sigaction 安装 SIGIO 信号的处理函数,这个时候就可以在 SIGIO 信号处理函数中进行 recv函数来接受数据。

(5)异步I/O模型  (poll模型 和 epoll模型) (重点)

  注:  这里只讲epoll    poll模型 查阅函数int poll(struct pollfd *ufds, unsigned int nfds, int time­out);

  Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 。

  epoll 模型优点:

    Epoll 没有最大并发连接的限制 

    效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关.

    内存拷贝, Epoll 在这点上使用了“共享内存"

 

  使用epoll:

    typedef union epoll_data{void *ptr;int fd; /*Socket*/  __uint32_t u32;  __uint64_t u64; } epoll_data_t; 

    struct epoll_event {__uint32_t events;  /*程序关注的事件 */   epoll_data_t data;  };

    其中events可以用以下几个宏的集合:

    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)  

    EPOLLOUT:表示对应的文件描述符可以写

    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

    EPOLLERR:表示对应的文件描述符发生错误

    EPOLLHUP:表示对应的文件描述符被挂断;

    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

    

    1> 生成一个 Epoll 专用的文件描述符  int epoll_create(int size);   size 就是你在这个 Epoll fd 上能关注的最大 socket数.

    2> 控制某个 Epoll 文件描述符上的事件:注册、修改、删除。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );

       参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。        

       参数op操作类型,有如下取值: EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除

       参数fd   socket

       参数event 指向epoll_event的指针

 

     3> 等待 I/O 事件的发生 int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);  

       参数epfd:由epoll_create 生成的epoll专用的文件描述符;

       参数epoll_event:用于回传等待处理的事件数组; 

       参数maxevents:每次能处理的事件数; 

       参数timeout:等待I/O事件发生的超时值(ms);-1永不超时,直到有事件产生才触发,0立即返回

  

  工作方式:

    1> LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。

    2>ET(edge-triggered):边沿触发,高速工作方式, 只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

  区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。 

#include stdio.h>
#include string.h>
#include unistd.h>
#include fcntl.h>
#include errno.h>
#include sys/types.h>
#include sys/socket.h>
#include sys/epoll.h>
#include netinet/in.h>
#include pthread.h>
#include stdlib.h>
#define SERV_PORT 5358
#define MAX_CONN 1024
#define EVENT_NUM 1024
#define EPOLL_SIZE 1024
#define BUF_LEN 1024

int setnonblocking(int fd)
{
    int opts;
    if((opts = fcntl(fd, F_GETFL))  0)
    {
        return -1;
    }
    opts |=O_NONBLOCK;
    if(fcntl(fd, F_SETFL, opts) 0)
    {
        return -1;
    }
    return 0;
}


void *str_echo(void *arg)
{
    int sockfd;
    ssize_t nread;
    char buf[BUF_LEN] = {0};
    pthread_detach(pthread_self());
    sockfd = *(int *)arg;
    while(1) 
    {
        bzero(buf, BUF_LEN);
        if((nread = read(sockfd, buf, BUF_LEN)) == -1)
        {
            if(errno == EINTR) 
            {
                continue;
            }
            else 
            {
                printf("read error: %s\n", strerror(errno));
                continue;
            }
        }
        else if (nread == 0) 
        {
            break;
        }
        else 
        {
            //fputs(buf, stdout);
            write(sockfd, buf, nread);
        }
    }
    return NULL;    
}
int main(int argc, char **argv)
{
    int listenfd, connfd, epfd, nfds;
    socklen_t addrlen;
    struct sockaddr_in cliaddr, servaddr;
    struct epoll_event ev, events[EVENT_NUM];
    pthread_t tid;
    //create socket fd
    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        printf("Create socket error!\n");
        return 0;
    }
    if (setnonblocking(listenfd) == -1)
    {
        printf("setnonblicking error!\n");
        close(listenfd);
        return 0;
    }
    //bind
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        printf("Socket bind error!\n");
        close(listenfd);
        return 0;
    }
    //listen
    if(listen(listenfd, MAX_CONN) == -1)
    {
        printf("listen error\n");
        close(listenfd);
        return 0;
    }
    //create epoll
    if((epfd = epoll_create(EPOLL_SIZE)) == -1)
    {
        printf("Create epoll error!\n");
        close(listenfd);
        return 0;
    }
    //register epoll event
    ev.data.fd = listenfd;
    ev.events = EPOLLIN | EPOLLET;
    if ((epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev)) == -1)
    {
        printf("epoll_ctl error!\n");
        close(listenfd);
        return 0;
    }
    while(1)
    {
        if((nfds = epoll_wait(epfd, events, EVENT_NUM, -1)) ==-1)
        {
            if(errno == EINTR){
                printf("%s\n", strerror(errno));
                continue;
            }
            else{
                printf("epoll_wait error!\n");
                continue;
            }
        }
        int i;
        for(i=0;infds;i++)
        {
            if(events.data.fd == listenfd)
            {
                addrlen = sizeof(cliaddr);
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &addrlen);
                if(connfd == -1)
                {
                    printf("%s\n", strerror(errno));
                    continue;
                }
                
                printf("New Connection %d\n", connfd);
                ev.data.fd = connfd;
                ev.events = EPOLLIN | EPOLLET;
                if((epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev)) == -1)
                {
                    printf("connect failed!\n");
                }
            }
            else
            {
                if((pthread_create(&tid, NULL, str_echo, &events.data.fd)) == -1) 
                {
                    exit(0);
                }
            }
        }
    }
    return 0;
}

 

  附加: epoll_data.ptr 可以设置成socket对应的回掉函数,当事件发生时候可以调用该函数处理。 也可设置成其它参数。

 

   

 

posted @ 2013-07-16 15:19  皁仩腄覺  阅读(283)  评论(0)    收藏  举报