zoukankan      html  css  js  c++  java
  • 《Linux高性能服务器编程》阅读笔记

    • bind成功时返回0,失败时返回-1并设置errno。其中,两种常见的errno是EACCES和EADDRINUSE,他们的含义分别是:
      • EACCES:被绑定的地址是受保护的地址,仅有超级用户可以访问
      • EADDRINUSE:被绑定的地址正在使用中。
    • listenbacklog参数表示:处于完全连接状态的socket的上限。
    • accept只是从监听队列中取出连接,而不管连接处于何种状态。(ESSTABLISHED或者CLOSE_WAIT
    • connect失败时返回-1并设置errno。其中,最常见的两种errno是ECONNREFUSEDETIMEDOUT
      • ECONNREFUSED:目标端口不存在,连接被拒绝。
      • ETIMEDOUT:连接超时。
    • close并非立即关闭一个连接,而是将fd的引用数减1。

    重点来了:

    • 对于监听Socket来说,在listen调用前设置这些常用的Socket选项,那么Accept返回的连接Socket将继承这些选项:SO_KEEPALIVESO_LINGERSO_RCVBUFSO_REVLOWATSO_SNDBUFSO_SNDLOWATTCP_NODELAY
    • 对于客户端Socket来说,上述的选项应该在调用Connect函数之前设置,因为Connect调用成功返回之后,TCP三次握手已经完成。
    • 下面我将详细介绍这些套接字选项:
      • SO_REUSEADDR:服务器程序可以通过设置Socket选项来强制使用陷入TIME_WAIT状态的连接占用的Socket Address
      • SO_REUSPORT:允许在一个端口上启动一个服务的多个实例,只要每个实例绑定不同的本地IP地址即可。
      • SO_RCVBUFSO_SNDBUF:表示TCP接受缓冲区与发送缓冲区的大小。TCP接受缓冲区的大小最好设置为MSS(1460)的偶数倍.
      • SO_RCVLOWATSO_SNDLOWAT:表示接受缓冲区与发送缓冲区的低水平位标记。默认情况下,均为1。
      • SO_LINGER:可以通过该选项控制Close系统调用的不同的行为。默认情况下,当我们使用Close系统调用来关闭一个Socket时,Slose立即返回,TCP模块负责将该Socket发送缓冲区中的数据发送给对方。
      • SO_KEPPALIVE:发送周期性保活报文维持连接。
      • TCP_NODELAY:禁止Nagle算法,即在连接上会出现很多的小的数据块,在局域网环境下可以开启。
    • 零拷贝函数:
    #include<sys/sendfile.h>
    ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
    
    • 零拷贝函数:
    #include<fcntl.h>
    ssize_t splice(int in_fd, loff_t* off_in, int out_fd, loff_t* off_out, size_t len, unsigned int flags);
    
    //使用splice实现零拷贝反射服务器
    int pipefd[2];
    int ret = pipe(pipefd);
    ret = splice(connectfd, NULL, pipefd[1], NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
    ret = splice(pipefd[0], NULL, connectfd, NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
    close(connectfd);
    
    • 针对非阻塞IO执行的系统调用总是立即返回,而不管事情是否已经发生。如果事情没有立即发生,这些系统调用就立即返回-1,就和出错返回一样,此时我们必须根据返回时设置的errno来判断具体发生了什么情况。对Accept,Send,Recv而言,事件发生未发生时errno通常被设置为EAGAIN(意味着“再来一次”)或者EWOULDBLOCK(意味着“期望阻塞”);对于Connect而言,errno则被设置为EINPEOCESS(意味着“在处理中”)。
    • IO复用函数本身是阻塞的,他们可以提高程序效率的原因在于他们具有同事监听多个IO事件的能力。
    • 服务器程序通常需要处理三类事件:IO事件定时器事件信号事件
    • 两种事件处理模式:ReactorProator
      • Reactor
        • 主线程往Epoll内核事件表中注册可读事件
        • 主线程等待可读事件发生
        • 可读事件发生时,主线程将可读事件放入请求队列
        • 主线程将睡眠在请求队列中的工作线程唤醒,处理可读事件。处理完毕之后,向Epoll注册可写事件
        • 主线程等待可写事件发生
        • 可写事件发生时,主线程将可写事件放入请求队列
        • 如此往复循环
      • Proator
        • 主线程向内核注册读完成事件,完成时,通过信号通知应用程序
        • 主线程继续处理其他逻辑
        • 主线程收到读完成事件后,将读到的数据封装成一个事件对象送入工作线程。工作线程处理完毕之后,向内核注册读完成事件,并告诉内核用户缓冲区的位置
        • 主线程继续处理其他逻辑
        • 主线程收到读完成事件,做善后处理
        • 如此往复循环
    • 三个IO复用机制
      • select:每次事件发生之后,之前注册事件都会被修改,需要用FD_ISSET进行判断,然后重新注册。
      • poll:同select,使用轮询机制
      • epollepoll对文件描述符的操作有两种模式:LT(电平触发)和ET(边沿触发)。LT模式是默认的工作模式。当设置EPOLLET时,epoll会以ET模式来操作文件描述符,ET是高效工作模式。
        • ET:当epoll_wait检测到其上有事件发生并将此次的事件通知给应用程序后,应用程序必须立刻处理该事件,因为后续的epoll_wait不会再次向应用程序通知这一事件。降低了epoll事件被触发的次数,因此效率高于LT。
        • LT:epoll_wait检测到有事件发生并将此次的事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait的时候,epoll_wait还会再次向应用通知该事件,直到该事件被处理。
        • EPOLLONESHOT:对于注册EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个事件(可读,可写或者异常),并且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。
    #include<sys/select.h>
    int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set*  exceptfds, struct timeval* timeout);
    
    FD_ZERO(fd_set* fdset);
    FD_SET(int fd, fd_set* fdset);
    FD_CLR(int fd,fd_set* fdset);
    int FD_ISSET(int fd,fd_set* fdset);
    
    struct timeval
    {
        long tv_sec;    //秒数
        long tv_usec;    //微秒数
    }
    
    #include<poll.h>
    int poll(struct pollfd* fds, nfds_t nfds, int timeout);
    
    struct pollfd
    {
        int fd;
        short event;    //注册的事件
        short revent;    //实际发生的事件,内核填充,记得每次判断后归零
    }
    
    POLLIN:数据可读
    POLLOUT:数据可写
    POLLREHUP:TCP连接被对方关闭,或者对方关闭了写操作
    POLLERR:错误
    POLLHUP:挂起
    POLLNVAL:文件描述符没有打开
    
    #include<sys/epoll.h>
    int epoll_create(int size);
    int epoll_ctl(int epollfd, int op, int fd, struct epoll_event* event);
    int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
    
    op参数的类型:
    EPOLL_CTL_ADD:向事件注册表中注册fd事件
    EPOLL_CTL_MOD:修改fd上注册的事件
    EPOLL_CTL_DEL:删除fd上注册的事件
    
    struct epoll_event
    {
        _uint32_t events;
        epoll_data_t data;
    };
    
    union epoll_data
    {
        void* ptr;
        int fd;    //指定事件所从属的描述符
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
    
    
    • 服务器端处理信号的方式:
    void sig_handler(int sig)
    {
        int save_errno = errno;
        int msg = sig;
        send(pipefd[1], (char*)&msg, 1, 0);
        errno = save_errno;
    }
    
    void addsig(int sig)
    {
        struct sigaction sa;
        memset(&sa, '', sizeof(sa));
        sa.sa_handleer = sig_handler;
        sa.sa_flags |= SA_RESTART;
        sigfillset(&sa.sa_mask);
        assrt(sigaction(sig, &sa, NULL) != -1);
    }
    
    • 与网络编程相关的信号:
      • SIGHUP:挂起进程的控制终端时,SIGHUP信号会被触发。
      • SIGPIPE:往一个关闭的管道或者Socket中写入数据。默认状态是结束进程。
      • SIGURG:带外数据到达。
      • SIGCHLD:子进程状态发生变化,使用wait进行处理
      • SIGTERM:终止进程。kill命令默认发送该信号。
      • SIGINT:键盘输入以中断进程。
  • 相关阅读:
    CodeForces 659F Polycarp and Hay
    CodeForces 713C Sonya and Problem Wihtout a Legend
    CodeForces 712D Memory and Scores
    CodeForces 689E Mike and Geometry Problem
    CodeForces 675D Tree Construction
    CodeForces 671A Recycling Bottles
    CodeForces 667C Reberland Linguistics
    CodeForces 672D Robin Hood
    CodeForces 675E Trains and Statistic
    CodeForces 676D Theseus and labyrinth
  • 原文地址:https://www.cnblogs.com/ukernel/p/9191185.html
Copyright © 2011-2022 走看看