Linux网络编程(多路IO复用select函数使用)

简介: Linux网络编程(多路IO复用select函数使用)

前言

本篇文章带大家来学习一下多路IO复用select函数的使用。

一、什么是多路IO复用

1.多路I/O复用(Multiplexing I/O)是一种用于同时监视和处理多个输入/输出(I/O)源的技术。它允许一个进程可以同时监听和处理多个文件描述符(sockets、文件、管道等),从而实现高效的事件驱动的编程模型。

2.在传统的I/O模型中,通常采用阻塞I/O或非阻塞I/O方式进行读写操作,为每个I/O源(例如一个socket连接)都创建一个线程或进程来处理。这种方式在高并发场景下,会导致线程或进程数目的剧增,系统资源浪费,且上下文切换开销大。

3.而多路I/O复用通过使用操作系统提供的机制,如select、poll、epoll(在Linux中),允许程序同时监听多个I/O源,实现了异步I/O操作。应用程序可以将多个I/O源注册到多路复用器中,并在多路复用器上等待事件的发生,一旦有事件就绪(如读写准备就绪),程序就可以针对这些就绪的事件进行相应的操作。

多路I/O复用的主要优点如下:

1.高效:通过事件驱动的方式,避免了线程和进程的频繁切换,减小了系统开销。

2.节省资源:使用较少的线程或进程来处理多个I/O源,节省了系统资源,并能够处理大量的并发连接。

3.简化编程模型:与传统的多线程或多进程编程方式相比,使用多路I/O复用可以简化编程模型,使得代码更加清晰和易于维护。

需要注意的是,多路I/O复用并非适用于所有情况,特别是在处理高延迟和大数据量的场景下可能不太适合。此外,不同的操作系统上多路复用器的机制和性能表现也有所不同。

二、select函数讲解

select()函数是一个用于多路复用的I/O模型,可以同时监视多个文件描述符,并在其中任何一个文件描述符就绪时进行相应的处理。它被广泛用于实现高效的事件驱动编程。

select()函数的原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

nfds:监视的文件描述符的最大值加1。

readfds:指向可读文件描述符集合的指针。

writefds:指向可写文件描述符集合的指针。

exceptfds:指向异常文件描述符集合的指针。

timeout:指定超时时间,选择阻塞的时长。

fd_set是一个文件描述符集合类型,通过宏定义和操作函数进行操作。

使用select()函数的步骤如下:

创建并初始化文件描述符集合。

将需要监视的文件描述符添加到对应的文件描述符集合中。

调用select()函数并传递文件描述符集合。

检查返回值,若为负数则表示出错,若为0则表示超时,若大于0则表示有文件描述符就绪。

使用FD_ISSET()宏检查具体哪些文件描述符就绪,并进行相应的处理。

下面是一个简单的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
    fd_set readfds;
    int max_fd, ret;
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);  // 监视标准输入
    max_fd = STDIN_FILENO + 1;
    struct timeval timeout;
    timeout.tv_sec = 5;  // 设置超时时间为5秒
    timeout.tv_usec = 0;
    ret = select(max_fd, &readfds, NULL, NULL, &timeout);
    if (ret == -1) {
        perror("select");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout\n");
    } else {
        if (FD_ISSET(STDIN_FILENO, &readfds)) {
            printf("Stdin is ready for reading\n");
            // 读取标准输入数据
            char buffer[100];
            fgets(buffer, sizeof(buffer), stdin);
            printf("Received: %s", buffer);
        }
    }
    return 0;
}

三、使用select编程并发服务器

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int maxfd;//最大文件描述符
    int ret = 0;
    int i = 0;
    server = socket(PF_INET, SOCK_STREAM, 0);
    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);
    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("server bind error\n");
        return -1;
    }
    if( listen(server, 128) == -1 )
    {
        printf("server listen error\n");
        return -1;
    }
    printf("server start success\n");
    maxfd = server;
    fd_set rets;
    fd_set allrets;
    FD_ZERO(&allrets);
    FD_SET(server, &allrets);
    while( 1 )
    {        
        rets = allrets;
        ret = select(maxfd + 1, &rets, NULL, NULL, NULL);//使用select函数进行监听
        if(ret > 0)
        {
            if(FD_ISSET(server, &rets))
            {
                /*有客户端连接上来了*/
                asize = sizeof(caddr);  
                client = accept(server, (struct sockaddr*)&caddr, &asize);
                FD_SET(client, &allrets);//将连接上来的客户端设置进去
                if(maxfd < client)
                {
                    /*更新最大文件描述符*/
                    maxfd = client;
                }
            }
            else if(ret == -1)
            {
                for(i = server + 1; i <= maxfd; i++)
                {
                    if(FD_ISSET(i, &rets))//判断是哪一个客户端有信息
                    {
                        len = read(i, buf, 1024);
                        if(len == 0)//客户端断开了连接
                        {
                            FD_CLR(i, &allrets);//将断开连接的客户端清除出去
                            close(i);//关闭客户端
                        }
                        else
                        {
                            printf("read len : %d read buf : %s\n", len, buf);
                            write(i, buf, len);
                        }
                    }
                }
            }
        }
        else
        {
            printf("select is err\n");
        }                
    }
    close(server);
    return 0;
}

