牛客Day4 I/O复用

I/O 多路复用

I/O 多路复用,也叫I/O多路转接。
I/O 多路复用使得程序能同时监听多个文件描述符 ,能够提高程序的性能,Linux 下实现 I/O 多路复用的
系统调用主要有 select、poll 和 epoll。
网络程序在下列情况下需要使用I/O复用技术

  • 客户端程序要同时处理多个socket
  • 客户端程序要同时处理用户输入和网络连接
  • TCP服务器要同时处理监听socket和连接socket
  • 服务器要同时处理TCP请求和UDP请求
  • 服务器要同时监听多个端口

阻塞等待:
好处: 不占用cpu宝贵的时间片
缺点:同一时刻只能处理一个操作,效率低
解决方案

  1. 多进程多线程解决
    缺点: 线程或者进程会消耗资源
    线程或进程的调用会消耗cpu资源
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

slect

主旨思想:
1 构造一个关于文件描述符的列表,列表中放的是将要监听的文件描述符,将要监听的文件描述符添加到该列表中。
2 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或多个I/O操作时,该函数才返回。
a 这个函数是阻塞的
b 函数对文件描述符的检测的操作时由内核完成的
3 在返回是,他会告诉进程有多少(那些)描述符要进行I/O操作

		// sizeof(fd_set) = 128 1024
       /* 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>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
            - 参数:
					- nfds : 委托内核检测的最大文件描述符的值 + 1
					- readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
								- 一般检测读操作
								- 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区
								- 是一个传入传出参数
					- writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
								- 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)
					- exceptfds : 检测发生异常的文件描述符的集合
					- timeout : 设置的超时时间
                    struct timeval {
							long tv_sec; /* seconds */
							long tv_usec; /* microseconds */
							};
					- NULL : 永久阻塞,直到检测到了文件描述符有变化
					- tv_sec = 0 tv_usec = 0, 不阻塞
					- tv_sec > 0 tv_usec > 0, 阻塞对应的时间

	- 返回值 :
			- -1 : 失败
			- >0(n) : 检测的集合中有n个文件描述符发生了变化
		 将参数文件描述符fd对应的标志位设置为0
       void FD_CLR(int fd, fd_set *set);
       // 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
       int  FD_ISSET(int fd, fd_set *set);
       // 将参数文件描述符fd 对应的标志位,设置为1
       void FD_SET(int fd, fd_set *set);
       // fd_set一共有1024 bit, 全部初始化为0
       void FD_ZERO(fd_set *set);
       

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
fd_set结构体的定义如下:

#include<typesizes.h>
#define__FD_SETSIZE 1024
#include<sys/select.h>
#define FD_SETSIZE__FD_SETSIZE
typedef long int__fd_mask;
#undef__NFDBITS
#define__NFDBITS(8*(int)sizeof(__fd_mask))
typedef struct
{ #
ifdef__USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;

演示代码:

服务器端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

int main() {

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 监听
    listen(lfd, 8);

    // 创建一个fd_set的集合,存放的是需要检测的文件描述符
    fd_set rdset, tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd, &rdset);
    int maxfd = lfd;

    while(1) {

        tmp = rdset;

        // 调用select系统函数,让内核帮检测哪些文件描述符有数据
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        if(ret == -1) {
            perror("select");
            exit(-1);
        } else if(ret == 0) {
            continue;
        } else if(ret > 0) {
            // 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
            if(FD_ISSET(lfd, &tmp)) {
                // 表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                // 将新的文件描述符加入到集合中
                FD_SET(cfd, &rdset);

                // 更新最大的文件描述符
                maxfd = maxfd > cfd ? maxfd : cfd;
            }

            for(int i = lfd + 1; i <= maxfd; i++) {
                if(FD_ISSET(i, &tmp)) {
                    // 说明这个文件描述符对应的客户端发来了数据
                    char buf[1024] = {0};
                    int len = read(i, buf, sizeof(buf));
                    if(len == -1) {
                        perror("read");
                        exit(-1);
                    } else if(len == 0) {
                        printf("client closed...\n");
                        close(i);
                        FD_CLR(i, &rdset);
                    } else if(len > 0) {
                        printf("read buf = %s\n", buf);
                        write(i, buf, strlen(buf) + 1);
                    }
                }
            }

        }

    }
    close(lfd);
    return 0;
}

客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 连接服务器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};

        sprintf(sendBuf, "send data %d", num++);
        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服务器已经断开连接...\n");
            break;
        }
        // sleep(1);
        usleep(1000);
    }

    close(fd);

    return 0;
}

select 的缺点

poll

poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll的原型如下:

#include<poll.h>
int poll(struct pollfd*fds,nfds_t nfds,int timeout);

参数说明

#include <poll.h>
struct pollfd {
		int fd; /* 委托内核检测的文件描述符 */
		short events; /* 委托内核检测文件描述符的什么事件 */
		short revents; /* 文件描述符实际发生的事件 */
};

struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
	- 参数:
		- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合,传入传出参数。
		- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
		- timeout : 阻塞时长
			0 : 不阻塞
			-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
			>0 : 阻塞的时长
		- 返回值:
			-1 : 失败
			>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化

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

epoll

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
牛客 a卷2022年第四季度的华为题目中,要求考生设计一种高效的数据结构,能够支持以下几种操作: 1. 添加一个元素 2. 删除一个元素 3. 查找是否存在某个元素 4. 返回元素的总数 该数据结构要求满足空间复杂度较小、时间复杂度较低、能够快速地进行查找和修改等多种操作。 想要编写这样一种数据结构,我们可以参考许多已有的经典算法与数据结构,如二叉树、哈希表、红黑树等,通过综合利用它们的优点来实现这个问题的解决。 例如,我们可以通过哈希表来存储所有元素的值,并在每个哈希链表的元素中再使用红黑树来进行排序与查找。这样,我们既能够轻松地进行元素的添加和删除操作,也能够在查找较大数据范围和数量时保持较高的速度与效率。同时,由于使用了多个数据结构来协同完成这个问题,我们也能够在空间复杂度上适度地进行优化。 当然,在具体设计这个数据结构的过程中,我们还需要考虑一些实践中的细节问题,例如如何避免哈希冲突、如何处理数据丢失与被删除元素所占用的空间等问题,这都需要相应的算法与流程来进行处理。 总体来看,设计这种支持多种操作的高效数据结构,需要我们具备丰富的算法知识和编程实践能力,同时需要我们在具体处理问题时能够将多种算法和数据结构进行有效地结合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值