学习笔记_网络编程中关注的问题

学习笔记_网络编程中关注的问题

概念

全连接队列(sync queue)和半连接队列(accept queue)

当服务端接收到客户端第一次SYN握手请求时,将创建的request_sock结构,存储在半连接队列中(向客户端发送SYN+ACK,并期待客户端响应ACK),此时的连接在服务器端出于SYN_RECV状态。当服务端收到客户端最后的ACK确认时,将半连接中的相应条目删除,然后将相应的连接放入 全连接队列中, 此时服务端连接状态为ESTABLISHED。 进入全连接队列中的连接等待accept()调用取用
accept如果全连接满了, 半连接会报错refuse

Reactor中网络关注的问题

连接的建立

客户端连接服务器, 服务器连接第三方服务(redis, memcahched, )
客户端–>服务器–>第三方服务
int clientfd = accept(listenfd, addr, sz);
- clientfd = -1,且errno == EWOULDBLOCK,说明全连接队列为空
listen(listenfd, backllog);
- backllog, 全连接队列的长度

int connectfd = socket(AF_INET, SOCK_STREAM, 0); 
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
- errno == ,正在建立连接
- errno == EISCONN, 连接建立成功

连接的断开

TCP是全双工, 有读端和写段两条
// 主动关闭 close(fd);
shutdown(fd, SHUT_RDWR);
// 主动关闭本地读端,对端写段关闭
shutdown(fd, SHUT_RD);
// 主动关闭本地写端,对端读段关闭
shutdown(fd, SHUT_WR);

// 被动:读端关闭 
int n = read(fd, buf, sz); 
if (n == 0) { 
    close_read(fd); 
    //半关闭状态,之后还可以写数据
    write()..
    // close(fd); 
}

// 被动:写端关闭 
int n = write(fd, buf, sz); 
if (n == -1 && errno == EPIPE) {
    close_write(fd); 
    // close(fd); 
}

消息的到达

                                用户态
----------------------------------
|读缓冲区| |写缓冲区|            内核态
int n = readd(fd, buf, sz);
- 将数据从内核态的读缓冲区中读出
- 正向的错误,不需要关闭fd
    - n = -1, errno = EWOULDBLOCK, 读缓冲区为空
    - n = -1, errno = EINRT, 被信号打断
- 其他的n = -1时需要关闭fd

消息发送完毕

                                用户态
----------------------------------
|读缓冲区| |写缓冲区|            内核态
int n = write(fd, buf, dz); 
if (n == -1) { 
    if (errno == EINTR || errno == EWOULDBLOCK) { 
        return; 
    }
    close(fd); 
}
- 将数据从用户态写到内核态的写缓冲区中
- n = -1, errno = EWOULDBLOCK, 写缓冲区已经满了, 只能等空了再去写
- n = -1, errno = EINRT, 被信号打断
- n = sz, 写入成功

网络IO职责

检测IO

io 函数本身可以检测 io 的状态;但是只能检测一个 fd 对应的状态;io 多路复用可以同时检测多个io的状态;
区别是:io函数可以检测具体状态;io 多路复用只能检测出可读、可写、错误、断开等笼统的事件;
io多路复用没告诉是具体的错误, 可以通过getsockopt查看具体的错误

操作IO, 阻塞IO和非阻塞IO

  • 阻塞在网络线程;
  • 连接的 fd 阻塞属性决定了 io 函数是否阻塞;
  • 具体差异在:io 函数在数据未到达时是否立刻返回;

accept阻塞则说明全链接队列中没有数据

// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

IO多路复用

