常见开源服务器模型学习 未完待续

  https://blog.csdn.net/answer3y/article/details/48276687html

 

本文要描述的主要有以下6种模型:
1)epoll 1线程(listen+accept+epoll_wait+处理) 模型 ...........................................表明产品redis
2)epoll 1线程(listen+accept+epoll_wait) + 1队列通知 + n线程(处理) 模型............表明产品thrift-nonblocking-server
2)epoll 1线程(listen+accept+epoll_wait) + n队列通知 + n线程(处理) 模型............表明产品memcached 
4)epoll 1进程(listen) + n进程(accept+epoll_wait+处理) 模型...............................表明产品nginx
5)epoll 1线程(listen) + n线程(accept+epoll_wait+处理) 模型
6)  epoll 1线程(listen+accept) + n线程(epoll_wait+处理) 模型
最后还有一个章节,对6种模型作一下统一的总结。
--------------------- linux

epoll网络编程几个主要函数的用途,这样能更好的理解下面6种模型
listen_fd = socket(...); // 建立listen_fd
bind(listen_fd, addr); // 把listen_fd绑定到本地地址addr
listen(listen_fd, ...); // 监听listen_fd
fd = accept(listen_fd, ...); // 从listen_fd接受一个新接进来的链接套接字fd
epfd = epoll_create(...); // 建立指定大小的epoll句柄epfd
epoll_ctl(epfd, op, fd, event); // 对epfd作op操做,操做涉及监听fd的event事件
// 好比op是EPOLL_CTL_ADD,意思是把 “对fd监听event事件” 注册到 epfd里面
num = epoll_wait(epfd, events, ...); // 监听句柄epfd上的事件,并把事件经过event数组返回,num返回值是发生事件的个数
---------------------

nginx

 

1、epoll 1线程(listen+accept+epoll_wait+处理) 模型
一、表明开源产品:redis
二、基本原理:
这种模型基本就是教科书上的epoll使用方式:
socket -> bind -> listen -> epoll_wait ->  accept或者处理读写事件 -> epoll_wait ......
redis基本遵循这样循环处理 网络事件和定时器事件
三、echo server测试:10万QPS
四、优势:
1)模型简单。这个模型是最简单的,代码实现方便,(因此这个单线程)适合 计算密集型应用                          若是是 IO 密集型那么最好就要使用多线程来提升cpu的利用率来
2)不用考虑并发问题。模型自己是单线程的,使得服务的主逻辑也是单线程的,那么就不用考虑许多并发的问题,好比锁和同步
3)适合短耗时服务。对于像redis这种每一个事件基本都是查内存,是十分适合的,一来并发量能够接受,二来redis内部众多数据结构都是很是简单地实现
五、缺点:
1)顺序执行影响后续事件。由于全部处理都是顺序执行的,因此若是面对长耗时的事件,会延迟后续的全部任务,特别对于io密集型的应用,是没法承受的
---------------------

web

 

2、epoll   1线程(listen+accept+epoll_wait)    +    1队列通知    +    n线程(处理) 模型
一、表明开源产品:thrift-nonblocking-server
二、基本原理:
1)在这种模型中,有1+n个线程。
2)有1个线程执行端口的listen并把listen_fd加入该线程的epoll_set,而后循环去作以下事情:1)epoll_wait监听新链接的到来,2)调用accept得到新到的fd,3)把fd放入队列,4) 回到 “1)” 继续epoll_wait
3)另外有n个工做线程,从队列里面获取文件描述符,而后执行:1)读取数据,2)执行任务
三、echo server测试:6万QPS
四、优势:
1)模型简单。这种模型的代码实现也是很是方便的
2)并发能力强。对于任务耗时或者IO密集的服务,能够充分利用多核cpu的性能实现异步并发处理
3)适合生产环境。虽然QPS没有特别高,可是对于目前大部分大型网站的吞吐量,这种网络处理能力也是足够了的,这也是为何thrift nonblocking server能够用这种模型的缘由
4)负载均衡。在这个模型,每一个工做工做线程完成任务以后就会去队列里主动获取文件描述符,这个模型自然地就实现了负载均衡的功能。缘由有2,一来是只有空闲的线程会拿到任务,二来是全部fd的事件监听都是由监听线程完成
五、缺点:
1)队列是性能瓶颈。
俗话说,不怕锁,只怕锁竞争。这个模型在运行过程当中,n+1个线程竞争于队列之上,因此队列的访问是须要加锁的。对于echo server这种每次任务耗时都极短的服务,每次处理完任务就很快就会回到队列锁的争抢行列。大量的锁竞争直接拖垮了QPS。
不过好在常见的生产环境web服务都不是echo server,每次请求都会是毫秒级的,不会在锁竞争上产生问题。
---------------------

