zoukankan      html  css  js  c++  java
  • Linux网络编程II

    1.TCP通信并发。使用多线程或多进程。
    1. 一个父进程,多个子进程
    2. 父进程负责等待并接受客户端的连接
    3. 子进程:完成通信,接收一个客户端连接就创建一个子进程用于通信
    ps: 回收子进程使用SIGCHILD信号,使用sigaction()信号捕捉函数,使用waitpid()非阻塞回收子进程资源
     
    2.TCP状态转换。
     
    3.半关闭
    • 当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据。
    #include <sys/socket.h>
    int shutdown(int sockfd, int how);
        - sockfd: 需要关闭的socket的描述符
        - how: 允许shutdown操作选择以下几种方式:
                - SHUT_RD(0): 关闭sockfd上的读功能,该套接字不再接收数据,任何在套接字接收缓冲区的数据都将被丢弃;
                - SHUT_WR(1): 关闭sockfd的写功能,进程不能对此套接字发出写操作;
                - SHUT_RDWR(2): 关闭读写功能,相当于调用shut down两次,首先是SHUT_RD,然后是SHUT_WR.
     
    1. 使用close(),如果多个进程共享一个套接字,close每调用一次,计数减1,直到计数为0,也就是所有进程都调用了close,套接字被释放。
    2. 使用shutdown(),在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其他进程将无法通信。但如果一个进程close(sfd)将不会影响其他进程。
    
    4.端口复用。
    • 场景:服务器先关闭,会进入FIN_WAIT_2状态,客户端关闭,服务器会进入TIME_WAIT状态,此时服务器的端口还未被释放,不能被重新使用。
    • 程序突然退出而系统没有释放端口
    // 设置套接字的属性,包括端口复用
    #include <sts/types.h>
    #include <sys/socket.h>
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
        - sockfd: 要操作的文件描述符
        - level: 级别 - SOL_SOCKET(端口复用)
        - optname: 选项的名称
                - SO_REUSEADDR
                - SO_REUSEPORT
        - optval: 端口复用的值(整型)
                - 1: 可以复用
                - 0: 不可以复用
        - optlen: optval参数的大小,sizeof(optval)
    

     

    5.IO多路复用(多路转接)
    • IO:对缓冲区的操作
    • IO多路复用使程序能同时监听多个文件描述符,能够提高程序的性能,Linux下实现多路复用的系统调用主要有select、poll、epoll。
     
    6.IO模型
    • BIO模型(Blocking)
    • NIO模型(Nonblocking)
    • IO多路转接技术
      • select、poll
      • epoll
     
    7.IO多路转接技术:select
    • 首先构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
    • 调用一个系统函数(select),监听该列表中的文件描述符,直到这些描述符中一个或多个进行了IO操作时,该函数才返回。
      • 该函数是阻塞的
      • 函数对文件描述符的检测操作是由内核完成的
    • 在返回时,该函数会告诉进程有多少(哪些)描述符要进行IO操作。
    • 缺点:
      • 每次调用select,都需要把fd集合从用户区拷贝到内核区,这个开销在fd比较多的时候会很大
      • 每次调用select都需要在内核遍历传递进来的所有fd(O(n)时间复杂度),这个开销也很大
      • select支持的文件描述符数量少,默认只有1024(fd_set底层是二进制位形式表示,fd集合用128个字节表示,所有默认表示的是1024位代表1024个文件描述符)
      • fds集合不能重用,每次需要重置(需要有一个fds集合专门记录需要检测的文件描述符,另外每次需要一个临时的fds记录从内核返回来的结果)
    #include <sys/times.h>
    #include <sys/types.h>
    #include <unistd.h>
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);    // sizeof(fd_set) = 128 byte = 1024 bit
        - nfds: 委托内核检测的最大文件描述符+1
        - readfds:  要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读属性,
                    - 对应的是对方发送过来的数据,
                    - 为读是被动接收数据,检测的就是读缓冲区
                    - 是一个传入传出参数
        - writefds: 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写属性
                    - 委托内核检测写缓冲区是否还可以写数据
        - exceptefds: 检测发生异常的文件描述符的集合
        - timeout: 设置的超时时间, NULL 永久阻塞,直到检测到了文件描述符有变化;tv_sec = 0 tv_usec = 0 不阻塞;tv_sec > 0 tv_usec > 0 阻塞对应时间
        - 返回值:失败返回-1;成功返回>0(n)的数,表示检测的集合中有n个文件描述符发生了变化
    struct timeval {
        long tv_sec;
        long tv_usec;
    }
     
    void FD_CLR(int fd, fd_set *set);
        - 作用:将参数文件描述符fd对应的标志位设置为0
     
    void FD_ISSET(int fd, fd_set *set);
        - 作用:判断fd对应的标志位是0还是1,
        - 返回值:fd对应的标志位的值
     
    void FD_SET(int fd, fd_set *set);
        - 作用:将参数文件描述符fd对应的标志位设置为1
     
    void FD_ZERO(fd_set *set);
        - 作用:fd_set一共有1024位,初始化所有位为0
     
     
    8.IO多路转接技术:poll
    • 改进了select的第3和第4点缺点,用结构体pollfd取代了select的fd_set数据结构,pollfd可以保存需要检测事件以及内核返回的结果。
    • 缺点:没有改进select的第1和第2点缺点
      • 每次调用也需要把fd集合从用户区拷贝到内核区,这个开销在fd比较多的时候会很大
      • 每次调用需要在内核遍历传递进来的所有fd(O(n)时间复杂度),这个开销在fd比较多的时候也很大
      • poll调用的返回的内核结果只告知了有几个文件描述符发生了改变,并没有告知是具体哪几个,依然需要程序遍历找出(O(n))
    #include <poll.h>
    struct pollfd {
        int fd;                // 委托内核检测的文件描述符
        short events;          // 委托内核检测文件描述符的什么事件 POLLIN 读  POLLOUT 写
        short revents;         // 内核返回的文件描述符发生的事件
    };
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
        - fds: 需要检测的文件描述符的集合
        - nfds: 委托内核检测的最大文件描述符+1
        - timeout: 阻塞时长, NULL 永久阻塞,-1 阻塞,当检测到需要检测的文件描述符发生变化时解除阻塞;0 不阻塞;>0 阻塞时长
        - 返回值:失败返回-1;成功返回>0(n)的数,表示检测的集合中有n个文件描述符发生了变化
     
    

      

    9.IO多路转接技术:epoll
    • 监控多个文件描述符,检测其中是否有可以进行IO操作的( monitoring multiple file descriptors to see if I/O is possible on any of them.)
    • 底层实现是红黑树和双链表,红黑树记录需要检测的文件描述符,遍历复杂度O(logn);双链表记录改变了的文件描述符,返回给程序后避免了再次遍历查找具体改变的文件描述符
    #include <sys/epoll.h>
    int epoll_create(int size);
        - 作用:创建一个新的epoll实例。在内核中创建一个数据,包括需要检测的文件描述符(RBT),和就绪列表存放检测到数据发生改变的文件描述符信息(双链表)
        - size: >0, 无意义;以前底层hashmap实现时需要
        - 返回值:成功返回文件描述符,操作epoll实例;失败返回-1并设置errno
     
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
        - epfd: epoll实例对应的文件描述符
        - op: 要进行什么操作,EPOLL_CTL_ADD 添加;EPOLL_CTL_MOD 修改;EPOLL_CTL_DEL 删除
        - fd: 要检测的文件描述符
        - event: 检测文件描述符什么事件,常见的epoll检测事件:EPOLLIN、EPOLLOUT、EPOLLERR
     
    int epoll_wait(nt epfd, struct epoll_event *events, int maxevents, int timeout);
        - epfd: epoll实例对应的文件描述符
        - events: 传出参数,保存了发生了变化的文件描述符的信息
        - maxevents: 第二参数结构体数组的大小
        - timeout: 阻塞时长, NULL 永久阻塞,-1 阻塞,当检测到需要检测的文件描述符发生变化时解除阻塞;0 不阻塞;>0 阻塞时长
        - 返回值:失败返回-1;成功返回>0(n)的数,表示检测的集合中有n个文件描述符发生了变化
    

      

    10.epoll的工作模式
    • LT模式(水平触发)
      • 缺省的工作模式,同时支持阻塞和非阻塞socket。
      • 内核告诉你一个文件描述符是否就绪,然后程序可以对该就绪的fd进行IO操作。如果不进行任何操作,内核会继续通知程序。
      • 过程:
        • 假设委托内核检测读事件 -> 检测fd的读缓冲区
        • 读缓冲区有数据 -> epoll检测到了会给用户通知
          • a. 用户不读数据 -> 数据一直在缓冲区,epoll会一直通知
          • b. 用户只读了一部分数据 -> epoll会继续通知
          • c. 缓冲区的数据读完了 -> 不通知
    • ET模式(边沿触发)
      • 高速的工作模式,支持非阻塞socket。
      • 当描述符从从未就绪变为就绪时,内核会通知程序,然后内核会假设程序已经知道文件描述符就绪了,将不再为那个文件描述发送更多的就需通知。直到缓冲区有新数据有新数据到达或由空变为非空的时候,将会再次触发内核通知程序。
      • ET模式从很大程度上减少了epoll时间被重复处罚的次数,效率比LT模式高。
      • ET模式下epoll工作必须使用非阻塞套接字接口,并且需要结合循环读取数据的方式,以避免由于一个文件描述符的阻塞读/写操作把处理多个文件描述符的任务饿死。
      • 过程:
        • 假设委托内核检测读事件 -> 检测fd的读缓冲区
        • 读缓冲区有数据 -> epoll检测到了会给用户通知
          • a. 用户不读数据 -> 数据一直在缓冲区,epoll下次检测的时候不通知
          • b. 用户只读了一部分数据 -> epoll不通知
          • c. 缓冲区的数据读完了 -> 不通知,直到缓冲区有新的数据写入才通知
     
    11.LT和ET模式下对读写操作是否就绪的判断
    • 水平触发
      • 读操作:只要缓冲内容不为空,LT模式返回读就绪。
      • 写操作:只要缓冲区还不满,LT模式返回写就绪。
    • 边沿触发
      • 读操作:1)缓冲区由空变为非空;2)缓冲区有新数据到达时;3)缓冲区有数据可读,且文件描述符进行EPOLL_CTL_MOD修改EPOLLIN事件时。
      • 写操作:1)缓冲区由非空变为空;2)缓冲区有数据被发走时,即缓冲区内容减少时;3)缓冲区有空间可写,且文件描述符进行EPOLL_CTL_MOD修改EPOLLOUT事件时。
     
    12. 使用epoll模型,水平(LT)触发模式,当socket可写时,会不停的触发socket可写的事件,如何处理?
    • 开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。
     
     
     
     
     
     
  • 相关阅读:
    Spring基础知识
    Hibernate基础知识
    Struts2基础知识
    在eclipse里头用checkstyle检查项目出现 File contains tab characters (this is the first instance)原因
    java后台获取cookie里面值得方法
    ckplayer 中的style.swf 中的 style.xml 中的修改方法
    java hql case when 的用法
    Windows下Mongodb安装及配置
    Mongodb中经常出现的错误(汇总)child process failed, exited with error number
    Mac 安装mongodb
  • 原文地址:https://www.cnblogs.com/tristatl/p/15120692.html
Copyright © 2011-2022 走看看