C++ Select()

C++ Select()是一种系统调用函数,能在一组文件描述符中强制时间限制内等待一个或多个条件成立,可用于I/O多路复用。下面从不同方面对C++ Select()做详细的阐述。

一、C Select函数

C Select函数的原型为:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout),其中:

• nfds为文件描述符集合中数值最大的描述符加1

• readfds,writefds,exceptfds为文件描述符集合

• timeout表示阻塞时间

C Select函数用于检查文件描述符集合中是否有可读、可写、异常等事件,可用于单进程内多个文件描述符的I/O事件监听。

以下为C++ Select函数使用示例:


#include<stdio.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

int main() {  
    fd_set rfds;  
    struct timeval tv;  
    int retval;  
    char buf[256];  

/* 等待stdin(标准输入)上的输入 */  
    FD_ZERO(&rfds);  
    FD_SET(STDIN_FILENO, &rfds);  

/* 等待5秒钟 */  
    tv.tv_sec = 5;  
    tv.tv_usec = 0;  

    retval = select(1, &rfds, NULL, NULL, &tv);  

/* 由于我们只等待stdin上的输入,所以不需要传递参数除STDIN_FILENO外 */
    if (retval == -1)  
        perror("select()");  
    else if (retval) {  
        fgets(buf, sizeof buf, stdin);  
        printf("输入: %s\n", buf);  
    }  
    else  
        printf("没有输入数据在五秒内.\n");  
    return 0;  
}

二、C Selection和S Selection

C Selection和S Selection都是Select函数的改进版,其扩展了参数类型,可以通过hash表实现高效的查找,提升了系统利用率。其中,C Selection适用于用户空间,S Selection适用于内核空间,可以用于高速网络和操作系统的性能优化。

C++ Select函数的相关参数和C Selection、S Selection相比,基本一致。在使用S Selection时,需要在建立socket时进行一个参数的设置,同时需要用到poll函数。

下面是借用《UNIX环境高级编程》一书中关于S Selection的代码示例:


#include<sys/time.h>
#include<sys/types.h>

/* Selserv.cpp */
int
main(int argc, char *argv[])
{
    int    i, maxi, maxfd, listenfd, connfd, sockfd;
    int    nready, client[FD_SETSIZE];
    ssize_t    n;
    fd_set rset, allset;
    char   buf[MAXLINE];
    char   str[INET_ADDRSTRLEN];
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;

    if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
        err_sys("bind error");

    if (listen(listenfd, LISTENQ) < 0)
        err_sys("listen error");
    else
        printf("<时间%ld>  监听端口 %d on %s\n",
               time(NULL), SERV_PORT, inet_ntop(AF_INET, &servaddr.sin_addr, str, sizeof(str)));

    maxfd = listenfd;        /* 定义初始最大描述符值 */
    maxi = -1;                /* 有效客户连接下标 */
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;        /* 初始化 */

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

    for ( ; ; ) {
        rset = allset;        /* 每次循环时都重新设置select的可读文件描述符集 */
        if ( (nready = select(maxfd+1, &rset, NULL, NULL, NULL)) < 0)
            err_sys("select error");

        if (FD_ISSET(listenfd, &rset)) { /* 新的客户连接请求 */
            clilen = sizeof(cliaddr);
            if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)
                err_sys("accept error");

            printf("<时间%ld>:新的客户连接来自%s: %d\n",
                   time(NULL), inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {
                    client[i] = connfd;    /* 保存新的描述符 */
                    break;
                }
            if (i == FD_SETSIZE)
                err_quit("too many clients");

            FD_SET(connfd, &allset);    /* 添加新的文件描述符到读集合中 */
            if (connfd > maxfd)
                maxfd = connfd;            /* for select */

            if (i > maxi)
                maxi = i;                /* 数组client中最大元素的下标 */
            if (--nready <= 0)
                continue;            /* 没有更多就绪事件时,继续回到select()调用*/

        }

        for (i = 0; i <= maxi; i++) {    /* 检测客户端socket */
            if ( (sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset)) {
                if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                    /* 当客户关闭连接时,服务器也关闭对应的连接,并将相应的client数组元素设置为-1,表示空闲 */
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                } else {
                    Writen(sockfd, buf, n);
                }
                if (--nready <= 0)
                    break;                /* 没有更多就绪事件时,继续回到select()调用*/
            }
        }
    }
}

三、C Select语句

C Select语句是select函数的一种可移植、跨平台的I/O多路复用机制,可以在一组文件描述符上等待多个I/O事件,多线程同时处理。C Select语句适用于网络编程中,方便实现高并发的服务器。

以下是一个使用C Select语句进行I/O复用的示例:


/* 实现一个TCP服务器,监听10086端口,并在有客户端连接时返回当前系统时间 */
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<time.h>

const int MAXLINE = 1024, LISTENQ = 5, SERV_PORT = 10086;