只负责检测IO,不负责操作IO
数据准备,就绪队列是否准备好
epoll_wait(epfd, evs, sz, timeout);
- timeout = -1,永久阻塞
- timeout = 0, 非阻塞, 立刻返回
- timeout > 0,设置超时时间

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 epollet
    epoll_data_t data; // 保存 关联数据 
};
typedef union epoll_data { 
    void *ptr; int fd; 
    uint32_t u32; 
    uint64_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); 
    /** 
    events[i].events: 
        EPOLLIN 触发读事件 
        EPOLLOUT 触发写事件 
        EPOLLERR 连接发生错误 
        EPOLLRDHUP 连接读端关闭 
        EPOLLHUP 连接双端关闭 
    */
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
- 通常设置512
  • 需要主要的是客户端对应的连接事件是写事件,服务端对应的是读事件
  • 需要注意的epoll事件
    • EPOLLIN,可读事件
      当一个连接需要建立的时候,客户端会发送syn+seq number,此时也代表收到了数据
    • EPOLLOUT,可写事件
    • EPOLLRDHUP,读端关闭事件
    • EPOLLRDHUP,读写端都关闭事件
  • 需要注意的errno错误码
    • EWOULDBLOCK,操作将被阻塞
      全连接队列满了,写缓存区满了,读缓存区为空
    • EINTR
      表示收到了终端信号,对于程序来说应该这个是非致命的

epoll连接的建立

//一、处理客户端的连接
//1.注册监听listenfd的读事件
struct epoll_eventev;
ev.events|= EPOLLIN;
epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&ev);
//2.当触发listenfd的读事件,调用accept接收新的连接
int clientfd = accept(listenfd,addr,sz);
struct epoll_event ev;
ev.events|= EPOLLIN;
epoll_ctl(efd,EPOLL_CTL_ADD,clientfd,&ev);

//二、处理连接第三方服务
//1.创建socket建立连接
int connectfd=socket(AF_INET,SOCK_STREAM,0);
connect(connectfd,(struct sockaddr*)&addr,sizeof(addr));
//2.注册监听connectfd的写事件
struct epoll_event ev;
ev.events|= EPOLLOUT;
epoll_ctl(efd,EPOLL_CTL_ADD,connectfd,&ev);
//3.当connectfd写事件被触发,连接建立成功
if(status==e_connecting&&e->events&EPOLLOUT){
    status==e_connected;
}

epoll连接的断开

// 表示读关闭的标志
f(e->events&EPOLLRDHUP){
    //读端关闭
    (fd);
    //close(fd);
}
// EPOLLHUP 表示读写都关闭
if(e->events&EPOLLHUP){
    //读写端都关闭
    close(fd);
}

epoll数据到达

if(e->events&EPOLLIN){
    intn=read(fd,buf,sz);
    if(n < 0) {
        if(errno==EINTR)
            continue;
        if(errno==EWOULDBLOCK)
            break;
        close(fd);
    } else if(n==0){
        close_read(fd);    
        //close(fd);
    }
}

epoll_数据发送完毕

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); 
        }
    }

编程

对于客户端, 单ack包发送出去后说明连接建立成功,所以connect要注册写事件
对于reactor中为什么要用非阻塞IO
触发了读事件,为什么缓冲区中没数据
可能数据到达后读事件已经被取消掉了

为什么新的连接不注册写事件
因为新连接的写缓冲区一定是空, 注册了写事件之后会立即触发写事件
一般write函数直接调用就可以了, 当write失败之后, ret = -1, errno = EWOULDBLOCK才去注册写事件, 写事件触发后再尝试写, 写完之后再将写事件删除

Reactor应用

组成:IO多路复用+非阻塞IO
事件方式通知,事件循环
将对IO的处理转换成对事件的处理

单reactor-redis

水平触发
mask用来映射红黑树中节点的状态
aeFiredEvent-event的封装
aeEventLoop-reactor的封装
acCreateEventLoop-创建reactor

为什么要把read放在网络线程中,写操作放在多线程中?
数据错乱的问题? 写入顺序没有问题,子需要加一个cas锁
为什么不在多线程读?会有数据错乱的问题, 一个线程读了一半,另外一个线程读一半

多reactor-memcached

水平触发
同过epoll_creat创建多个epoll
利用一个reactor专门处理accept,来保证连接的时效

nginx

边缘触发
做反向代理的,将一条数据转发到不同的后端服务器中
listen之后fork

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值