redis、memcached、nginx网络组件

reactor的应用

  1. 梳理reactor网络编程
  2. 编程细节,一些返回值和错误码
  3. redis、nginx、memcached、reactor具体使用

网络编程关注的问题:

  1. 连接的建立
    1. 客户端连接服务器
      1. 三次握手首先发送connect包(半连接队列),服务器发送ack,客户端发送同步这样连接就建立了(全连接队列)int clientfd = accept(listenfd, addr, sz); 根据addr和sz可以获取到客户端的ip地址(可以用来做黑白名单)
      2. 如果clientfd==-1&&errno==EWOULDBLOCK说明全连接队列为空。Listen(listenfd, backllog) backllog指的是全连接队列大小(linux下,mac系统可能是半连接队列和全连接队列长度之和)accept既能够检测全连接队列是否有数据,同时可以取出clientfd
    2. 服务器连接第三方服务(redis,memcat等)
      1. Int ret = connect(connectfd,(struct sockaddr*)&addr,sizeof(addr));
      2. Errno = EINPROFRESS(正在建立连接)、EISCONN(建立成功)
  2. 连接的断开
    1. 主动断开
      1. 主动调用close/shutdown
      2. 一条连接有R和W(全双工),断开连接有两种状态读断、写断、都断
      3. 四次挥手
    1. 被动断开
      1. Int n = read(fd, buf, sz)为0,读端被动关闭,有的时候网络编程需要支持半关闭状态,这时还可以write
      2. 关闭写端write = -1 && errno = EPIPE
  3. 消息的到达
    1. 有两个状态:用户态和内核态
    2. 一条连接有两个缓冲区:读缓冲区和写缓冲区(内核态)
    3. Int n =  read(fd, buf, sz),从内核态的读缓冲区读到用户态,sz:预期读到的数据,n为实际读出的数据个数,n≤sz
    4. 如果n=-1
      1. 读缓冲区为空EWOULDBLOCK
      2. EINTR被信号打断(前两个是正向错误,下一次还可以继续读)
      3. 其他的错误直接close
  4. 消息发送完毕
    1. Int n = write(fd, buf, sz); 把用户态的缓冲区写到内核态缓冲区中。
    2. n = -1
      1. EINTR信号打断(下一次继续写)
      2. EWOULDBLOCK写缓冲区满(只能等到空闲的时候再写)
    3. n = sz

网络IO职责

  1. 检测IO
    1. 检测fd对应状态,io多路复用可以检测多个io状态,io函数可以检测具体状态,io多路复用只能检测出可读、可写等笼统事件。 可以通过io函数和getsockopt检测具体的错误。
    2. 操作io
      1. 阻塞io
      2. 非阻塞io
      3. 区别:阻塞在网络线程;连接的fd阻塞属性决定了io函数是否阻塞;差异在:io函数在数据未到时是否立刻返回。
      4. 默认情况下时阻塞的,设置非阻塞方法:int flag = fcntl(fd, F_SETFL, flag | O_NONBLOCK);
      5. 数据准备阶段:read阻塞,数据准备好后将数据拷贝到用户态(数据拷贝阶段):read仍阻塞
      6. 数据准备阶段:read立刻返回erro=EWOULDBLOCK,数据准备好后将数据拷贝到用户态:read阻塞。
      7. 所以阻塞io在数据拷贝阶段都是阻塞的,区别在于数据准备阶段是否立刻返回
    3. io多路复用
      1. 只负责检测io,不负责操作io
      2. 都会携带一个timeout
      3. 数据准备阶段io多路复用都是阻塞的(就绪队列是否准备好)
      4. Timeout=-1是永久阻塞
      5. timeout = 0和前面的非阻塞io一样
      6. Epoll_wait(epfd, evs, sz, timeout)
      7. Time_out=具体的数值,则数据阻塞的事件为具体的数据
    4. Epoll
      1. Struct eventpoll {
            Struct rb_root rbr; //管理epoll监听的事件
        
            Struct list_head rdllist; //保存着epoll_wait返回满足条件的事件
        
        };
        
        
        
        Struct epitem {
        
            Struct rb_node rbn; //红黑树节点
        
            Struct list_head rdllist;//双向链表节点
        
            struct epoll_filefd ffd;//事件句柄信息
        
            Struct eventpoll *ep;//指向所属的eventpoll对象
        
            Struct epoll_event event;//注册的事件类型
        
        };
        
        struct epoll_event {
        
            __uint32_t events;//epollin epollout epollel
        
            Epoll_data_t data;//保存关联数据
        
        };
        
        typedef union epoll_data {
        
            Void *ptr;
        
            Int fd;
        
            Uint32_t u32;
        
            Unit64_t u64;
        
        } epoll_data_t;
        
        
        
        Int epoll_create(int size);
        
        /**
        
        Op:
        
        EPOLL_CTL_ADD
        
        EPOLL_CTL_MOD
        
        EPOLL_CTL_DEL
        
        
        
        Event.events:
        
        EPOLLIN//读事件
        
        EPOLLOUT//写事件
        
        EPOLLET//边缘触发模式,默认时水平触发
        
        **/
        
        Int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
        
        /**
        
        Event[i].events:
        
        EPOOLLIN
        
        EPOLLOUT
        
        EPOLLERR
        
        EPOLLRDHUP
        
        EPOLLHUP
        
        */
        
        Int epoll_wait(int epfd, stuct epoll_event* events, int maxevents, int timeout);