int main() {
    int i, maxi, maxfd, listenfd, connfd, sockfd;
    int nready, client[FD_SETSIZE];
    ssize_t n;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;
    fd_set rset, allset;

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("error: socket");
        return -1;
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
        perror("error: bind");
        return -1;
    }

    if (listen(listenfd, LISTENQ) == -1) {
        perror("error: listen");
        return -1;
    }

    maxfd = listenfd;
    maxi = -1;
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    for (;;) {
        rset = allset;
        if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) == -1) {
            perror("error: select");
            return -1;
        }

        if (FD_ISSET(listenfd, &rset)) {
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
            printf("accept client ip=%s port=%d\n",
                inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                ntohs(cliaddr.sin_port));


            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {
                    client[i] = connfd;
                    break;
                }

            if(i == FD_SETSIZE) {
                perror("too many clients");
                return -1;
            }

            FD_SET(connfd, &allset);

            if (connfd > maxfd)
                maxfd = connfd;
            if (i > maxi)
                maxi = i;

            if (--nready == 0)
                continue;
        }
        for (i = 0; i <= maxi; i++) {
            if ((sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset)) {
                if ((n = read(sockfd, buf, MAXLINE)) == 0) {
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                }
                else {
                    time_t t = time(NULL);
                    char buffer[1024] = {0};
                    strftime(buffer, 1024, "%Y-%m-%d %H:%M:%S\n", localtime(&t));
                    write(sockfd, buffer, strlen(buffer));
                }
                if (--nready == 0)
                    break;
            }
        }
    }
    return 0;
}

四、C Select详解与模型

C Select是一种基于事件驱动的编程范式,其主要用于网络编程中的I/O多路复用与高并发服务器。其基本工作原理为:首先需要定义一组文件描述符,通过select函数等待其事件到达,当其中任意一个文件描述符事件发生变化时,select函数返回;在返回后,通过遍历文件描述符集合获取到其中发生变化的文件描述符并进行对应处理。

C Select模型与并发服务器中的运作模型是有关联的:在并发服务器中,通过创建多线程来支持多并发客户端连接,当每个线程需要处理多个不同的客户端请求时,其I/O事件规律拓扑结构应采用Multiplexer或Reactor这两种I/O通信模型,其中,Multiplexer模型使用了Select程序,而Reactor模型使用的是相对更为高级的Epoll机制。

以下为一个简单的使用C Select函数的示例:


#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/time.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

void server(int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
if (fd == -1) {
perror("socket");
return;
}
int optval = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval); // 配置选项
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(fd, (struct sockaddr*)&addr, sizeof addr) == -1) { // 绑定端口

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/278898.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2024-12-20 15:02
下一篇 2024-12-20 15:02

相关推荐

  • 使用SQL实现select 聚合查询结果前加序号

    select语句是数据库中最基础的命令之一,用于从一个或多个表中检索数据。常见的聚合函数有:count、sum、avg等。有时候我们需要在查询结果的前面加上序号,可以使用以下两种方…

    编程 2025-04-29
  • 深入理解SQL SELECT AS

    一、AS的基本用法 1、AS的含义 在SQL语句中,SELECT语句除了可以通过列名选择指定的列,还可以对查询结果进行自定义,对列进行别名定义。这个别名就是AS的作用。 SELEC…

    编程 2025-04-25
  • 深入了解select模型

    一、select模型特点 select是传统的IO多路复用模型,与其他IO多路复用模型(如epoll,kqueue)不同的是,select函数能够同时监听多个socket句柄的可读…

    编程 2025-04-23
  • Select Join的作用与应用

    一、Select Join简介 Select Join是SQL中的命令语句,常用于连接多个数据表以显示相关数据。该操作能够通过使用共同的列连接多个表,从而将这些表的行组合在一起,从…

    编程 2025-04-23
  • SQL SELECT AS详解

    在 SQL 中,使用 SELECT 语句根据给定的条件从数据库中选取数据。AS 关键字用于为列或表格名称指定别名,提供更准确、更有意义的列名和表名。 一、AS关键字的基础使用 AS…

    编程 2025-04-12
  • Select1和Select*的区别

    一、Select语句 在数据库操作中,Select语句是最常用的语句之一,它用来从表中获取数据,再根据数据的不同属性进行分类、计算等操作。在Select语句中,一个常见的问题是:在…

    编程 2025-03-12
  • User-select详解:控制文字选中

    一、user-select怎么样 user-select属性控制是否允许用户选择文本,以及如何选择文本。该属性在CSS3中出现。 如果user-select的值被设置为none,则…

    编程 2025-03-12
  • jQuery select change事件详解

    一、基本用法 1、首先需要在网页中引入jQuery库 <script src=”https://code.jquery.com/jquery-3.6.0.min.js”&gt…

    编程 2025-02-17
  • 详解el-select的默认值

    一、el-select 默认值 el-select是Element UI组件库中的一种下拉选择器。在使用el-select组件时,可以通过设置默认值来使页面初始显示选定的选项。el…

    编程 2025-02-15
  • 深入解析select*fromwhere

    一、select from where如何提升搜索速度 当我们需要从一张表中查询数据时,我们可以通过使用select语句来实现。使用select语句时,我们需要指定要查询的列名和查…

    编程 2025-02-05

发表回复

登录后才能评论