zoukankan      html  css  js  c++  java
  • 网络编程——C10K简述

    C10K问题
     
    网络服务在处理数以万计的客户端连接时,往往出现效率底下甚至完全瘫痪,这被成为C10K问题。
    (C10K = connection 10 kilo 问题)。k 表示 kilo,即 1000 比如:kilometer(千米), kilogram(千克)。
     
    非阻塞I/O,最关键的部分是readiness notification(when ready, then notify!)和找出哪一个socket上面发生了I/O事件。

    一般我们首先会想到用select来实现。
    int select(int n, fd_set *rd_fds; fd_set *wr_fds, fd_set *ex_fds, struct timeval * timeout);
     
    其中用到了fd_set结构,而fd_set不能大于FD_SETSIZE,默认是1024,很容易导致数组越界。
    针对fd_set的问题,*nix提供了poll函数作为select的一个替代品:

    int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
    第一个参数ufds是用户提供的一个pollfd数组,大小由用户自行决定,因此避免了FD_SETSIZE带来的麻烦。

    然而select和poll在连接数增加时,性能急剧下降。这有两方面的原因:
     
      《1》首先操作系统面对每次的select/poll操作,都需要重新建立一个当前线程的关心事件链表,并把线程挂在这个复杂的等待队列上,这是相当耗时的。
      《2》其次,应用软件在select/poll返回后也需要对传入的句柄链表做一次循环扫描来dispatch,这也是很耗时的。这两件事都是和并发数相关,而I/O事件的密度也和并发数相关,导致CPU占用率和并发数近似成O(n2)的关系。
     

    基于以上原因,*nix的hacker们开发了epoll, kqueue, /dev/poll这3套利器。epoll是Linux的方案,kqueue是freebsd的方案,/dev/poll是solaris的方案。
    简单的说,这些api做了两件事:
     
      《1》避免了每次调用select/poll时kernel分析参素建立事件等结构的开销,kernel维护一个长期的时间关注列表,应用程序通过句柄修改这个链表和捕获I/P事件
      《2》避免了select/poll返回后,应用程序扫描整个句柄表的开销,kernel直接返回具体的链表给应用程序。
     
    在接触具体api之前,先了解一下边缘触发(edge trigger)和条件触发(level trigger)的概念。边缘触发是指每当状态变化时发生一个io事件,假定经过长时间的沉默后,现在来了100个字节,这是无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序在读完来的50个字节,然后重新调用api等待io事件。这时条件触发的api会因为还有50个字节可读从而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。

    因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止, 否则这个socket就算废了。而使用条件触发的api时,如果应用程序不需要写就不要关注socket可写的事件,否则会无限次的立即返回一个 write ready nitification. 大家常用的select就是属于条件触发这一类,以前本人翻过长期关注socket写事件从而CPU 100%的毛病。
     
    epoll相关调用:
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
     
    epoll_create 创建kernel中的关注事件表,相当于创建fd_set.
    epoll_ctl 修改这个表,相当与FD_SET等操作。
    epoll_wait 完全是select/poll的升级版,支持的事件完全一致。并且epoll同时支持边缘触发和条件触发,一般来讲边缘触发的性能要好一些。
     
    简单的例子:
    [cpp] view plaincopy
    
            strut epoll_event ev, *events;  
            int kdpfd = epoll_create(100); //创建kernel中的关注事件表,返回一个kernel事件表的句柄  
              
            ev.events = EPOLLIN | EPOLLET; //边缘触发  
            ev.data.fd = listener;   
            epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev); //将事件ev加入到kernel关注的事件表中  
              
            for(;;){  
              
               nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待被通知   
               for(n = 0; n < nfds; n++){  
                  if(events[n].data.fd == listener){  
                     client = accept(listener, (struct sockaddr*)&local, &addrlen);   
                     if(client < 0){  
                        peror("accept");  
                        continue;  
                     }  
              
                     setnonblocking(client);  
                     ev.events = EPOLLIN | EPOLLET;  
                     ev.data.fd = client;  
                     if(epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0){  
                        fprintf(stderr, "epoll set insertion error: fd = %d, client);  
                        return -1;  
                 }  
                 }else  
                     do_use_fd(events[n].data.fd);  
               }  
            } 
  • 相关阅读:
    solidworks 学习 (二)洗手液瓶
    solidworks 学习 (一)螺丝刀
    tensorflow 2.0 学习(三)MNIST训练
    tensorflow 2.0 学习(二)线性回归问题
    tensorflow 2.0 学习(一)准备
    sscanf linux-c从一个字符串中读进与指定格式相符的数据
    Linux-c glib库hash表GHashTable介绍
    Linux-c给线程取名字
    linux-c getopt()参数处理函数
    golang Linux下编译环境搭建
  • 原文地址:https://www.cnblogs.com/Simon-xm/p/4093567.html
Copyright © 2011-2022 走看看