Epoll_create会创建红黑树和就绪队列(创建一个epoll对象)。调用epoll_ctl添加epoll中的事件会与网卡驱动程序建立回调关系,通过epoll_ctl调用回调关系,把红黑树节点拷贝到就绪队列中(用户态events),maxevents是预期要读的个数通常设为512。backlog的值一般不会很大(一个事件循环)超出会refused,epoll不一定只检测listenfd。连接fd也可以监测

  1. epoll编程
    1. 连接建立
      1. 客户端连接
        1. 注册监听listenfd的读事件
Struct epoll_evenet ev;

Ev.events |= EPOLLIN;

Epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev);
  1. 当触发listenfd的读事件,调用accept接收新的连接
Int clientfd = accept(listenfd, addr, sz);

Struct epoll_event ev;

Ev.evnets |= EPOLLIN;//不能监听写事件,最开始写缓冲区是空的,写事件会一直被触发。

Epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev);
  1. 处理连接第三方服务
    1. 创建socket建立连接
Int connectfd = socket(AF_INET, SOCK_STREAM, 0);

Connect(connectfd, (struct sockaddr*)&addr, sizeof(addr));
  1. 注册监听connectfd的写事件(服务器作为客户端,要发送同步建立连接包,所以要注册可写事件)
struct epoll_event ev;

Ev.events |= EPOLLOUT;

Epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev);
  1. 当connectfd写事件被触发,连接建立成功
If(status == e_connecting && e->events & EPOLLOUT) {

    Status = e_connected;

}
  1. 连接断开
If(e->events & EPOLLRDHUP) {

    //读端关闭, 客户端写端关闭,做读端关闭的对应逻辑,通常就是close(fd)

    Close_read(fd);

}

If(e->events & EPOLLHUP) {

    //读写端都关闭
    close(fd);

}
  1. 数据到达

//reactor要用非阻塞io

If(e->events & EPOLLIN) {

    Int n = read(fd, buf, sz);

    If(n < 0) {

        If(errno == EINTR)//被信号打断,如果是边缘触发所以要continue

            Continue;

        If(errno == EWOULDBLOCK)//读缓冲区米有数据

            Break;
    
        Close(fd);

    } else if (n==0) {//读端关闭

        Close_read(fd);//close(fd)

    }

    //业务逻辑

}
  1. 数据发送完毕
Int n = write(fd, buf, dz);

If(n == -1) {

    If (errno == EINTR)

        Continue;

    If(errno == EWOULDBLOCK) {

        Struct epoll_event ev;

        Ev.events = EPOLLOUT;

        Epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

    }

    Close(fd);

}

If(e->events & EPOLLOUT) {
    
    Int n = write(fd, buf, sz);

    If(n > 0) {

        Epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);

    }

}

//当写缓冲区不够的时候,会写失败,进行写注册再次尝试写

reactor的应用(组成:io多路复用+非阻塞io,事件方式通知 事件循环)

将对io的处理转化为对事件的处理

While(1) {

    Int n = epoll_wait();

    For(int I = 0; I < n; I++) {

        If(e->events & )

    }

}
  1. 单reactor(redis)

只有一个io多路复用,一个epoll对象管理,一个reactor有一个acceptor,read,write。读事件要先进行decode解密之后进行对应的业务逻辑之后将结果encode压缩成字节流发送到对端去。

Ae_epoll.c

Ae_evport.c                 ae.c                  anet.c             connection.h

Ae_kqueue.c    ➡   ae.h                 anet.h             connection.c      networking.c

Ae_select.c

io多路复用                  io检测                   io操作                                                       协议处理(redis协议)

redis、memcached、nginx网络组件_第1张图片

Redis6.0支持了io多线程就是在networking中封装的

首先在ae.c中会根据HAVE_EVPORT判断到底使用哪一个io复用(linux用aeepoll,mac用aekqueue都不是用select)

redis事件封装:ae.h中aeFileEvent(reactor中先注册事件,事件触发回调函数)mask用来对应红黑树节点状态,rfileProc,wfileProc读写事件的回调

aeFireEvent事件循环中取出来的事件

aeEventLoop代表reactor的封装。maxfd是因为select的原因。setsize为maxevent大小

在ae_epoll.c中使用aeApiCreate创建epoll对象,aeDeleteEvenetLoop删除事件,arStop停止事件。aeProcessEvents事件循环

你可能感兴趣的:(零声教育上课笔记,网络,redis,memcached)