• 《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:键盘输入以中断进程。
  • 相关阅读:
    第五篇
    第四篇
    PAT Basic 1094 谷歌的招聘 (20 分)
    PAT Basic 1093 字符串A+B (20 分)
    Dubbo 04 服务化最佳实现流程
    Dubbo 03 Restful风格的API
    Dubbo 02 微信开发
    Dubble 01 架构模型&start project
    PAT Basic 1020 月饼 (25 分)
    PAT Basic 1019 数字黑洞 (20 分)
  • 原文地址:https://www.cnblogs.com/ukernel/p/9191185.html
Copyright © 2020-2023  润新知