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服用实现了并发服务器程序的编写。


相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
93 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
86 1
Linux C/C++之IO多路复用(aio)
|
2月前
|
机器学习/深度学习 编解码
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
本文介绍了九种常用的神经网络激活函数:Sigmoid、tanh、ReLU、ReLU6、Leaky ReLU、ELU、Swish、Mish和Softmax,包括它们的定义、图像、优缺点以及在深度学习中的应用和代码实现。
133 0
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
25 0
Linux C/C++之IO多路复用(poll,epoll)
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
3月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
4月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
49 2
|
4月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
4月前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流
下一篇
无影云桌面