IO模型之IO多路复用

文章介绍了在服务器端如何处理fgets和accept两个阻塞函数的同步问题,提出了四种方法,重点讲解了使用IO多路复用,特别是select函数,来实现对多个文件描述符的监听,使得fgets和accept能独立执行,解决了资源浪费和同步难题。
摘要由CSDN通过智能技术生成

1. 实例代码以及说明

while(1)
{
    fgets(buf, 128, stdin);
    buf[strlen(buf) - 1] = '\0';
    printf("buf = %s\n", buf);

    if(accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen) == -1)
    {
        ERRLOG("accept error");
    }
    printf("客户端[%s - %d]连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
}

以上代码在运行的过程中,必须先执行fgets,后执行accept,所以如果事先有客户端连接服务器,由于服务器端代码没有来得及执行accept,所以就无法直接连接,必须等fgets执行完毕后才能执accept,

如何实现让这两个阻塞函数独立执行呢?

方法1:阻塞IO,无法解决问题

方法2:非阻塞IO,将fgets和accept都设置为非阻塞,确实可以实现功能,但是非阻塞要轮询每次都判断,没数据就立即返回,所以比较浪费资源

方法3:使用多进程或者多线程,也可以实现想要的功能,但是涉及进程或者线程就要考虑资源释放问题,所以也比较麻烦

方法4:使用IO多路复用,这是专门用于解决这类问题的方法

2. IO多路复用的流程

基本思想是:

先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。

函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

IO多路复用的核心思想:

fgets和accept函数在执行之前,让数据先写入到指定文件描述符的缓冲区中,所以如果缓冲区中有数据,判断是哪个文件描述符标识,然后直接执行对应的阻塞函数就解决这个问题了

3. 使用select函数实现IO多路复用

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:
允许一个程序操作多个文件描述符,阻塞等待文件描述符准备就绪,如果有文件描述符准备就绪,则返回立即返回,然后执行相应的IO操作
参数:
nfds:
最大的文件描述符加1
readfds:
保存读操作的文件描述符集合
writefds:
保存写操作的文件描述符集合
exceptfds:
保存其他或者异常操作的文件描述符集合
timeout:
超时
NULL 阻塞
返回值:
成功:
准备就绪的文件描述符的个数
失败:
-1
    
清空集合set    
void FD_ZERO(fd_set *set);

将文件描述符fd添加到集合set中
void FD_SET(int fd, fd_set *set);

将文件描述符fd从集合set中移除
void FD_CLR(int fd, fd_set *set);

判断文件描述符fd是否在集合set中
int  FD_ISSET(int fd, fd_set *set);
返回值:
1  存在
0  不存在

4. 相关代码

//TCP网络编程之服务器

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

//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[128] = {0};

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转化为无符号32位整形数据
    //htons:将主机字节序转化为网络字节序
    //atoi:将数字型字符串转化为整形数据   "1568" --> 1568
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //第四步:将套接字设置为被动监听状态
    if(listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }

    //使用select函数实现IO多路复用
    //第一步:创建保存要操作的文件描述符的集合并清空
    fd_set readfds;
    FD_ZERO(&readfds);

    int maxfd = sockfd;
    int ret;

    while(1)
    {
        //第二步:将要操作的文件描述符添加到集合中
        //fgets() --> 0
        //accept() --> sockfd
        FD_SET(0, &readfds);
        FD_SET(sockfd, &readfds);

        //第三步:调用select函数阻塞等待文件描述符准备就绪
        if((ret = select(maxfd+1, &readfds, NULL, NULL, NULL)) == -1)
        {
            ERRLOG("select error");
        }

        printf("ret = %d\n", ret);

        //第四步:如果有文件描述符准备就绪,select函数立即返回,
        //注意:当select返回之后,第一时间会移除集合中没有准备就绪的
        //     文件描述符,所以判断集合中还剩哪个没有被移除,说明就是
        //     准备就绪的
        //判断哪个文件描述符还在集中,接下来执行相应的IO操作
        if(FD_ISSET(0, &readfds) == 1)
        {
            fgets(buf, 128, stdin);
            buf[strlen(buf) - 1] = '\0';
            printf("buf = %s\n", buf);
        }

        if(FD_ISSET(sockfd, &readfds) == 1)
        {
            if(accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen) == -1)
            {
                ERRLOG("accept error");
            }
            printf("客户端[%s - %d]连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
        }        
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小徐的记事本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值