Linux应用 select编程

本文详细解释了Linux中的多路复用机制,特别是select函数,介绍了其原理、应用场景,以及如何通过编程接口在服务器和客户端中实现。通过实例展示了如何在服务器端处理并发连接和在客户端发送接收数据。
摘要由CSDN通过智能技术生成

1、概念

1.1 多路复用

在Linux中,多路复用是一种机制,用于同时监视多个文件描述符的状态,以便在其中任何一个文件描述符准备好进行读写操作时立即通知进程。常见的多路复用机制包括 select、poll 和 epoll。

1.2 select

select 是一种用于实现异步通信的系统调用。它允许进程监视多个文件描述符(包括套接字、管道等)的状态,当其中任何一个文件描述符准备好进行读或写操作时,select 就会通知进程。select 的原理是通过在内核中维护一个数据结构来管理文件描述符的状态,当进程调用 select 时,内核会检查所监视的文件描述符的状态,并根据情况进行相应的处理。

1.3 应用场景

  • 并发服务器:在服务器端可以同时处理多个客户端连接请求,使用 select 可以监视多个客户端的套接字,当有客户端数据到达时立即处理。
  • I/O多路复用:在客户端,可以同时处理多个文件描述符的读写操作,节省了不必要的阻塞等待时间。
  • 实时性要求高:当需要实时响应多个文件描述符的状态变化时,select 可以及时通知进程。

1.4 特点

  • 监视多个文件描述符select 可以同时监视多个文件描述符,包括套接字、管道、标准输入等。
  • 非阻塞等待select 可以让进程在等待多个文件描述符准备就绪的过程中继续执行其他任务,不会阻塞在一个文件描述符上。
  • 跨平台支持select 是跨平台的系统调用,在不同操作系统上都能使用。
  • 简单易用:相比其他多路复用机制如 poll 和 epollselect 使用简单,适用于一些简单的并发场景。

2、编程接口

2.1 select

select 函数用于监视一组文件描述符,当其中任何一个文件描述符准备好进行 I/O 操作时,select 函数就会返回,告知应用程序哪些文件描述符可以进行读取、写入或发生异常。

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

    • nfds:监视的文件描述符中最大的文件描述符值加一。
    • readfds:指向一个 fd_set 集合,包含了需要监视读取操作的文件描述符。
    • writefds:指向一个 fd_set 集合,包含了需要监视写入操作的文件描述符。
    • exceptfds:指向一个 fd_set 集合,包含了需要监视异常情况的文件描述符。
    • timeout:指定 select 函数的超时时间,可以是 NULL(阻塞直到有文件描述符准备好)、struct timeval 结构体(设置超时时间)或者 {0, 0}(立即返回)。
  • 返回值

    • 大于 0:返回准备就绪的文件描述符的数量。
    • 等于 0:超时时间到达,没有文件描述符准备好。
    • 小于 0:出错,返回 -1。

2.2 fd_set

在 Linux C 语言编程中,fd_set 是一个用于表示文件描述符集合的数据结构,通常用于多路复用函数(如 selectpollepoll)中。fd_set 提供了一系列操作函数来对文件描述符集合进行操作。

2.2.1 FD_ZERO

将一个 fd_set 集合清空,将所有文件描述符从集合中移除。

void FD_ZERO(fd_set *set);
  • 入参

    • set:要移除文件描述符的集合。
2.2.2 FD_SET

将指定的文件描述符添加到 fd_set 集合中。

void FD_SET(int fd, fd_set *set);
  • 入参

    • fd 表示要添加的文件描述符
    • set 表示要添加到的 fd_set 集合
2.2.3 FD_CLR

从 fd_set 集合中移除指定的文件描述符。

void FD_CLR(int fd, fd_set *set);
  • 入参

    • fd 表示要移除的文件描述符
    • set 表示要从中移除的 fd_set 集合
2.2.4 FD_ISSET

检查指定的文件描述符是否在 fd_set 集合中。

int FD_ISSET(int fd, fd_set *set);
  • 入参

    • fd 表示要检查的文件描述符
    • set 表示要检查的 fd_set 集合
  • 返回值

    • 如果文件描述符在集合中,返回非零值;否则返回 0。

3、编程测试

3.1 服务器编程

编写服务器测试程序如下:

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

#define PORT 8888
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024

