select函数实现多路io复用

注:本文章着重介绍select关于客户端代码不详细解释

select函数介绍

select是一个用于多路I/O复用的系统调用函数,常用于监听多个文件描述符上是否有读写事件发生。它的原型为:

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

  1. nfds表示监听的文件描述符集合中最大的文件描述符值加1。
  2. readfds、writefds和exceptfds分别表示待检查的读、写和异常事件集合,它们都是指向fd_set类型的指针。
  3. timeout用于指定select函数等待的最长时间,它也是一个结构体类型。

select函数的返回值表示发生事件的文件描述符的个数,如果返回值为0,则表示在指定时间内没有任何事件发生,如果出错则返回-1。

在使用select函数之前需要对文件描述符集合进行初始化。fd_set类型是一个位图,每一个位表示一个文件描述符。可以通过下面的宏来操作fd_set类型:

FD_ZERO(fd_set *set):将set清零,初始化为一个空集。
FD_SET(int fd, fd_set *set):将set集合中的第fd个描述符设为1。
FD_CLR(int fd, fd_set *set):将set集合中的第fd个描述符设为0。
FD_ISSET(int fd, fd_set *set):如果set集合中的第fd个描述符被设置,则返回1,否则返回0。

select使用流程

select函数的使用流程一般如下:

  1. 创建并初始化fd_set类型的读、写、异常事件集合;
  2. 将待检查的文件描述符添加到对应的集合中;
  3. 调用select函数,并设置超时时间;
  4. 检查返回值,如果出错则处理错误,否则根据FD_ISSET宏来检查哪些文件描述符发生了事件;
  5. 处理事件。

程序代码

server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("use:fd\n");
        return -1;
    }
    //1.socket
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket err.");
        return -1;
    }
    //2.listen_fd
    struct sockaddr_in listen_addr, accept_addr;
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(atoi(argv[1]));
    listen_addr.sin_addr.s_addr = INADDR_ANY;

    socklen_t len = sizeof(accept_addr);

    //3.bind
    if (bind(socked, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0)
    {
        perror("bind err.");
        return -1;
    }
    //4.listen
    listen(socked, 10);
    //1>create fd_fet
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    FD_SET(0, &readfds);
    FD_SET(socked, &readfds);

    int max_fd = socked;
    int recv_num;
    int accept_num;
    char buf[128];
    while (1)
    {
        tempfds = readfds;
        int ret = select(max_fd + 1, &tempfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select err.");
            return -1;
        }
        if (FD_ISSET(0, &tempfds))
        {
            fgets(buf, sizeof(buf), stdin);
            printf("key:%s\n", buf);
            for (int i = 4; i <= max_fd; i++)
            {
                if (FD_ISSET(i, &readfds))
                {
                    send(i, buf, sizeof(buf), 0);
                }
            }
        }
        if (FD_ISSET(socked, &tempfds))
        {
            accept_num = accept(socked, (struct sockaddr *)&accept_addr, &len);
            if (accept_num < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("duan:%d ip:%s talk:%s \n",
                   ntohs(accept_addr.sin_port), inet_ntoa(accept_addr.sin_addr),
                   buf);
            FD_SET(accept_num, &readfds);
            if (max_fd < accept_num)
            {
                max_fd = accept_num;
            }
        }
        for (int i = 4; i <= max_fd; i++)
        {
            if (FD_ISSET(i, &tempfds))
            {
                recv_num = recv(i, buf, sizeof(buf), 0);
                if (recv_num < 0)
                {
                    perror("recv err.");
                    return -1;
                }
                else if (recv_num == 0)
                {
                    printf("client is exit\n");
					close(i);
                    FD_CLR(i, &readfds);
                    if (i == max_fd)
                    {
                        max_fd--;
                    }
                    break;
                }
                else
                {
                    printf("%d client:%s\n", i, buf);
                }
            }
        }
        // close(accept_fd);这里无关闭
    }
    close(socked);
    return 0;
}

代码分析

这是一个简单的TCP服务器程序,它监听指定的端口,当有客户端连接时,服务器会接受连接,并将客户端的套接字加入到fd_set集合中,然后继续等待其他客户端的连接或者客户端发送数据。

程序中使用了select函数来实现I/O多路复用,同时使用了fd_set类型来存储多个文件描述符。当某个文件描述符上有可读事件时,select会返回,然后程序会对所有处于读就绪状态的文件描述符进行处理。

下面是程序的主要流程:

  1. 首先检查参数个数是否正确,如果不正确则提示用法并退出程序。
  2. 创建套接字。
  3. 绑定端口。
  4. 监听端口。
  5. 使用fd_set类型创建一个文件描述符集合,并将标准输入文件描述符和服务器套接字文件描述符加入集合中。
  6. 进入无限循环,在循环中使用select函数检查文件描述符集合中是否有就绪的文件描述符,如果有则进行处理。
  7. 如果标准输入文件描述符就绪,表示有数据从键盘输入,将数据发送给所有客户端。
  8. 如果服务器套接字文件描述符就绪,表示有客户端连接,接受连接,并将客户端套接字文件描述符加入文件描述符集合中。
  9. 对于其他处于就绪状态的文件描述符,接收数据并输出。
  10. 重复步骤6到9。

为了验证代码这里分享一段客户端代码来进行验证

client(非本文重点)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("use:fd\n");
        return -1;
    }
    //1.socket
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket err.");
        return -1;
    }
    //2.tianchong
    struct sockaddr_in listen_addr;
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(atoi(argv[1]));
    listen_addr.sin_addr.s_addr = INADDR_ANY;

    int con_val = connect(socked, (struct sockaddr *)&listen_addr, sizeof(listen_addr));
    if (con_val < 0)
    {
        perror("con_val err.");
        return -1;
    }
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fork err.");
    }
    else if (pid == 0)
    {
        char buf[128];
        ssize_t ret;
        while (1)
        {
            ret = recv(socked, buf, sizeof(buf), 0);
            if (ret < 0)
            {
                perror("recv err");
                return -1;
            }
            else if (ret == 0)
            {
                printf("server is exit\n");
                break;
            }
            else
            {
                printf("%s\n", buf);
            }
        }
    }
    else
    {
        char buf[128];
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
            {
                buf[strlen(buf) - 1] == '\0';
            }
            send(socked, buf, sizeof(buf), 0);//这里
        }
    }
    return 0;
}

结果展示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值