注意点:

使用select()函数时,传入和传出的readfds等参数可能是不一样的。这是因为select()函数会修改传入的文件描述符集合,以指示哪些文件描述符已经就绪。

所以在使用select时需要先将readfds做一个备份,以免丢失一些文件描述符。

四、select函数的缺点

1.低效性:select()函数在功能上存在一些限制。它使用线性扫描的方式来遍历监视的文件描述符集合,因此在大量文件描述符的情况下效率较低。每次调用select()都需要将整个文件描述符集合传递给内核,并在返回时重新检查整个集合以确定哪些文件描述符已就绪。这种线性扫描的开销随着监视的文件描述符数量的增加而增加。

2.文件描述符数量限制:select()函数所能监视的文件描述符数量存在限制,在某些系统上,这个限制可能是比较小的,例如1024。因此,如果要监视的文件描述符数量超过了限制,就需要采用其他方法来解决。

3.阻塞模式:select()函数是一种阻塞式调用,即当没有任何文件描述符就绪时,它会一直阻塞等待。这会导致程序无法进行其他操作。虽然可以通过设置超时时间来解决这个问题,但超时时间过短可能导致错误的超时,而超时时间过长则会影响程序的响应性。

4.受限的事件类型:select()函数只能监视文件描述符的可读性、可写性和异常情况。如果需要监视其他类型的事件,例如定时器事件或信号事件,就无法使用select()函数。

5.每次调用重新初始化:每次调用select()函数之前,需要重新初始化文件描述符集合,将需要监视的文件描述符重新添加到集合中。这样的初始化过程比较繁琐,尤其当文件描述符集合发生变化时,需要手动更新集合。

总结

本篇文章主要讲解了使用select函数进行多路IO服用实现了并发服务器程序的编写。


相关文章
|
19天前
|
安全 Linux 网络安全
Web安全-Linux网络协议
Web安全-Linux网络协议
44 4
|
13天前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
19 6
|
14天前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
51 3
|
19天前
|
机器学习/深度学习 安全 网络协议
Web安全-Linux网络命令
Web安全-Linux网络命令
17 1
|
27天前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
1月前
|
Linux PHP
Linux CentOS 宝塔 Suhosin禁用php5.6版本eval函数详细图文教程
【8月更文挑战第27天】本文介绍两种禁用PHP执行的方法:使用`PHP_diseval_extension`禁用和通过`suhosin`禁用。由于`suhosin`不支持PHP8,仅适用于PHP7及以下版本,若服务器安装了PHP5.6,则需对应安装`suhosin-0.9.38`版本。文章提供了详细的安装步骤,并强调了宝塔环境下与普通环境下的PHP路径差异。安装完成后,在`php.ini`中添加`suhosin.so`扩展并设置`executor.disable_eval = on`以禁用执行功能。最后通过测试代码验证是否成功禁用,并重启`php-fpm`服务生效。
26 2
|
15天前
|
网络协议 Linux
Linux 网络配置
了解基本命令与权限后,如何让Linux系统联网?可通过编辑`/etc/sysconfig/network-scripts/`下的`ifcfg-ethX`文件配置网卡,其中`ethX`代表第X块网卡。对于DHCP自动获取或静态IP,需设置`BOOTPROTO`参数,并指定IP、子网掩码和网关等。配置完成后,运行`/etc/init.d/network restart`重启网络。DNS可在`/etc/resolv.conf`中设置,添加`nameserver`行即可,无需重启网卡。配置好后,可用`ifconfig`查看IP信息,并通过远程工具如SecureCRT连接服务器。
32 0
|
22天前
|
域名解析 负载均衡 网络协议
Linux网络接口配置不当所带来的影响
总而言之,Linux网络接口的恰当配置是保证网络稳定性、性能和安全性的基础。通过遵循最佳实践和定期维护,可以最大程度地减少配置错误带来的负面影响。
62 0
|
1天前
|
存储 安全 网络安全
网络安全与信息安全的守护之道
本文聚焦于网络安全与信息安全领域,深入剖析了网络漏洞、加密技术及安全意识等核心要素。通过详尽的技术分析与实例展示,旨在为读者构建一道坚实的网络防线,同时强调人为因素在网络安全中的关键作用,共同维护网络世界的和谐与安全。
|
1天前
|
SQL 安全 网络安全
网络安全与信息安全:构建数字世界的坚固防线
在数字化时代,网络安全与信息安全已成为维护数据完整性、保障隐私安全和确保业务连续性的关键。本文将深入探讨网络安全漏洞的形成原因、常见类型及其影响,解析加密技术的原理、分类及应用场景,强调个人和企业提升安全意识的重要性,并分享实用的防护措施。通过这些内容的学习,读者将能够更好地理解网络安全挑战,掌握保护自身免受网络威胁的方法,共同构建一个更加安全的网络环境。