int main() 
{
    int server_fd, new_socket, max_clients = MAX_CLIENTS;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    fd_set readfds;
    int client_sockets[MAX_CLIENTS] = {0};
    char buffer[BUFFER_SIZE] = {0};

    // Create a TCP socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) 
    {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Bind the socket to localhost:PORT
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) 
    {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if (listen(server_fd, 3) < 0) 
    {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    while (1) 
    {
        FD_ZERO(&readfds);
        FD_SET(server_fd, &readfds);

        int max_sd = server_fd;

        for (int i = 0; i < max_clients; i++) 
        {
            int sd = client_sockets[i];

            if (sd > 0) 
            {
                FD_SET(sd, &readfds);
            }

            if (sd > max_sd) 
            {
                max_sd = sd;
            }
        }

        // Select the file descriptors
        int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);

        if (FD_ISSET(server_fd, &readfds)) 
        {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) 
            {
                perror("Accept failed");
                exit(EXIT_FAILURE);
            }

            printf("New connection, socket fd is %d, IP is : %s, port : %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            for (int i = 0; i < max_clients; i++) 
            {
                if (client_sockets[i] == 0) 
                {
                    client_sockets[i] = new_socket;
                    break;
                }
            }
        }

        for (int i = 0; i < max_clients; i++) 
        {
            int sd = client_sockets[i];

            if (FD_ISSET(sd, &readfds)) 
            {
                int valread = read(sd, buffer, BUFFER_SIZE);
                if (valread == 0) 
                {
                    // Client disconnected
                    close(sd);
                    client_sockets[i] = 0;
                } 
                else 
                {
                    // Process data from client
                    printf("Received from client: %s\n", buffer);

                    // Add prefix to the received data
                    char reply[BUFFER_SIZE + 15];
                    sprintf(reply, "Server Recv Ok: %s", buffer);

                    // Send the modified data back to client
                    send(sd, reply, strlen(reply), 0);
                }
            }
        }
    }

    return 0;
}

3.2 客户端编程

编写客户端测试程序如下:

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

#define PORT 8888
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() 
{
    int client_fd;
    struct sockaddr_in server_address;
    char buffer[BUFFER_SIZE] = {0};

    // Create a TCP socket
    if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(PORT);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if (inet_pton(AF_INET, SERVER_IP, &server_address.sin_addr) <= 0) 
    {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

    // Connect to the server
    if (connect(client_fd, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) 
    {
        perror("Connection failed");
        exit(EXIT_FAILURE);
    }

    printf("Connected to server\n");

    while (1) 
    {
        printf("Enter message to send (or 'exit' to quit): ");
        fgets(buffer, BUFFER_SIZE, stdin);

        if (strcmp(buffer, "exit\n") == 0) 
        {
            break;
        }

        // Send data to server
        send(client_fd, buffer, strlen(buffer), 0);

        // Receive response from server
        int valread = read(client_fd, buffer, BUFFER_SIZE);
        printf("Server response: %s\n", buffer);
    }

    close(client_fd);

    return 0;
}
3.3 测试

发起服务器程序,然后开启多个终端分别发起客户端程序,客户端程序接收控制台输入数据后发送给服务器,服务器添加一个头部后将数据发送回客户端,测试结果如下:

4、总结

本文介绍了select的相关概念,详细阐述了编程常用接口,最后在linux下进行编程测试。

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tun/tap是一个在Linux系统中用于网络通信和虚拟化的设备驱动程序。它允许用户空间程序通过虚拟网络设备与操作系统内核进行通信。 在Linux Tuntap编程中,我们可以使用一些编程语言如C/C++来利用Tun/tap设备进行网络编程。通过创建和配置Tun/tap设备,我们可以实现对网络数据包的处理和转发。 首先,我们需要使用系统调用函数来创建Tun/tap设备。在C语言中,可以使用open()函数来打开/dev/net/tun设备文件并设置传递ioctl请求。然后使用ioctl()函数来配置和分配设备以及设置设备的IP地址和网络子网等网络属性。 接下来,我们可以使用read()和write()函数来读取和写入设备数据。当数据包到达Tun/tap设备时,我们可以使用read()函数从设备中接收数据,进行必要的处理并进行操作。然后使用write()函数将数据包发送到设备。 此外,我们可以使用select或epoll等函数来实现设备的异步非阻塞读写。通过这种方式,我们可以处理多个Tun/tap设备的并行通信。 在Tun/tap编程中,我们可以利用它的虚拟性质来创建虚拟的网络拓扑,以及进行虚拟化网络环境的测试和开发。我们可以使用网络协议栈和虚拟机监控程序(如QEMU)来模拟虚拟网络,并通过Tun/tap设备进行通信。 总的来说,Linux Tun/tap编程提供了一种灵活、高效的网络编程方式。我们可以利用Tun/tap设备来实现自定义的网络处理逻辑,并进行网络数据包的转发和处理。这对于网络应用开发、网络协议测试等方面都具有重要意义。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值