IO多路复用 --select和poll函数

可以通过IO多路服用技术,实现全双工服务器

什么是IO多路复用

同时对多个文件描述符进行监听,当文件描述符发生相应事件的时候,返回这个文件描述符

IO多路复用基本思想(机制)

  • 先构建一张关于文件描述符的表,然后调用一个函数
  • 返回在这些文件描述符一个或多个发生I/O操作时才会返回,否则阻塞
  • 通过函数返回,判断对哪一个文件描述符进行操作

**实现IO多路复用的方式 **

select

/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
fd_set readfds
int select(int nfds, fd_set *readfds, fd_set *writefds,
        fd_set *exceptfds, struct timeval *timeout);
    功能:等待对应文件描述符表产生操作,只保留表中有事件发生的文件描述符
        nfds	 	表的大小
        readfds   	等待件描述符表读事件发生
        writefds 	等待件描述符表写事件发生
        exceptfds 	等待件描述符表异常事件发生
void FD_CLR(int fd, fd_set *set);
    在表中删除文件描述符fd
int  FD_ISSET(int fd, fd_set *set);
    判断表里的文件描述符是不是 fd
void FD_SET(int fd, fd_set *set);
    将fd写入表中
void FD_ZERO(fd_set *set);
    清空表

步骤:

  1. 定义表fd_set rfs,tfd;
  2. 清空表FD_ZERO(&rfs)
  3. 填表 FD_SET(0,&rfs)
  4. 监听表
    1. tfd = rfd
    2. select(max+1,&tfd,NULL,NULL,NULL)
  5. 判断表FD_ISSET(0,&tfd)

例子

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

int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        perror("usage:<ip><port>");
        return -1;
    }

    // 1套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    // 2绑定
    struct sockaddr_in ad;
    // 从终端输入获取IP和端口
    ad.sin_addr.s_addr = inet_addr(argv[1]);
    ad.sin_port = htons(atoi(argv[2]));
    ad.sin_family = AF_INET;
    socklen_t len = sizeof(ad);
    if (bind(sfd, (struct sockaddr *)&ad, len) < 0)
    {
        perror("file");
        return -1;
    }
    listen(sfd, 8);

    // 创建表
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);

    //填充表
    FD_SET(0, &readfds);
    FD_SET(sfd, &readfds);
    // 记录有几个接入的端口
    int max = sfd;
    char buf[64] = "";
    while (1)
    {

        tempfds = readfds;
        int ret = select(max + 1, &tempfds, NULL, NULL, NULL);
        // 链接套接字接收到链接信息
        if (FD_ISSET(sfd, &tempfds))
        {
            int acceptfd = accept(sfd, (struct sockaddr *)&ad, &len);
            // 加入表
            FD_SET(acceptfd, &readfds);
            if (max < acceptfd)
            {
                max = acceptfd;
            }
        }
        if (FD_ISSET(0, &tempfds))
        {
            // 发送前先清空
            memset(buf, 0, 64);
            // 获取缓存区字符
            gets(buf);
            // 客户端文件文件描述符从4开始
            // 信息发送给所有文件
            for (int i = 4; i <= max; i++)
            {   
                // 如果读取表中有该文件描述符
                if (FD_ISSET(i, &readfds))
                {
                    // 发送
                    send(i, buf, 64, 0);
                }
            }
        }
        // 判断每一个文件描述符在缓存区有无消息
        for (int i = 4; i <= max; i++)
        {
            if (FD_ISSET(i, &tempfds))
            {
                int ret = recv(i, buf, 64, 0);
                if(ret<0)
                {
                    perror("ret err");
                    return -1;
                }
                else if (ret > 0)
                {
                    printf("接收到%s\n", buf);
                }
                else // =0 端口退出
                {
                    printf("%d,quit\n", i);
                    // 关闭文件描述符
                    close(i);
                    FD_CLR(i, &readfds);
                    while (!FD_ISSET(max, &readfds))
                    {
                        max--;
                    }
                }
                memset(buf, 0, 64);
            }
        }
    }

    return 0;
}

poll

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds,int timeout);
    功能:监视并等待多个文件描述符的属性变化
    参数:
    struct pollfd *fds 	:结构体数组
    nfds_t nfds			:数组大小
    int timeout:指定等待的毫秒数,无论 I/O 是否准备好,poll() 都会返回.
        设置为-1,则会在事件发生前一直阻塞
结构体:
struct pollfd{
	int fd;			//文件描述符
	short events;	//等待的事件
	short revents;	//实际发生的事件
};

步骤

  1. 创建表(结构体数组)
  2. 填充表(数组)
  3. 监听表
  4. 判断表

例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <poll.h>

int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        perror("file");
        return -1;
    }

    // 1套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    // 2绑定
    struct sockaddr_in ad;
    // ad.sin_addr.s_addr = inet_addr(argv[1]);
    ad.sin_addr.s_addr =INADDR_ANY;

    ad.sin_family = AF_INET;
    ad.sin_port = htons(atoi(argv[2]));
    socklen_t len = sizeof(ad);
    if (bind(sfd, (struct sockaddr *)&ad, len) < 0)
    {
        perror("file");
        return -1;
    }
    listen(sfd, 8);
    char buf[64] = "";
    // 创建表
#define N 10
    struct pollfd fds[N];
    int last = -1;      // 初始表长度
    fds[++last].fd = 0; /*  */
    fds[last].events = POLLIN;
    fds[++last].fd = sfd;
    fds[last].events = POLLIN;

    while (1)
    {
        // 结构体,大小,无定时.无操作时一直阻塞
        poll(fds, last + 1, -1);
        for (int i = 0; i <= last; i++)
        {
            // 如果文件描述符i有输入
            if (fds[i].revents == POLLIN)
            {
                // 判断输入来源
                // 如果是链接套接字
                if (fds[i].fd == sfd)
                {
                    // 创建通信套接字
                    int afd = accept(sfd, (struct sockaddr *)&ad, &len);
                    fds[++last].fd = afd;
                    fds[last].events = POLLIN;
                    fds[last].revents = 0;
                }
                // 如果是标准输入
                else if (fds[i].fd == 0)
                {

                    memset(buf, 0, 64);
                    gets(buf);
                    // 注意,我们要将信息发送给所有客户端,所有需要重新遍历,注意开始位置
                    for (int j = 2; j <= last; j++)
                    {

                        int s = send(fds[j].fd, buf, 64, 0);
                    }
                }
                // 接收,如果不是标准输入,也不是连接套接字输入,那就是有端口发来了消息,属于信息套接字有输入
                else
                {
                    int ret = recv(fds[i].fd, buf, 64, 0);
                    if (ret < 0)
                    {
                        perror("ret err");
                    }
                    else if (ret > 0)
                    {
                        printf("%s\n", buf);
                        memset(buf, 0, 64);
                    }
                    // =0 有端口退出
                    else
                    {
                        printf("%d:quit\n", fds[i].fd);
                        close(fds[i].fd);
                        fds[i--] = fds[last--];
                    }
                }
            }
        }
    }

    return 0;
}

select和poll优缺点

select

优点:

  • 轻量级,跨平台(LINUX,MACOS,Windows都可用),可以同时监听多个文件描述符。

缺点:

  • 最大监听文件描述符数量为1024,最大监听1020个客户端,
  • 每次要进行轮询比较消耗CPU
  • 每次都要拷贝一遍表

poll

优点:

  • 对比select,能监听的文件描述符数可以更多,能监听的文件描述符数取决于数组的大小,数组大小取决于内存。
  • 不需要每次都重新构建文件描述符表,传入一次即可。

缺点:

  • 需要轮询
  • 只能用在UNIX原生系统下,不支持跨平台
  • 18
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值