redis

 

3、epoll   1线程(listen+accept+epoll_wait)  +  n队列通知  +  n线程(处理) 模型
一、表明开源产品:memcached
二、基本原理:
这种模型基本相似于 上一种模型,   区别在于 把1个队列换成n个队列,每一个工做线程绑定一个队列,每一个工做线程从本身的队列消费数据,其余的保持一致
三、echo server测试:20万QPS
四、优势:
1)并发能力更强。相比于单队列的模型,多队列的好处是减小了队列的锁竞争。 对于短耗时任务能获得比较多的提高,很适合缓存类应用
五、缺点:
1)有可能致使负载不均。由于监听线程是不会去根据不一样线程的处理速度决定把任务分配给哪一个线程的,  若是每一个任务的耗时不均衡,那么就可能致使有些线程累死,有些线程饿死
六、memcached对该模型改进:
memcached是多线程且是缓存类应用,很是适合这个模型。改进以下:
1)工做线程拿到的fd,这个fd会加到本线程的epoll_set里面,这个fd的后续读写事件都由该线程处理
2)工做线程和监听线程之间创建了管道,工做线程的管道fd也加入到工做线程的epoll_set里面,那么就可让 ‘新fd到来’和‘旧fd可读写’ 这两种事件都由epoll_set监听,减小调度的复杂性
3)由于memcached的任务基本是查内存的工做,耗时短并且相对均匀,因此对负载问题不是很敏感,可使用该模型编程

---------------------

segmentfault

4、epoll 1进程(listen) + n进程(accept+epoll_wait+处理) 模型
一、表明开源产品:nginx
二、基本原理:(依据nginx的设计分析)
1)master进程监听新链接的到来,并让其中一个worker进程accept。这里须要处理惊群效应问题,详见nginx的accept_mutex设计
2)worker进程accept到fd以后,把fd注册到到本进程的epoll句柄里面,由本进程处理这个fd的后续读写事件
3)worker进程根据自身负载状况,选择性地不去accept新fd,从而实现负载均衡
三、echo server测试:后续补充
四、优势:
1)进程挂掉不会影响这个服务
2)和第二种模型同样,是由worker主动实现负载均衡的,这种负载均衡方式比由master来处理更简单
五、缺点:
1)多进程模型编程比较复杂,进程间同步没有线程那么简单
2)进程的开销比线程更多
---------------------

数组

5、epoll 1线程(listen) + n线程(accept+epoll_wait+处理) 模型
一、表明开源产品:无
二、基本原理:
1)该设计和第四种的多进程模型基本一致,每一个worker进程换成worker线程
三、echo server测试:后续补充
四、优势:
1)多线程模型更简单,线程同步方便
2)多线程模型线程开销比进程更少
--------------------- 浏览器

 

 

