Linux-12


  1. I/O多路复用的功能:使得程序能同时监听多个文件描述符,能够提高程序的性能.

  2. select

  操作步骤:

  • 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
  • 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O
    操作时,该函数才返回。
    • 这个函数是阻塞
    • 函数对文件描述符的检测的操作是由内核完成的
  • 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    - 参数
        - nfds: 委托内核检测的最大文件描述符的值+1
        - readfds: 要检测读的文件描述符表,一般是检测是否有新数据到达(读缓冲区),传入传出参数
        - writefds: 要检测写的文件描述符表,其实就是让内核去检测描述符表是否还可以继续写数据(未满就可以写)
        - exceptfds: 检测异常的文件符表
        - timeout: 设置超时时间
            struct timeval {
                long tv_sec; /* seconds */
                long tv_usec; /* microseconds */
            };
            - NULL:一直阻塞直至有改变
            - 均为0则不阻塞
            - 总的大于0则阻塞对应的时间
    - 返回值:-1失败,大于0则表示发生变化的文件描述符表的数量

// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);
// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);
// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);
  1. poll
#include <poll.h>
struct pollfd {
int fd; /* 委托内核检测的文件描述符 */
short events; /* 委托内核检测文件描述符的什么事件 */
short revents; /* 文件描述符实际发生的事件 */
};
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    - 参数:
        - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
        - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
        - timeout : 阻塞时长,int类型,单位为毫秒
            0 : 不阻塞
            -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
            >0 : 阻塞的时长
    - 返回值:
        -1 : 失败
        >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
  1. epoll
#include <sys/epoll.h>

// 创建epoll实例,其中包含文件描述符的信息(存储在红黑树中)和就绪列表,存放检测到数据改变的文件描述符(用双向列表存储)
int epoll_create(int size);
    - 参数:size无意义,大于0即可
    - 返回值,-1则失败,>0则为文件描述符

// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
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: 要检测文件描述符的操作(读写),EPOLLIN,EPOLLOUT,EPOLLERR
            struct epoll_event {
                uint32_t events; /* Epoll events */
                epoll_data_t data; /* User data variable */
            };
            typedef union epoll_data {
                void *ptr;
                int fd;
                uint32_t u32;
                uint64_t u64;
            } epoll_data_t;

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    - 参数:
        - epfd: epoll实例的文件描述符
        - events: 传出参数,保存发生变化的文件描述符信息
        - maxenvents: 上一个参数的数组大小
        - timeout: 0不阻塞,-1阻塞到有改变,>0阻塞时间(毫秒)
    - 返回值:失败返回-1,成功返回发生变化的描述符个数
  1. UDP
#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
    - 参数:
        - sockfd : 通信的fd
        - buf : 要发送的数据
        - len : 发送数据的长度
        - flags : 0
        - dest_addr : 通信的另外一端的地址信息
        - addrlen : 地址的内存大小
    - 返回值:成功返回发送的数据的字节数,失败返回-1

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
    - 参数:
        - sockfd : 通信的fd
        - buf : 接收数据的数组
        - len : 数组的大小
        - flags : 0
        - src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
        - addrlen : 地址的内存大小
  1. 广播

  服务器向子网中的所有计算机发送消息,客户端只要绑定了服务器广播所使用的端口,那么就可以收到广播消息,但是广播只能在局域网中使用。具体的实现方法是服务端创建一个socket,然后通过setsockopt()来使能广播,然后服务端向广播地址发送数据;客户端创建一个socket,然后绑定本地端口及IP地址,便可接收到来自服务端的数据。

// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
    - sockfd : 文件描述符
    - level : SOL_SOCKET
    - optname : SO_BROADCAST
    - optval : int类型的值,为1表示允许广播
    - optlen : optval的大小
  1. 组播

  单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。多播(组播)只会向加入了多播组的接口发送数据,可用于局域网和广域网。

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
    - 参数
        // 服务器设置多播
        - level:  IPPROTO_IP
        - optname: IP_MULTICAST_IF
        - optval: struct in_addr
    
        // 客户端加入多播组
        - level: IPPROTO_IP
        - optname:IP_ADD_MEMBERSHIP
        - optval: struct ip_mreq类型指针

struct ip_mreq
{
    /* IP multicast address of group. */
    struct in_addr imr_multiaddr; // 组播的IP地址
    /* Local IP address of interface. */
    struct in_addr imr_interface; // 本地的IP地址
};
  1. 本地socket

  本地socket可以用于进程间通信,且一般使用TCP通信流程。

// 服务器端
1. 创建监听的套接字
    int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 -> server端
    struct sockaddr_un addr;
    // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
    bind(lfd, addr, len);
3. 监听
    listen(lfd, 100);
4. 等待并接受连接请求
    struct sockaddr_un cliaddr;
    int cfd = accept(lfd, &cliaddr, len);
5. 通信
    接收数据:read/recv
    发送数据:write/send
6. 关闭连接
    close();

// 客户端的流程
1. 创建通信的套接字
    int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口
    struct sockaddr_un addr;
    // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
    bind(lfd, addr, len);
3. 连接服务器
    struct sockaddr_un serveraddr;
    connect(fd, &serveraddr, sizeof(serveraddr));
4. 通信
    接收数据:read/recv
    发送数据:write/send
5. 关闭连接
    close();


// 头文件: sys/un.h
#define UNIX_PATH_MAX 108
    struct sockaddr_un {
    sa_family_t sun_family; // 地址族协议 af_local
    char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0
};

文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !