• study notes: high performance linux server programming


    1:linux网络API分为:socker地址API,socker基础API,网络信息API

      1,socker地址API:包含IP地址和端口(ip, port)。表示TCP通信的一端。

      2,socker基础API:创建/命名/监听socker,接收/发起链接,读写数据,获取地址信息,检测带外标记和读取/设置socker选项。sys/socket.h

      3,网络信息API:主机名和IP地址的转换,服务名和端口号的转换。netdb.h

    2:socket和API的函数 和 相关知识。

      1,函数。

    1 IP地址转换函数
      <arpa/inet.h>
      in_addr_t inet_addr( const char* strptr );  // 格式转换:十进制字符串IP -> 网络字节序(大端)IP
      int inet_aton( const char* cp, struct in_addr* inp );  // 格式转换:如上,但将数据保存到参数inp中
      char* inet_ntoa( struct in_addr in );  //  格式转换:网络字节序(大端)IP -> 十进制字符串IP
      int inet_pton( int af, const char* src, void* dst ); // 格式转换:字符串IP地址 -> 网络字节序(大端)整数IP
      const char* inet_ntop( int af, const void* src, char* dst, socklen_t cnt );  格式转换:网络字节序(大端)整数IP -> 字符串IP地址
      // inet_ntoa函数不可重入。
      // inet_ntop返回目标储存单元的地址。
    2 创建socket
      <sys/types.h> <sys/socket.h>
      int socket( int domain, int type, int protocol );
      // 1 参数domain:使用的底层协议族。
      // 2 参数type:指定服务类型。SOCK_STREAM(TCP) SOCK_UGRAM(UDP)
      // 3 参数protocol:指定具体协议。一般默认为0.(前面参数已经确定了协议)
      // 4 返回:socket文件描述符。失败-1,设置errno
    3 命名socket
      <sys/types.h> <sys/socket.h>
      int bind( int sockfd, const struct sockaddr* my_addr, socklen_t addrlen );
      // 1 参数addrlen:socket地址长度。
      // 2 返回:成功0,失败-1,设置errno
    4 监听socket
      <sys/socket.h>
      int listen( int sockfd, int backlog );
      // 1 参数sockfd:指定被监听socket
      // 2 参数backlog:监听队列最大长度。典型值:5
    5 接收链接。
      <sys/types.h> <sys/socket.h>
      int accept( int sockfd, struct sockaddr* addr, socklen_t* addrlen );
      // 1 参数sockfd:执行过listen的socket。
      // 2 参数addr:被接受链接的远程socket地址。
      // 3 参数addrlen:地址长度。
      // 4 返回:成功新的链接socket,失败-1设置errno
      // 5 accept只从监听队列取出连接,并不检测网络状态。
    6 发起链接
      <sys/types.h> <sys/socket.h>
      int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );
      // 1 参数sockfd:由socket系统调用返回一个socket
      // 2 参数serv_addr:服务器监听的socket地址。
      // 3 返回:成功0,失败-1设置errno
    7 关闭连接
      <unistd.h>
      int close( int fd );
      // 1 不会立即关闭,只是将引用减一。只有引用为0时才会真正关闭。
      <sys/socket.h>
      int shutdown( int sockfd, int howto );
      // 1 立即关闭连接。
      // 2 参数howto:决定函数的行为。SHUT_RD/WR/RDWR:可以分别控制读写关闭。
      // 3 返回:成功0,失败-1设errno
    8 TCP数据读写。
      <sys/types.h> <sys/socket.h>
      ssize_t recv( int sockfd, void* buf, size_t len, int flags );  // 返回:实际读取到的数据长度。失败-1errno
      ssize_t send( int sockfd, const void* buf, size_t len, int flags );  // 返回:实际写入的数据长度。失败-1reeno
      // 1 参数buf,len:缓冲区的地址和大小。
      // 2 参数flags:数据收发额外控制。通常设0.
    9 UDP数据读写。
      <sys/types.h> <sys/socket.h>
      ssize_t recvfrom( int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen );
      ssize_t sendto( int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t *addrlen );
      // 1 参数src/dest_addr:确定发送/接收端地址。(因UDP没有连接概念)
      // 2 这两个函数同样可以应用于TCP读写数据。
    10 通用数据读写函数。
      <sys/socket.h>
      ssize_t recvmsg( int sockfd, struct msghdr* msg, int flags );
      ssize_t sendmsg( int sockfd, struct msghdr* msg, int flags );
      struct msghdr{
        void* msg_name;             // socket 地址
        socklen_t msg_namelen;    // 地址长度
        struct iovec* msg_iov;      // 分散的内存块
        int msg_iovlen;             // 分散内存块的数量
        void* msg_control;      // 辅助数据地址
        socklen_t msg_controllen;   // 辅助数据大小
        int msg_flags;              // 保存函数flag参数
      }
    11 确定下一个数据是否时带外数据。
      <sys/socket.h>
      int sockatmark( int sockfd );  // 返回:带外1,否则0
    12 获取地址信息。
      <sys/socket.h>
      int getsockname( int sockfd, struct sockaddr* address, socklen_t* address_len );  // 本端
      int getpeername( int sockfd, struct sockaddr* address, socklen_t* address_len );  // 远端
      // 1 返回:成功0,失败-1errno
    13 获取/设置socket选项
      <sys/socket.h>
      int getsockopt( int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len );
      int setsockopt( int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len );
      // 1 参数level:具体协议选项。
      // 2 返回:成功0,失败-1errno。
    
    // 此后为网络信息API
    14 获取主机完整信息。
      <netdb.h>
      struct hostent* gethostbyname( const char* name );  // 通过主机名称获取
      struct hostent* gethostbyaddr( const void* addr, size_t len, int type );  //通过IP地址获取
      // 1 参数type:IP地址的类型。
      // 2 返回:主机信息。
    15 获取服务的完整信息。
      <netdb.h> 
      struct servent* getservbyname( const char* name, const char* proto );  // 通过名字获取
      struct servent* getservbyport( int port, const char* proto );  // 通过端口号获取
      // 1 参数proto:确定服务类型:UDP/TCP
    16 获取主机和服务的综合函数。
      int getaddrinfo( const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result );
      int getnameinfo( const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags );

       2,88-94在完成初版IM时,需要进行测试

    3:高级IO(高编已由函数省略)

      1,函数

    1 两个文件描述符之间直接传递数据
      ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
      // 1 参数count:传输字节数
      // 2 返回:成功返回传输字节数,失败-1errno
      // 3 in_fd:必须时真实文件,out_fd:必须是socket
    2 两个文件描述符之间移动数据。零copy操作
      ssize_t splice( int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags );  // 具体需要再了解和使用。
    3 两个管道文件描述符之间复制数据。零copy操作
      ssize_t tee( int fd_in, int fd_out, size_t len, unsigned int flags );

    4:linux服务程序规范。

      1,函数。

    1 和日志进程通信。
      <syslog.h>
      void syslog( int priority, const char* message );
      // 1 参数priority:设置值与日志级别按位与。
    2 设置日志进程的输出方式。
      void openlog( const char* ident, int logopt, int facility );
      // 1 参数ident:添加到日志消息的日期和时间之后。
      // 2 参数facility:用来修改syslog函数中的默认值。
    3 设置日志进程的掩码。(设置屏蔽信息)
      int setlogmask( int maskpri );
    4 关闭日志功能。
      void closelog();
    5 使进程称为后台进程:
      <unistd.h>
      int daemon( int nochdir, int noclose );
      // 1 参数nochdir:传0,则工作目录为根目录/。否则不变。
      // 2 参数noclose:传0,则被重定向为/dev/null。
      // 3 返回:成功0,失败-1errno。

      2,基本规范。

        1)一般以后台进程形式运行。后台进程又称为守护进程。

        2)通常有一套日志系统。至少能输出日志到文件。

        3)一般以某个专门的非root身份运行。

        4)通常时可配置的。

        5)启动时,会生成一个PID文件并存入/var/run目录中。

        6)通常需要考虑系统资源和限制。

      3,日志进程:rsyslogd。

        1)能够接收 用户/内核 的日志。

      4,ps命令可查看进程、进程组和会话之间的关系。

    5:高性能服务器框架。

      1,函数。

      2,零散细节。

        1)客户连接请求时随即到达的异步事件,服务器需要使用某种IO模型来监听。

        2)服务器可以解构成三个主要模块:IO处理单元,逻辑单元,储存单元。

        3)服务器基本模块的功能描述。

    模块 单个服务器程序 服务器机群
    IO处理单元 处理客户连接,读写网络数据 作为接入服务器,实现负载均衡
    逻辑单元 业务进程或线程 逻辑服务器
    网络储存单元 本地数据库、文件或缓存。 数据库服务器
    请求队列 各单元之间的通信方式。 各服务器之间的永久TCP连接

        4)IO服用函数本身时阻塞的,它们能提高程序效率的原因:它们具有同时监听多个IO事件的能力。

        5)同步IO向应用程序通知的是IO就绪事件,异步IO向应用程序通知的是IO完成事件。

        6)服务器程序通常需要处理三类事件:IO事件,信号和定时事件。

        7)如果程序是计算密集型, 并发没有任何优势。但如果是IO密集型,并发就会有意义。因为IO速度比CPU缓慢。

        8)并发模式中,同步指程序完全按照代码序列顺序执行(用于处理客户逻辑),异步指程序执行需要由系统事件来驱动(用于处理IO)。

        9)主线程向工作线程发送scoket最简单的方式:往管道中写数据。

        10)池是一组资源的集合。这组资源在服务器启动之初就被完全创建好并初始化。常见的有:内存池,线程池,进程池和连接池。

        11)高性能服务器应该避免不必要的数据复制。(避免数据复制,共享内存是最块的方式,但不一定是最好的。

        12)并发程序必须考虑上下文切换的开销。

        13)尽量减少锁的开销。

      3,P2P模式:peer to peer。

        1)P2P模式时,主机之间很难相互发现。

      4,reactoer模式。

        1)它要求主线程(IO处理单元)只负责监听文件描述符是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。

        2)特点:主线程只确定有请求,工作线程针对具体事件类型针对处理。

      5,proactor模式:它将所有IO操作都交给主线程和内核来处理。工作线程仅仅负责业务逻辑。。

      6,半同步/半反应堆模式:主线程用来监听IO,并发送请求到请求队列。然后工作线程竞争抢夺处理权限。

        1)主线程和工作线程共享请求队列。需要锁来保证读写,消耗CPU

        2)每个工作线程同一时间只能处理一个客户请求。

      7,leader/follewer模式:leader监听scoket。当有新请求时,leader去处理请求,并从follower中推选新leader。

        1)需要包含几个组件:句柄集,线程集,事件处理器,具体事件处理器。

    6:IO复用

      1,函数。

    1 epoll系列:linux特有的IO复用函数。
      <sys/epoll.h>
      int epoll_create( int size ); // 创建一个描述符(标识事件表)
      int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event ); // 对 事件表 进行操作(添加,修改,删除注册事件)。
      int epoll_wait( int epfd, struct epoll_event* events, int maxevents, int timeout ); // 等待事件。
    2

      2,零散细节。

        1)IO复用虽然能同时监听多个文件描述符,但它本身是阻塞的。

        2)epoll系列有两种模式:LT和ET。ET是相对高效的模式。

        3)ET模式的文件描述符都应该是非阻塞的。(如果是阻塞的,可能会一直阻塞下去)

        4)EPOLLONESHOT事件:使事件只能触发一次。若需要再次触发,需要重置。可以防止重复读写。

      3,select,poll,epoll的区别

    系统调用 select poll epoll
    事件集合 用户通过三个参数分别传入可读,可写和异常事件。内核通过这些参数在线修改就绪事件。每次调用都会重读 统一处理所有事件类型。需要一个事件集。用户通过pollfd.event传入事件。内核通过修改pollfd.event反馈就绪事件。 内核通过事件表直接管理所有事件。无需反复传入事件。epoll_wait仅用来反馈就绪事件(不需要多余判断)
    应用程序索引就绪文件描述符的时间复杂度 n n

    1

    最大支持文件描述符数 一般有最大值限制 65535 65535
    工作模式 LT LT LT/ET
    内核实现和工作效率 轮询方式检测就绪事件 轮询 回调方式检测就绪事件。

    7:信号。

      1,函数

      2,零散细节。

        1)服务器程序必须处理(至少能忽略)一些常见信号。以避免异常终止。

        2)信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。所以信号需要尽快完成。典型的解决方案:信号仅提供信号,具体处理尽量方在主循环中。

    8:时间堆:将所有定时器超时时间最小的一个定时器的超时值作为心搏间隔。

    9:高性能IO框架库。

      1,常见框架:ACE,ASIO,Libevent。

      2,linux服务器程序必须处理三类事件:IO,信号和定时事件。但需要考虑三个问题

        1)统一事件源。需要统一管理三类事件。一般方法为:利用IO复用系统调用来管理所有事件。

        2)可移植性。

        3)对并发编程的支持。

      3,IO框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组更便于使用的接口。

      4,Libevent框架库。

        1)跨平台支持,拥有统一事件源,线程安全,基于Reactor模式。

        2)官方网站:libevent.org

      5,P240,需要执行代码确认

    10:多线程编程。

      1,线程实现方式:完全在用户空间实现,完全由内核调度和双层调度。

        1)完全在用户空间实现优点:创建和调度线程无需内核的干预,速度很快。创建多各线程对系统性能没有太大影响。但无法在多CPU情况下使用。

        2)内核调度:和用户空间完全相反。

        3)双层调度:上两种模式的混合体。

    11:进程池和线程池。

      1,线程池中的线程数量应该和CPU数差不多。

      2,进程之间(有关联进程)传递数据最简单的方法是管道。

    12:服务器调制,调试和测试。

      1,linux平台的一个优秀特性是内核微调:可以通过修改文件的方式来调正内核参数。

      2,调试方法:tcpdump抓包看数据,或者gdb。

      3,linux对应用程序能打开的最大文件描述符数量有两个层次的限制:用户级限制和系统级限制。

      4,gdb调试多进程的两个简单方法:

        1)找到PID,然后使用attach添加到gdb中。

        2)set follow-fork mode(parent or child)

      5,gdb调试多线程的方法。

        1)info threads:显示当前可调试的所有线程。

        2)threadID:调试ID线程

        3)set scheduler-locking(off|on|step):off所有线程执行,on只有当前线程执行,step单步调试时,只有当前线程会执行。

      6,压力测试:IO复用,多线程,多进程并发编程等方法(不懂- -)。

    13:常用工具。

      1,tcpdump:经典的网络抓包工具

      2,lsof:列出当前系统打开的文件描述符的工具。

      3,nc:用来快速构建网络连接。

        1)服务器方式运行时:监听某个端口并接收客户端连接。

        2)客户端方式运行时:可以向服务器发起连接并收发数据。

        3)所以可以用来调试客户端和服务器。

      4,strace:测试服务器性能的工具。

        1)跟踪程序运行过程中执行的系统调用和接收到的信号,并将系统调用名、参数、返回值及信号名数处到标准输出或指定文件。

      5,netstat:网络信息统计工具。

        1)可以打印本地网卡接口上的全部连接、路由表信息、网卡接口信息。

      6,ifstat:interface statistics。简单的网络流量检测工具。

      7,mpstat:multi-processor statistics。能实时监测多处理器系统上的每个CPU的使用情况。

  • 相关阅读:
    在Spring Bean的生命周期中各方法的执行顺序
    java面试宝典
    js代码中实现页面跳转的几种方式
    APP测试学习:系统资源分析
    APP测试学习:webview性能分析
    APP测试学习:app启动性能分析
    App测试学习:自动遍历测试
    性能测试学习:jmeter通过代理录制、回放请求
    Docker学习五:如何搭建私有仓库
    Docker学习四:容器基本操作
  • 原文地址:https://www.cnblogs.com/zheng39562/p/4275227.html
Copyright © 2020-2023  润新知