6、epoll 1线程(listen+accept) + n线程(epoll_wait+处理) 模型 (200万QPS实现echo server方案)
一、对应开源产品:无
二、基本原理:
1)1个线程监听端口并accept新fd,把fd的监听事件round robin地注册到n个worker线程的epoll句柄上
2)若是worker线程是多个线程共享一个epoll句柄,那么事件须要设置EPOLLONESHOT,避免在读写fd的时候,事件在其余线程被触发
3)worker线程epoll_wait得到读写事件并处理之
三、echo server测试:200万QPS(由于资源有限,测试client和server放在同一个物理机上,实际能达到的上限应该还会更多)
四、优势:
1)减小竞争。在第四和第五种模型中,worker须要去争着完成accept,这里有竞争。而在这种模型中,就消除了这种竞争
五、缺点:
1)负载均衡。这种模型的链接分配,又回到了由master分配的模式,依然会存在负载不均衡的问题。可让若干个线程共享一个epoll句柄,从而把任务分配均衡到多个线程,实现全局更好的负载均衡效果
---------------------

缓存

 

7、总结和启示
一、除了第一个模型,其余模型都是多线程/多进程的,因此都采用了“1个master-n个worker”结构
二、若是accept由master执行,那么master就须要执行分配fd的任务,这种设计会存在负载不均的问题可是这种状况下,accept由谁执行不会存在竞争,性能更好
三、若是accept让worker去执行,那么同一个listen_fd,这个时候由哪一个worker来执行accept,便产生了竞争;产生了竞争就使得性能下降
四、对于通常的逻辑性web业务,选择由master去accept并分配任务更合适一些。 由于大部分web业务请求耗时都较长(毫秒级别),并且请求的耗时不尽相同,因此对负载均衡要求也较高; 另一般的web业务,也不可能到单机10万QPS的量级
五、这几个模型的变化,无非就是三个问题的选择:
1) 多进程,多线程,仍是单线程?
2)每一个worker本身管理事件,仍是由master统一管理?
3)accept由master仍是worker执行?
六、在系统设计中,须要根据实际状况和需求,选择合适的网络事件方案
---------------------

 

https://blog.csdn.net/zmzsoftware/article/details/17356199

  

