精华内容
下载资源
问答
  • 多路复用技术

    2013-05-14 16:49:41
    详细介绍了几种常见多路复用技术,例如:频分复用、码分复用、波分复用等。
  • 多路复用技术分为以下四种: 1、频分多路复用,特点是把电路或空间的频带资源分为多个频段,并将其分配给多个用户,每个用户终端的数据通过分配给它的子通路传输。主要用于电话和电缆电视系统。 2、时分多路复用,...

    转载:https://zhidao.baidu.com/question/2073819183176757868.html
    多路复用技术分为以下四种:

    1、频分多路复用,特点是把电路或空间的频带资源分为多个频段,并将其分配给多个用户,每个用户终端的数据通过分配给它的子通路传输。主要用于电话和电缆电视系统。

    2、时分多路复用,特点是按传输的时间进行分割,将不同信号在不同时间内传送。又包含两种方式:同步时分复用和异步时分复用。

    3、波分多路复用,特点是对于光的频分复用。做到用一根光纤来同时传输与多个频率很接近的光波信号。

    4、码分多路复用,特点是每个用户可在同一时间使用同样的频带进行通信,是一种共享信道的方法。通信各方面之间不会相互干扰,且抗干扰能力强。

    在这里插入图片描述
    拓展资料

    多路复用是指以同一传输媒质(线路)承载多路信号进行通信的方式。各路信号在送往传输媒质以前,需按一定的规则进行调制,以利于各路已调信号在媒质中传输,并不致混淆,从而在传到对方时使信号具有足够能量,且可用反调制的方法加以区分、恢复成原信号。

    多路复用常用的方法有频分多路复用和时分多路复用,码分多路复用的应用也在不断扩大。

    多路复用技术的实质是,将一个区域的多个用户数据通过发送多路复用器进行汇集,然后将汇集后的数据通过一个物理线路进行传送,接收多路复用器再对数据进行分离,分发到多个用户。多路复用通常分为频分多路复用、时分多路复用、波分多路复用、码分多址和空分多址。

    有兴趣:传送门

    展开全文
  • 今天给大家带来的是“多路复用技术”,什么是多路复用技术呢?简单的说就是许多单个的信号通过高速线路上的信道同时进行传输,这里两点是要说的。第一,前面所说的高速线路的信道实际上是由一条信道分割出来的多条...

    今天给大家带来的是“多路复用技术”,什么是多路复用技术呢?简单的说就是许多单个的信号通过高速线路上的信道同时进行传输,这里有两点是要说的。第一,前面所说的高速线路的信道实际上是由一条信道分割出来的多条子信道,所以可以有N条信道;第二就是要想实现多路复用技术就需要一个工具:多路复用器。

    拓展:多路复用器的存在意义是为了充分利用通信信道的容量,大大降低系统的成本。例如,对于一对电话线来说,它的通信频带一般在100kHz以上,而每一路电话信号的频带一般限制在4kHz以下。此时,信道的容量远大于一路电话的信息传送量。采用多路复用器,可使多路数据信息共享一路信道。当复用线路上的数据流连续时,这种共享方式可取得良好效果。显然,这样做比每台终端各用一根通信线路传送也更为经济。多路复用器总是成对使用的。一个连续终端,另一个在主机附近,它的作用是将接收的复合数据流,依照信道分离数据,并将它们送到对应的输出线上,故称为解多路复用器。举个例子,现在有一桌子饭,如果只有一个人就只能吃掉十分之一,为了不剩下,就再增加9个人不就能都吃掉了嘛。这一桌子饭就相当于一条频率带宽很大的信道,一个人的食量就相当于一个信号;下图就是多路复用的系统结构图。

    多路复用技术又分为了好多种,这里介绍常见的三种频分多路复用、时分多路复用和波分多路复用。

    1、频分多路复用(FDM):就是在物理信道的频率带宽大大超过单一原始信号所需要的带宽的情况下,可以将物理信道的总带宽分割成多个与传输单个信号带宽相同(略宽)的子信道,每个信道传输一路信号,多路原始信号在频分复用前,先要通过频谱搬移技术将各路信号的频谱搬移到物理信道频谱的不同段上,使各信号的带宽不相互重叠。

    多路原始信号在频分复用前,先要通过频谱搬移技术将各路信号的频谱搬移到物理信道频谱的不同段上,使各信号的带宽不相互重叠

    2、时分多路复用 (TDM):若物理信道能够达到的位传输速率超过各路信号源所要求的的数据传输速率,可以采用时分多路复用技术。时分多路复用TDM是将一条物理信道按时间分为若干时间片轮换地给多个信号使用,每一时间片由复用的一个信号占用,这样可以在一条物理信道上传输多个数字信号。举个例子,一般人一秒钟磕一个瓜子,老王十分之一秒就能磕一个瓜子,因此老王就可以将一秒钟分成10份,同样的一秒钟老王就可以磕10个瓜子.

    总结:

    对于频分复用,频带越宽,则在此频带宽度内所能划分的子信道就越多;对于时分复用,时隙长度越短,则每个时分复用帧中所包含的时隙数就越多,因而所划分的子信道就越多。FDM主要用于模拟信道的复用,TDM主要用于数字信道的复用。

    对于频分多路复用的分配方法,有分为了同步时分多路复用和异步时分多路复用。二者的区别是同步时分多路复用的时隙都是安排好并且固定不变的,即每一个信号源与一个时隙对应,因此在接收端只需要通过时隙序号就可以判断出是那个信号源的数据。但是这种多路复用也有其不足,因为同步时分多路复用有一个原则就是哪怕某个时隙某个信号源没有数据发过来,那么这个时隙宁可空着也不能给别的信号源用。其实大家知道了同步时分多路复用的原理也就基本知道了异步时分多路复用是怎么回事。刚刚说了同步时分多路复用的缺点是如果用户没有数据发送,那么就会浪费带宽,但是异步时分多路复用恰恰避免了这个问题,它可以动态的分配信道的时隙,用户不固定占用某个某个时隙,如果某路数据源没有数据发送,就允许其他数据源占用这个时隙。

    3、波分多路复用(WDM):光分多路复用是指在同一根光纤中同时让两个或两个以上的光波长信号通过不同光信道各自传输信息,其实就是将电的频分多路复用技术用于光纤信道,唯一的区别就是WDM使用光调制解调设备将不同信道的信号调制成不同波长的光,并复用到光纤通道上,在接收端使用波分设备分离出不同的波长的光。

    4、码分多路复用(CDM):码分bai多路复用也是一种共享du信道的方法,每个用户可zhi在同一时间dao使用同样的zhuan频带进行通信,shu但使用的是基于码型的分割信道的方法,即每个用户分配一个地址码,各个码型互不重叠,通信各方之间不会相互干扰,且抗干拢能力强。码分多路复用技术主要用于无线通信系统,特别是移动通信系统。它不仅可以提高通信的话音质量和数据传输的可靠性以及减少干扰对通信的影响,而且增大了通信系统的容量。

     

    总结:FDM是以频段不同来区分不同的信号的,特点是信道不独占,但是时间共享,每一子信道使用的频带不重叠;TDM的特点是独占时隙,但是信道资源共享,每一个子信道使用的时隙不重叠;而CDM的特点是所有子信道在同一时间可以使用整个信道进行数据传输,它在信道与时间资源上均为共享。

     

    展开全文
  • redis IO多路复用技术

    万次阅读 2018-02-04 18:38:22
    redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量。 LINUX IO多路复用原理 在linux下面, 常见5中网络IO方式, 具体可以参考如下的文章, 总结的很清楚, 我们就不再具体介绍: ...

    redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统。 redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量。

    LINUX IO多路复用原理

    在linux下面, 常见的有5中网络IO方式, 具体可以参考如下的文章, 总结的很清楚, 我们就不再具体介绍:
    http://blog.csdn.net/lltaoyy/article/details/54861749

    redis的多路复用, 提供了select, epoll, evport, kqueue几种选择,在编译的时候来选择一种。

    • select是POSIX提供的, 一般的操作系统都有支撑;
    • epoll 是LINUX系统内核提供支持的;
    • evport是Solaris系统内核提供支持的;
    • kqueue是Mac 系统提供支持的;

    我们一般运行的服务器都是LINUX系统上面, 并且我对Solaris和Mac系统不是很了解, 我们这里重点比较一下select、poll和epoll 3种多路复用的差异。

    • select: 单个进程所能打开的最大连接数有FD_SETSIZE宏定义, 其大小为1024或者2048; FD数目剧增后, 会带来性能问题;消息传递从内核到与到用户空间,需要copy数据;

      性能问题:
      (1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
      (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

    • poll: 基本上与select一样, 不通点在于没有FD数目的限制, 因为底层实现不是一个数组, 而是链表;

    • epoll: FD连接数虽然有限制, 但是很大几乎可以认为无限制;epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题; 内核和用户通过共享内存来传递消息;

    LINUX IO多路复用的接口

    select 在LINUX的接口:

    #include <sys/select.h>
    
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
                      fd_set *exceptfds, struct timeval *timeout);
    
    void FD_CLR(int fd, fd_set *set);
    int  FD_ISSET(int fd, fd_set *set);
    void FD_SET(int fd, fd_set *set);
    void FD_ZERO(fd_set *set);

    select 函数的参数:
    - nfds:fd_set的FD的个数, 采用位图的方式记录fd_set集合的FD状态;
    - readfds: fd_set 集合中来监控有哪些读操作没有被block, 如果有可读,select
    - writefds:fd_set 集合中来监控有哪些写操作没有被block;
    - exceptfds: fd_set 集合中来监控有哪些except操作没有被block;
    - timeout: FD z最小被block的时间, 如果timeout的2个field都是0, 会立刻返回, 如果该参数是NULL, 会一直block;
    select如果有一个或者多个读操作, 写操作, except操作不被block, 返回大于1的数值; 若果没有不被block的FD, 但是某些FD block超时, 返回0; 如果错误出现, 返回-1;
    FD_XXX函数, 是添加、删除、清空以及判断fd_set的工具函数。

    select的pseudo 代码:

    while1){
       int ret = select(streams[]);
       if (ret > 0 ) {
          for i in streams[] {
               if i has data {
                  read or write streams[i];
               }
          }        
       } else if (ret == 0) {
          handle timeout FDs;
       }else {
          handle error
       }
    
    }

    epoll的LINUX的接口:

    #include <sys/epoll.h>
    
    //预定义的EVENT
    enum EPOLL_EVENTS                                                                                                                                                                            
      {
        EPOLLIN = 0x001,
    #define EPOLLIN EPOLLIN
        EPOLLPRI = 0x002,
    #define EPOLLPRI EPOLLPRI
        EPOLLOUT = 0x004,
    #define EPOLLOUT EPOLLOUT
        EPOLLRDNORM = 0x040,
    #define EPOLLRDNORM EPOLLRDNORM
        EPOLLRDBAND = 0x080,
    #define EPOLLRDBAND EPOLLRDBAND
        EPOLLWRNORM = 0x100,
    #define EPOLLWRNORM EPOLLWRNORM
        EPOLLWRBAND = 0x200,
    #define EPOLLWRBAND EPOLLWRBAND
        EPOLLMSG = 0x400,
    #define EPOLLMSG EPOLLMSG
        EPOLLERR = 0x008,
    #define EPOLLERR EPOLLERR
        EPOLLHUP = 0x010,
    #define EPOLLHUP EPOLLHUP
        EPOLLRDHUP = 0x2000,
    #define EPOLLRDHUP EPOLLRDHUP
        EPOLLWAKEUP = 1u << 29,
    #define EPOLLWAKEUP EPOLLWAKEUP
        EPOLLONESHOT = 1u << 30,
    #define EPOLLONESHOT EPOLLONESHOT
        EPOLLET = 1u << 31
    #define EPOLLET EPOLLET
      };
    
    int epoll_create(int size);
    //创建epoll对象并回传其描述符。
    
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    //将要交由内核管控的文件描述符加入epoll对象并设置触发条件。
    
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    //等待已注册之事件被触发或计时终了。

    epoll提供edge-triggered及level-triggered模式。在edge-trigger模式中,epoll_wait仅会在新的事件首次被加入epoll 对象时返回;于level-triggered模式下,epoll_wait在事件状态未变更前将不断被触发。
    举例来说,倘若有一个已经于epoll注册之管线接获数据,epoll_wait将返回,并发出数据读取的信号。现假设缓冲器的数据仅有部分被读取并处理,在level-triggered模式下,任何对epoll_wait之调用都将即刻返回,直到缓冲器中的数据全部被读取;然而,在edge-triggered的情境下,epoll_wait仅会于再次接收到新数据(亦即,新数据被写入管线)时返回。

    epoll的实现pseudo 代码

    epollfd = epoll_create()
    
    while1) {
        active_stream[] = epoll_wait(epollfd)
        for (i=0; i < len(active_stream[]); i++) {
            read or write active_stream[i]
        }
    }

    redis 多路复用的应用

    接下来我们看一下, redis的多路复用如何实现的。整个redis的main函数包含如下3部分:
    1、初始化Redis Server参数,这部分代码通过initServerConfig实现。
    2、初始化Redis Server,这部分代码在initServer里面。
    3、启动事件轮询器。

    这里第一部分, 就是通过配置文件的参数来初始化server对象的参数, 和本文的主题没有太大关系这里略过。
    第二部分, 包含了创建轮询器, 以及一个时间event队列, 和file event数组。

    void initServer(void) {
        ...
        server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
        if (server.el == NULL) {
            serverLog(LL_WARNING,
                "Failed creating the event loop. Error message: '%s'",
                strerror(errno));
         }
         ...
             /* Create the timer callback, this is our way to process many background
         * operations incrementally, like clients timeout, eviction of unaccessed
         * expired keys and so forth. */
        if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
            serverPanic("Can't create event loop timers.");
            exit(1);
        }
    
        /* Create an event handler for accepting new connections in TCP and Unix
         * domain sockets. */
        for (j = 0; j < server.ipfd_count; j++) {
            if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
                acceptTcpHandler,NULL) == AE_ERR)
                {
                    serverPanic(
                        "Unrecoverable error creating server.ipfd file event.");
                }
        }
        if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
            acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
    
    
        /* Register a readable event for the pipe used to awake the event loop
         * when a blocked client in a module needs attention. */
        if (aeCreateFileEvent(server.el, server.module_blocked_pipe[0], AE_READABLE,
            moduleBlockedClientPipeReadable,NULL) == AE_ERR) {
                serverPanic(
                    "Error registering the readable event for the module "
                    "blocked clients subsystem.");
        }
    

    第三部分, 是整个event loop部分:

    int main() {
        // 第一部分
        // 第二部分入口
        initServer();
        ...
        aeSetBeforeSleepProc(server.el,beforeSleep);                                                                                                                                   
        aeSetAfterSleepProc(server.el,afterSleep);
        aeMain(server.el);
        aeDeleteEventLoop(server.el);
        return 0;
    }
    
    void aeMain(aeEventLoop *eventLoop) {                                                                                                                                              
        eventLoop->stop = 0;
        while (!eventLoop->stop) {
            if (eventLoop->beforesleep != NULL)
                eventLoop->beforesleep(eventLoop);
            aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
        }
    }
    
     * The function returns the number of events processed. */
    int aeProcessEvents(aeEventLoop *eventLoop, int flags)
    {
             * some event fires. */
            numevents = aeApiPoll(eventLoop, tvp);
    
            /* After sleep callback. */
            if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
                eventLoop->aftersleep(eventLoop);
    
            for (j = 0; j < numevents; j++) {
                aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
                int mask = eventLoop->fired[j].mask;
                int fd = eventLoop->fired[j].fd;
                int rfired = 0;
    
            /* note the fe->mask & mask & ... code: maybe an already processed
                 * event removed an element that fired and we still didn't
                 * processed, so we check if the event is still valid. */
                if (fe->mask & mask & AE_READABLE) {
                    rfired = 1;
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                }
                if (fe->mask & mask & AE_WRITABLE) {
                    if (!rfired || fe->wfileProc != fe->rfileProc)
                        fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                }
                processed++;
            }
        }
        /* Check time events */
        if (flags & AE_TIME_EVENTS)
            processed += processTimeEvents(eventLoop);
    
        return processed; /* return the number of processed file/time events */
    }
    
    static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
        aeApiState *state = eventLoop->apidata;
        struct epoll_event ee = {0}; /* avoid valgrind warning */
        int mask = eventLoop->events[fd].mask & (~delmask);
    
        ee.events = 0;
        if (mask & AE_READABLE) ee.events |= EPOLLIN;
        if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
        ee.data.fd = fd;
        if (mask != AE_NONE) {
            epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
        } else {
            /* Note, Kernel < 2.6.9 requires a non null event pointer even for
             * EPOLL_CTL_DEL. */
            epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
        }       
    }   

    上述的代码比较简单, 没有做过多的说明, 只是把调用关系罗列出来, 以便看清楚redis的整个框架。

    展开全文
  • Java IO多路复用技术详解

    万次阅读 2017-04-28 09:49:58
    服务器端编程经常需要构造高性能的IO模型,常见的IO模型四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型。 (2)同步非阻塞IO(Non-blocking IO):默认创建的...(3)IO多路复用(IO Multiplexi

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

    (1)同步阻塞IO(Blocking IO):即传统的IO模型。

    (2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

    (3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

    (4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

     

    同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

    阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

     

    另外,Richard Stevens 在《Unix 网络编程》卷1中提到的基于信号驱动的IO(Signal Driven IO)模型,由于该模型并不常用,本文不作涉及。接下来,我们详细分析四种常见的IO模型的实现原理。为了方便描述,我们统一使用IO的读操作作为示例。

     

    一、同步阻塞IO

     

    同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。

    图1 同步阻塞IO

    如图1所示,用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。

    用户线程使用同步阻塞IO模型的伪代码描述为:

    {

    read(socket, buffer);

    process(buffer);

    }

    即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。

     

    二、同步非阻塞IO

     

    同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK。这样做用户线程可以在发起IO请求后可以立即返回。

     

    图2 同步非阻塞IO

    如图2所示,由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。

    用户线程使用同步非阻塞IO模型的伪代码描述为:

    {

    while(read(socket, buffer) != SUCCESS)

    ;

    process(buffer);

    }

    即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。

     

    三、IO多路复用

    IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

    图3 多路分离函数select

    如图3所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

    从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

    用户线程使用select函数的伪代码描述为:

    {

    select(socket);

    while(1) {

    sockets = select();

    for(socket in sockets) {

    if(can_read(socket)) {

    read(socket, buffer);

    process(buffer);

    }

    }

    }

    }

    其中while循环前将socket添加到select监视中,然后在while内一直调用select获取被激活的socket,一旦socket可读,便调用read函数将socket中的数据读取出来。

     

    然而,使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

    IO多路复用模型使用了Reactor设计模式实现了这一机制。

    图4 Reactor设计模式

    如图4所示,EventHandler抽象类表示IO事件处理器,它拥有IO文件句柄Handle(通过get_handle获取),以及对Handle的操作handle_event(读/写等)。继承于EventHandler的子类可以对事件处理器的行为进行定制。Reactor类用于管理EventHandler(注册、删除等),并使用handle_events实现事件循环,不断调用同步事件多路分离器(一般是内核)的多路分离函数select,只要某个文件句柄被激活(可读/写等),select就返回(阻塞),handle_events就会调用与文件句柄关联的事件处理器的handle_event进行相关操作。

    图5 IO多路复用

    如图5所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。

    用户线程使用IO多路复用模型的伪代码描述为:

    void UserEventHandler::handle_event() {

    if(can_read(socket)) {

    read(socket, buffer);

    process(buffer);

    }

    }

     

    {

    Reactor.register(new UserEventHandler(socket));

    }

    用户需要重写EventHandler的handle_event函数进行读取数据、处理数据的工作,用户线程只需要将自己的EventHandler注册到Reactor即可。Reactor中handle_events事件循环的伪代码大致如下。

    Reactor::handle_events() {

    while(1) {

    sockets = select();

    for(socket in sockets) {

    get_event_handler(socket).handle_event();

    }

    }

    }

    事件循环不断地调用select获取被激活的socket,然后根据获取socket对应的EventHandler,执行器handle_event函数即可。

    IO多路复用是最常使用的IO模型,但是其异步程度还不够“彻底”,因为它使用了会阻塞线程的select系统调用。因此IO多路复用只能称为异步阻塞IO,而非真正的异步IO。

     

    四、异步IO

     

    “真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

    异步IO模型使用了Proactor设计模式实现了这一机制。

    图6 Proactor设计模式

    如图6,Proactor模式和Reactor模式在结构上比较相似,不过在用户(Client)使用方式上差别较大。Reactor模式中,用户线程通过向Reactor对象注册感兴趣的事件监听,然后事件触发时调用事件处理函数。而Proactor模式中,用户线程将AsynchronousOperation(读/写等)、Proactor以及操作完成时的CompletionHandler注册到AsynchronousOperationProcessor。AsynchronousOperationProcessor使用Facade模式提供了一组异步操作API(读/写等)供用户使用,当用户线程调用异步API后,便继续执行自己的任务。AsynchronousOperationProcessor 会开启独立的内核线程执行异步操作,实现真正的异步。当异步IO操作完成时,AsynchronousOperationProcessor将用户线程与AsynchronousOperation一起注册的Proactor和CompletionHandler取出,然后将CompletionHandler与IO操作的结果数据一起转发给Proactor,Proactor负责回调每一个异步操作的事件完成处理函数handle_event。虽然Proactor模式中每个异步操作都可以绑定一个Proactor对象,但是一般在操作系统中,Proactor被实现为Singleton模式,以便于集中化分发操作完成事件。

    图7 异步IO

    如图7所示,异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步IO。

    用户线程使用异步IO模型的伪代码描述为:

    void UserCompletionHandler::handle_event(buffer) {

    process(buffer);

    }

     

    {

    aio_read(socket, new UserCompletionHandler);

    }

    用户需要重写CompletionHandler的handle_event函数进行处理数据的工作,参数buffer表示Proactor已经准备好的数据,用户线程直接调用内核提供的异步IO API,并将重写的CompletionHandler注册即可。

    相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。Java7之后已经支持了异步IO,感兴趣的读者可以尝试使用。

    展开全文
  • 多路复用技术概述

    千次阅读 2018-07-29 18:51:36
    概述 频分复用(Frequency Division Multiplexing) 时分复用(Time Division Multiplexing) ...数据是在物理链路的信道中传输的,通常一条链路上会有多条信道。在默认情况下,一条信道只传输一路信...
  • Redis I/O 多路复用

    万次阅读 多人点赞 2019-05-19 19:18:01
    为什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回...
  • Java IO多路复用机制详解

    千次阅读 2020-04-03 18:03:42
    1、在Java中,常见的IO模型4种, 同步阻塞IO(BlockingIO):即传统的IO模型。 同步非阻塞IO(Non-blockingIO):默认创建的socket都... IO多路复用(IOMultiplexing):经典的Reactor模式,也称为异步阻塞IO,...
  • IO多路复用
  • IO多路复用机制详解

    万次阅读 多人点赞 2017-12-28 11:42:29
    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型四种: (1)同步阻塞IO(BlockingIO):即传统的IO模型。 (2)同步非阻塞IO(Non-blockingIO):默认创建的...(3)IO多路复用(IO...
  • Redis 和 I/O 多路复用

    千次阅读 2017-04-16 16:21:00
    最近在看 UNIX 网络编程并研究了一下 Redis ...几种 I/O 模型为什么 Redis 中要使用 I/O 多路复用这种技术呢?首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都
  • 一文看懂IO多路复用

    2020-03-22 15:03:45
    本文首发在 技术成长之道 博客,访问 hechen0.com ...目前哪些IO多路复用的方案 具体怎么用 不同IO多路复用方案优缺点 1. 什么是IO多路复用 一句话解释:单线程或单进程同时监测若干个文件描述符是否可以执行IO...
  • 在服务端socket编程中,我们常见的accpet函数、recv函数都是采取的阻塞形式。以recv为例: 当上层应用App调用recv系统调用时,如果对等方没有发送数据(Linux内核缓冲区中没有数据),上层应用Application1将阻塞;当...
  • Redis 和 IO 多路复用

    千次阅读 2018-07-30 23:11:16
    最近在看 UNIX 网络编程并研究了一下 Redis 的实现,感觉 Redis 的源代码...为什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的, 但是由于读写操作...
  • 课程总结了常见的 IO 多路复用机制,主要包括 IO 模型、IO 多路复用接口概览、epoll 细节(触发模式、定时器、惊群问题等),Java 上的多路复用实现,Netty 怎么使用 ...
  • 时分多路复用(TDM)

    千次阅读 2020-10-26 11:57:24
    时分多路复用(TDM)是按传输信号的时间进行分割的,它使不同的信号在不同的时间内传送,将整个传输时间分为许多时间间隔(Slot time,TS,又称为时隙),每个时间片被一路信号占用。TDM就是通过在时间上交叉发送每...
  • Redis技能—底层IO多路复用

    千次阅读 2018-04-23 16:00:13
    这是网上找到的比较全的Reids IO多路复用资料,此部分其实和Redis的键失效机制一定的衔接。最近在看 UNIX 网络编程并研究了一下 Redis 的实现,感觉 Redis 的源代码十分适合阅读和分析,其中 I/O 多路复用...
  • 一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。 用户线程使用IO多路复用模型的伪代码描述为: void ...
  • redis原理,为什么快,io多路复用

    千次阅读 2020-03-21 17:34:18
    很多同学对Redis的单线程和I/O多路复用技术并不是很了解,所以我用简单易懂的语言让大家了解下Redis单线程和I/O多路复用技术的原理,对学好和运用好Redis打下基础。 一、Redis的单线程理解 Redis客户端对服务端的...
  • 音频时分多路复用(TDM)

    千次阅读 2019-10-30 22:33:14
    时分多路复用(TDM)是按传输信号的时间进行分割的,它使不同的信号在不同的时间内传送,将整个传输时间分 为许多时间间隔(Slot time,TS,又称为时隙),每个时间片被一路信号占用。TDM就是通过在时间上交叉发 送...
  • 基于 ID 的 Windows 事件多路复用

    千次阅读 2011-09-05 19:09:47
    Microsoft Windows 提供了通过 WaitForMultipleObjects 方法及其变体对多个事件进行多路复用侦听的功能。这些函数功能强大,但不便于在动态事件列表中使用。 困难在于事件信号用索引 标识在对象句柄数组中。当在该...
  • 址技术与复用技术

    千次阅读 2016-07-27 15:26:47
    址技术: 1、目的是用来区分不同用户的一种技术。...复用技术: 1、目的是让个信息源共同使用同一个物理资源(比如一条物理通道),并且互不干扰; 2、这里的复用是指“个共同使用”的意思;
  • Redis和多路复用模型

    2020-09-01 00:01:10
    为什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,...
  • 最近在看 UNIX 网络编程并研究了一下 Redis 的实现,感觉 Redis 的...为什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,(这句话是问题原因的重...
  • python 协程详解及I/O多路复用,I/O异步

    万次阅读 多人点赞 2018-08-04 18:24:11
    Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力(当然,后来了multiprocessing,可以实现进程并行)。既然在GIL之下,同一时刻只能一个线程在运行,那么对于CPU密集的程序来说,线程...
  • 数字调制与多路复用 比特与代表他们的信号之间的转换过程称为数字调制。 数字调制的解决方案: 基带传输:即信号的传输占有传输介质上从零到最大值之间的全部频率,这是有线介质普遍使用的调制方法。近距离通信的...
  • 多路复用问题汇总 BIO什么缺陷? 针对C10K这样的需求,NIO靠什么解决问题? 多路复用操作系统函数select(…)工作原理? 多路复用操作系统函数select(…)默认监听socket数量为什么是1024? 多路复用操作系统函数...
  • Linux系统编程——I/O多路复用select、poll、epoll

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,104
精华内容 11,241
关键字:

常见的多路复用技术有