C语言select函数案例

​ C语言 select 函数允许进程指示内核等待多个事件的任意一个发生,并在一个或多个事件发生或指定时间才唤醒进程.

#include <sys/select.h>
#include <sys/time.h>
/**
 * int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);
 * 
*/
返回:准备好描述符字的正数目,0为超时,-1为出错
 一、参数说明
 __nfds——传入参数,集合中所有文件描述符的范围,即最大文件描述符值+1
 __readfds——传入传出参数,select调用时传入要监听的可读文件描述符集合,select返回时传出发生可读事件的文件描述符集合
 __writefds——传入传出参数,select调用时传入要监听的可写文件描述符集合,select返回时传出发生可写事件的文件描述符集合
 __exceptfds——传出参数,select返回时传出发生事件(包括可读和可写)中异常事件的文件描述符集合
 __timeout——传入参数,设置select阻塞的时间。若设置为NULL,则select一直阻塞直到有事件发生;
 若设置为0,则select为非阻塞模式,执行后立即返回;
 若设置为一个大于0的数,即select的阻塞时间,若阻塞时间内有事件发生就返回,否则时间到了立即返回

二、 timeval 结构,提供秒数和毫秒数

struct timeval 
{
long tv_sec; //秒
long tv_usec;//毫秒
}
1. timeval 设置空指针 NULL时,永远等待下去
2. timeval 设置大于0的值,等待固定时间返回
3. timeval 设置为0,立即返回 

fd_set 可看作一个集合,存放可读、可写或异常事件的文件描述符。fd_set集合通常有以下四个宏来操作:

void FD_ZERO(fd_set *fdset);  //清空fdset中所有文件描述符
void FD_SET(int fd,fd_set *fdset);  //添加文件描述符fd到集合fdset中
void FD_CLR(int fd,fd_set *fdset); //将文件描述符fd从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);  //判断文件描述符fd是否在集合fdset中

select工作原理

​ 传入要监听的文件描述符集合(可读、可写或异常)开始监听,select处于阻塞状态,当有事件发生或设置的等待时间timeout到了就会返回,返回之前自动去除集合中无事件发生的文件描述符,返回时传出有事件发生的文件描述符集合。

​ 但select传出的集合并没有告诉用户集合中包括哪几个就绪的文件描述符,需要用户后续进行遍历操作。

select 优点

  • 优点
  1. select的可移植性较好,可以跨平台;
  2. select可设置的监听时间timeout精度更好,可精确到微秒,而poll为毫秒。
  • 缺点
    1. select支持的文件描述符数量上限为1024,不能根据用户需求进行更改;
    2. select每次调用时都要将文件描述符集合从用户态拷贝到内核态,开销较大;
    3. select返回的就绪文件描述符集合,需要用户循环遍历所监听的所有文件描述符是否在该集合中,当监听描述符数量很大时效率较低。

select案例

server 端

    /*
 * @Descripttion:
 * @version: 1.0.0
 * @Author: xiaoge
 * @Date: 2023-05-25 06:22:00
 * @LastEditors: xiaoge
 * @LastEditTime: 2023-06-07 08:43:08
 */
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <strings.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>

#define PORT 1234
#define BACKLOG 5

int main(int argc, char const *argv[])
{

    int i, n, j, maxi;
    int maxfd, listenfd, connfd, sockfd;
    int nready, client[FD_SETSIZE - 1]; // 客户端请求连接最多1023,listen 占用一个
    char buf[BUFSIZ], str[INET_ADDRSTRLEN];

    struct sockaddr_in c_addr, s_addr;
    socklen_t c_addr_len;
    fd_set allset, readsset; // 定义监听描述符、发生事件描述符

    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    s_addr.sin_port = htons(PORT);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    bind(listenfd, (struct sockaddr *)&s_addr, sizeof(s_addr));
    listen(listenfd, BACKLOG);
    // 初始化最大文件描述符为监听listenfd
    maxfd = listenfd;

    maxi = -1;
    for (i = 0; i < FD_SETSIZE; i++)
    {
        // 数组client存储文件描述符的个数,初始值-1
        client[i] = -1;
    }

    // 初始化监听描述符
    FD_ZERO(&allset);

    // 监听描述符 添加集合
    FD_SET(listenfd, &allset);

    while (1)
    {
        // 监听文件符 设置监听
        readsset = allset;
        // 监听有无accept 客户端连接请求
        nready = select(maxfd + 1, &readsset, NULL, NULL, NULL);
        if (nready < 0)
        {
            printf("select error");
            exit(0);
        }
        if (FD_ISSET(listenfd, &readsset))
        {
            c_addr_len = sizeof(c_addr);
            connfd = accept(listenfd, (struct sockaddr *)&c_addr, &c_addr_len);
            inet_ntop(AF_INET, &c_addr.sin_addr.s_addr, str, sizeof(str));
            printf("received from %s at port %d\n",
                   str,
                   ntohs(c_addr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++)
            {
                // 将connfd赋值给client 数组第一个为-1的元素
                if (client[i] < 0)
                {
                    client[i] = connfd;
                    break;
                }
            }

            if (i == FD_SETSIZE)
            {
                fputs("too many clients\n", stderr);
                exit(1);
            }
            // 客户端connfd 设置到 总集合中
            FD_SET(connfd, &allset);
            // 更新最大文件描述符值
            if (connfd > maxfd)
                maxfd = connfd;
            if (i > maxi)
                maxi = i;
            // 如果此时有事件发生且=1,
            nready--;
            if (nready == 0)
                continue;
        }

        for (i = 0; i < maxi; i++)
        {
            sockfd = client[i];
            // 客户端描述符失效
            if (sockfd < 0)
                continue;
            if (FD_ISSET(sockfd, &readsset))
            {
                n = read(sockfd, buf, sizeof(buf));
                if (n == 0) // 当客户端关闭连接,服务端也关闭连接
                {
                    close(sockfd);
                    FD_CLR(sockfd, &allset); // 解除select对该已连接文件描述符的监控
                    client[i] = -1;
                }
                else if (n > 0)
                {
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    sleep(2);
                    write(sockfd, buf, n);
                }
                --nready;
                if (nready == 0)
                    break; // 跳出for循环,还在while中
            }
        }
    }
    close(listenfd);
    return 0;
}

客户端
/*
 * @Descripttion:
 * @version: 1.0.0
 * @Author: xiaoge
 * @Date: 2023-05-25 06:22:06
 * @LastEditors: xiaoge
 * @LastEditTime: 2023-06-07 08:44:54
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>

// 客户端、服务端都在一台主机上,所以直接用本机IP地址
#define SERV_IP "192.168.135.129"
#define SERV_PORT 1234

int main()
{
    int n, cfd;
    struct sockaddr_in serv_addr;
    char buf[BUFSIZ];

    //创建socket
    cfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 将点十进制字节串转换为网络字节序
    //客户端如果连接成功
    connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    while (1)
    {
        //客户端等待输入
        fgets(buf, sizeof(buf), stdin);
        //客户端写入服务端
        write(cfd, buf, strlen(buf));
        //等待服务端返回
        n = read(cfd, buf, sizeof(buf));
        //客户端标准输出
        write(STDOUT_FILENO, buf, n);
    }

    close(cfd);
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小哥(xpc)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值