Linux C select()函数

1.功能

  I/O多路复用允许我们同时检测多个文件描述符,看其中任意一个是否可以执行I/O操作。我们可以在普通文件、终端、伪终端、管道、FIFO、套接字以及一些其他类型的字符型设备上使用select()和poll()来检查文件描述符。

  本文以select()函数操作socket来讲述。

  select 机制会监听它所负责的所有 socket,当其中一个 socket 或者多个 socket 可读或者可写的时候,它就会返回,而如果所有的 socket 都是不可读或者不可写的时候,这个进程就会被阻塞,直到超时,当 select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。

  简单点说就是,假如想在一个线程处理两个阻塞的socket时,就得用select()函数。



2.函数原型

#include <sys/time.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set exceptfds, struct timeval *timeout)

nfds:需要监听的所有描述符集合中的最大值+1,例如监听socket_a = 5、socket_b = 9,那么此时这里应该输入10。这时select函数会轮询描述符为0-9的所有I/O,系统开销比较大,这也是select()函数的缺点。

readfds: 是用来检测输入是否就绪的描述符集合;

writefds: 是用来检测输出是否就绪的描述符集合;

exceptfds: 是用来检测异常情况是否发生的描述符集合;

timeout: 阻塞超时时间。


数据类型fd_set以位掩码的形式来实现,我们不需要知道这些细节,因为所有的操作都是通过下面这四个函数来完成的:

#include <sys/select.h>
// 将fdset指向的集合初始化为空
void FD_ZERO(fd_set *fdset);

// 将文件描述符fd添加到fdset指向的集合
void FD_SET(int fd, fd_set *fdset);

// 将文件描述符fd从fdset指向的集合中移除
void FD_CLR(int fd, fd_set *fdset);

// 判断文件描述符fd是否在fdset指向的集合中,如果是返回True
int FD_ISSET(int fd, fd_set *fdset);

3.demo

在一个线程中同时创建一个tcp_socket和一个udp_socket,利用select()来让两个socket都能随时收到消息。

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

#define TCP_HOST "192.168.123.121"      // 设置服务器IP
#define UDP_HOST "192.168.123.195"      // 绑定本机IP
#define PORT 6666                       // 端口号
#define BUF_SIZE_MAX (4 * 1024)         // 4k 的数据区域

int main(void)
{
    int tcp_socket_fd, udp_socket_fd, ret;
    struct sockaddr_in tcp_addr, udp_addr;
    char buf[BUF_SIZE_MAX];
    struct timeval tv;
    fd_set readfds;

    // 创建TCP套接字描述符
    tcp_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (tcp_socket_fd == -1) 
    {
        printf("Fail to creat tcp_socket.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置TCP链接的服务器IP与端口
    memset(&tcp_addr, 0,sizeof(tcp_addr));
    tcp_addr.sin_family = AF_INET;    // IPV4
    tcp_addr.sin_port = htons(PORT);
    tcp_addr.sin_addr.s_addr = inet_addr(TCP_HOST);

    // 建立TCP连接
    ret = connect(tcp_socket_fd, (struct sockaddr *)&tcp_addr, sizeof(struct sockaddr));
    if (ret == -1) 
    {
        close(tcp_socket_fd);
        printf("Fail to connect server.\r\n");
        exit(EXIT_FAILURE);
    }

    // 创建UDP套接字描述符
    udp_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket_fd == -1) 
    {
        close(tcp_socket_fd);
        printf("Fail to creat udp_socket.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置UDP链接需要绑定的本地IP与端口
    memset(&udp_addr, 0,sizeof(udp_addr));
    udp_addr.sin_family = AF_INET;
    udp_addr.sin_port = htons(PORT);
    udp_addr.sin_addr.s_addr = inet_addr(UDP_HOST);

    // 绑定IP与端口
    ret = bind(udp_socket_fd, (struct sockaddr*)&udp_addr, sizeof(struct sockaddr));
    if(ret == -1)
    {
        close(tcp_socket_fd);
        close(udp_socket_fd);
        printf("Fail to bind udp_addr.\r\n");
        exit(EXIT_FAILURE);
    }

    // 设置select阻塞时间
    tv.tv_sec = tv.tv_usec = 0;

    while (1) 
    {
        memset(buf, 0, BUF_SIZE_MAX);
        FD_ZERO(&readfds);
        FD_SET(tcp_socket_fd, &readfds);
        FD_SET(udp_socket_fd, &readfds);

        ret = select(udp_socket_fd + 1, &readfds, NULL, NULL, &tv);
        if(ret < 0)
        {
            printf("ret = %d.\r\n", ret);
            break;
        }
        else
        { 
            // TCP数据
            if(FD_ISSET(tcp_socket_fd, &readfds))
            {
                ret = read(tcp_socket_fd, buf, BUF_SIZE_MAX);
                printf("[TCP recv]: %s\r\n", buf);
            }

            // UDP数据
            else if(FD_ISSET(udp_socket_fd, &readfds))
            {
                ret = read(udp_socket_fd, buf, BUF_SIZE_MAX);
                printf("[UDP recv]: %s\r\n", buf);
            }
        }

        usleep(100000);     
    }

    close(tcp_socket_fd);
    close(udp_socket_fd);
    exit(0);
}

在这里插入图片描述
在这里插入图片描述

### Apache 启动时遇到 “the requested operation has failed”的常见原因及解决方案 #### 可能的原因分析 当尝试启动 Apache 服务器并收到“the requested operation has failed”错误消息时,这可能是由多种因素引起的。具体来说: - **端口冲突**:其他应用程序占用了80或其他默认HTTP服务使用的端口号可能导致此问题发生[^1]。 - **软件冲突**:某些特定类型的第三方程序可能会干扰Apache的服务进程,比如Dr.com客户端等网络管理工具会影响TCP/IP协议栈中的NetBIOS设置,进而阻止Apache正常工作[^2]。 - **配置文件错误**:`httpd.conf` 文件内的语法或逻辑失误也会引发此类警告;尤其是在修改过PHP支持等相关选项后更易出现问题。通过命令行方式加载Apache可帮助定位具体的配置项异常位置[^3]。 - **Winsock损坏**:Windows套接字(Winsock)组件受损同样会造成持续性的启动失败现象。对此情况可以通过重置Winsock来临时解决问题[^4]。 #### 对应的解决措施建议 针对上述提到的各种可能性,采取相应的对策能够有效提高成功启动的概率: 对于端口占用的情况,检查是否有其它应用正在监听目标端口,并考虑更改Apache监听地址或者关闭竞争者。 面对潜在的应用层兼容性难题,调整系统网络参数以适应两者共存的需求是一个可行的办法——即进入网络连接属性界面下的TCP/IPv4高级设定部分取消对NETBIOS over TCP/IP的支持选框。 若是怀疑存在配置上的偏差,则利用带有调试标志的手动启动指令获取详细的报错详情以便快速修正不当之处。例如,在命令提示符窗口中键入 `httpd.exe -t` 来测试当前配置的有效性和完整性。 最后,考虑到操作系统层面的因素影响,执行简单的Winsock恢复操作或许就能让一切恢复正常运作状态。只需简单地在管理员权限下运行CMD并输入 `netsh winsock reset` 命令即可完成修复过程。 ```bash # 测试配置文件合法性 httpd.exe -t # 手动指定实例名称和服务动作启动Apache httpd.exe -w -n "Apache2" -k start ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值