void ET( epoll_event* events, int number, int epollfd, int listenfd )//边沿非阻塞 方式
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {//这段代码不会被重复触发  因此循环取出数据 确保把socket缓冲区存储的数据读出来
            printf( "event trigger once\n" );
            while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {//对于非阻塞IO 下面成立表示数据读取ok 此后epoll就能再一次触发fd 上的EPOLLIN事件
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {   //无需关闭socket   说明还有数据未接收 等待下一次处理
                        printf( "read later\n" );
                        break;
                    }
                    //返回-1 那么寿命发生错误直接中止
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );// ==0 ET模式返回0 表示已经接受了全部数据了
                }
                else
                {//接收ok
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

 

 

 connect()函数

1.阻塞模式

客户端调用connect()函数将激发TCP的三路握手过程,但仅在链接创建成功或出错时才返回。返回的错误可能有如下几种状况:

    1>.若是TCP客户端没有接收到SYN分节的响应即(ACK),则返回ETIMEDOUT,阻塞模式的超时时间在75秒(4.4BSD内核)到几分钟之间。

    2>.若是对客户的SYN的响应时RST,则代表该服务器主机在咱们指定的端口上没有进程在等待与之链接(例如服务器进程也许没有启动),这称为硬错,客户一接收到RST,立刻就返回错误 ECONNREFUSED.

    3>.若是某客户发出的SYN在中间的路由器上引起了一个目的地不可达ICMP错误,屡次尝试发送失败后返回错误号为EHOSTUNREACH或ENETUNREACH.

附加产生RST的三种状况,一是SYN到达某端口但此端口上没有正在侦听的服务器、 二是TCP想取消一个已有链接、  三是TCP接收了一个根本不存在的链接上的分节

 

 2 非阻塞connect:

当一个非阻塞的tcp套接字上调用connect时,connect将当即返回一个EINPROGRESS错误,不过已经发起的tcp三路握手继续进行。咱们接着使用select检测这个链接或成功或失败的已创建条件。非阻塞connect有三个用途:

(1) 咱们能够把三路握手叠加在其余处理上,完成一个connect要花的RTT时间,而RTT波动很大,从局域网上的几毫秒到几百毫秒甚至是广域网的几秒。这段时间内也许有咱们想要执行的其余工做可执行;

(2) 咱们可以使用这个技术同时创建多个链接;这个技术随着web浏览器流行起来;

(3) 既然使用select等待链接创建,咱们能够给select指定一个时间限制,使得咱们可以缩短connect的超时。

非阻塞connect细节:

(1) 尽管套接字是非阻塞的,若是链接到的服务器在同一个主机上,那么当咱们调用connect时候,链接一般马上创建,咱们必须处理这种情形;

(2) 源自Berkeley的实现(和posix)有关select和非阻塞connect的如下两个原则:

--(a) 当链接成功创建时,描述符变为可写;

--(b) 当链接创建遇到错误时,描述符变为既可读又可写;

 

 

关于accept函数调用的理解和整理      也能够阅读  unix网络编程p362       非阻塞accept 

                                 accept以前内核有两个队列 要知道

.accept()函数

1.阻塞模式

          第一种状况:阻塞模式下调用accept()函数,并且没有新链接时,进程会进入睡眠状态。  (最多见状况 也是最好理解的一种状况

     第二种状况:就是,当使用IO多路复用的函数如select/EPOLL_WAIT函数等方法去检测监听套接字一个外来链接时  不少时候咱们将监听套接字设置为非阻塞     不少人以为 IO多路复用函数会告诉咱们该套接字上已有链接就绪  那么accept函数调用就不该该阻塞啊!  但是这里有bug啊 !

在比较忙的服务器中,在创建三次握手以后调用accept以前可能出现客户端断开链接的状况,再这样的状况下;如,三次握手以后,客户端发送rst,而后服务器调用accept,  即  服务器在io多路复用函数返回到调用accept以前 服务器收到客户端的RST   这是已经完成三次握手的 链接会被  服务器驱除出队列   假设队列中没有其余完成的链接了     服务器此时调用accept  可是没有已经完成3次握手的链接   因而服务器阻塞了  没法处理其余已经就绪的链接了     值到有其客户端创建一个链接位置

      解决办法以下        就是下面的那样 

 

2.非阻塞模式

非阻塞模式下调用accept()函数,并且没有新链接时,将返回  EWOULDBLOCK 错误。

 

非阻塞模式select() + accept() 

非阻塞accept:

在比较忙的服务器中,在创建三次握手以后,调用accept以前,可能出现客户端断开链接的状况,再这样的状况下;如,三次握手以后,客户端发送rst,而后服务器调用accept。posix指出这种状况errno设置为CONNABORTED;

注意Berkeley实现中,没有返回这个错误,而是EPROTO,同时完成三次握手的链接会从已完成队列中移除;在这种状况下,若是咱们用select监听到有新的链接完成,但以后又被从完成队列中删除,此时若是调用阻塞accept就会产生阻塞;

解决办法:

(1) 使用select监听套接字是否有完成链接的时候,老是把这个监听套接字设置为非阻塞;

(2) 在后续的accept调用中忽略如下错误,EWOULDBLOCK(Berkeley实现,客户停止链接),  ECONNABORTED(posix实现,客户停止链接), EPROTO(serv4实现,客户停止链接)和EINTR(若是有信号被捕获);

 

 

 

 http://www.cnblogs.com/fighting-boy/p/9875288.html         (linux网络编程    里面有两个大一点的demo  web服务器  一个防火墙)

 

     epoll     EPOLLONESHOT

https://segmentfault.com/a/1190000003063859

https://blog.csdn.net/linuxheik/article/details/73294658

https://www.cnblogs.com/charlesblc/p/5538363.html

https://blog.csdn.net/liuhengxiao/article/details/46911129

相关文章
相关标签/搜索