Linux C select()函数

1.功能

  I/O多路复用允许我们同时检测多个文件描述符,看其中任意一个是否可以执行I/O操作。我们可以在普通文件、终端、伪终端、管道、FIFO、套接字以及一些其他类型的字符型设备上使用select()和poll()来检查文件描述符。

  本文以select()函数操作socket来讲述。

  select 机制会监听它所负责的所有 socket,当其中一个 socket 或者多个 socket 可读或者可写的时候,它就会返回,而如果所有的 socket 都是不可读或者不可写的时候,这个进程就会被阻塞,直到超时,当 select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。

  简单点说就是,假如想在一个线程处理两个阻塞的socket时,就得用select()函数。



2.函数原型

#include <sys/time.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set exceptfds, struct timeval *timeout)

nfds:需要监听的所有描述符集合中的最大值+1,例如监听socket_a = 5、socket_b = 9,那么此时这里应该输入10。这时select函数会轮询描述符为0-9的所有I/O,系统开销比较大,这也是select()函数的缺点。

readfds: 是用来检测输入是否就绪的描述符集合;

writefds: 是用来检测输出是否就绪的描述符集合;

exceptfds: 是用来检测异常情况是否发生的描述符集合;

timeout: 阻塞超时时间。


数据类型fd_set以位掩码的形式来实现,我们不需要知道这些细节,因为所有的操作都是通过下面这四个函数来完成的:

扫描二维码关注公众号,回复: 13115017 查看本文章
#include <sys/select.h>
// 将fdset指向的集合初始化为空
void FD_ZERO(fd_set *fdset);

// 将文件描述符fd添加到fdset指向的集合
void FD_SET(int fd, fd_set *fdset);

// 将文件描述符fd从fdset指向的集合中移除
void FD_CLR(int fd, fd_set *fdset);

// 判断文件描述符fd是否在fdset指向的集合中,如果是返回True
int FD_ISSET(int fd, fd_set *fdset);

3.demo

在一个线程中同时创建一个tcp_socket和一个udp_socket,利用select()来让两个socket都能随时收到消息。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netdb.h>

#define TCP_HOST "192.168.123.121"      // 设置服务器IP
#define UDP_HOST "192.168.123.195"      // 绑定本机IP
#define PORT 6666                       // 端口号
#define BUF_SIZE_MAX (4 * 1024)         // 4k 的数据区域

int main(void)
{
    
    
    int tcp_socket_fd, udp_socket_fd, ret;
    struct sockaddr_in tcp_addr, udp_addr;
    char buf[BUF_SIZE_MAX];
    struct timeval tv;
    fd_set readfds;

    // 创建TCP套接字描述符
    tcp_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (tcp_socket_fd == -1) 
    {
    
    
        printf("Fail to creat tcp_socket.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置TCP链接的服务器IP与端口
    memset(&tcp_addr, 0,sizeof(tcp_addr));
    tcp_addr.sin_family = AF_INET;    // IPV4
    tcp_addr.sin_port = htons(PORT);
    tcp_addr.sin_addr.s_addr = inet_addr(TCP_HOST);

    // 建立TCP连接
    ret = connect(tcp_socket_fd, (struct sockaddr *)&tcp_addr, sizeof(struct sockaddr));
    if (ret == -1) 
    {
    
    
        close(tcp_socket_fd);
        printf("Fail to connect server.\r\n");
        exit(EXIT_FAILURE);
    }

    // 创建UDP套接字描述符
    udp_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket_fd == -1) 
    {
    
    
        close(tcp_socket_fd);
        printf("Fail to creat udp_socket.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置UDP链接需要绑定的本地IP与端口
    memset(&udp_addr, 0,sizeof(udp_addr));
    udp_addr.sin_family = AF_INET;
    udp_addr.sin_port = htons(PORT);
    udp_addr.sin_addr.s_addr = inet_addr(UDP_HOST);

    // 绑定IP与端口
    ret = bind(udp_socket_fd, (struct sockaddr*)&udp_addr, sizeof(struct sockaddr));
    if(ret == -1)
    {
    
    
        close(tcp_socket_fd);
        close(udp_socket_fd);
        printf("Fail to bind udp_addr.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置select阻塞时间
    tv.tv_sec = tv.tv_usec = 0;

    while (1) 
    {
    
    
        memset(buf, 0, BUF_SIZE_MAX);
        FD_ZERO(&readfds);
        FD_SET(tcp_socket_fd, &readfds);
        FD_SET(udp_socket_fd, &readfds);

        ret = select(udp_socket_fd + 1, &readfds, NULL, NULL, &tv);
        if(ret < 0)
        {
    
    
            printf("ret = %d.\r\n", ret);
            break;
        }
        else
        {
    
     
            // TCP数据
            if(FD_ISSET(tcp_socket_fd, &readfds))
            {
    
    
                ret = read(tcp_socket_fd, buf, BUF_SIZE_MAX);
                printf("[TCP recv]: %s\r\n", buf);
            }

            // UDP数据
            else if(FD_ISSET(udp_socket_fd, &readfds))
            {
    
    
                ret = read(udp_socket_fd, buf, BUF_SIZE_MAX);
                printf("[UDP recv]: %s\r\n", buf);
            }
        }

        usleep(100000);     
    }

    close(tcp_socket_fd);
    close(udp_socket_fd);
    exit(0);
}

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36973838/article/details/115371468