文件操作与IO多路复用.md

17 篇文章 0 订阅

文件操作

低级别IO :只能用在POSIX, unix系统支持,Linux系统支持,类unix支持

POSIX文件操作(无缓冲)

open,close,read,write,lseek

int open(const char *pathname, int flags);

  • pathname: 要打开的文件路径名
  • flags: 打开方式,如O_RDONLY,O_WRONLY等
  • 返回值: 文件描述符,成功则返回非负整数,失败返回-1

int close(int fd);

  • fd: 要关闭的文件描述符
  • 返回值: 成功返回0,失败返回-1
  1. read

ssize_t read(int fd, void *buf, size_t count);

  • fd: 要读取的文件描述符
  • buf: 读入数据的缓冲区
  • count: 要读取的字节数
  • 返回值: 成功返回读入的字节数,失败返回-1

ssize_t write(int fd, const void *buf, size_t count);

  • fd: 要写入的文件描述符
  • buf: 存储写入数据的缓冲区
  • count: 要写入的字节数
  • 返回值:成功返回写入的字节数,失败返回-1

off_t lseek(int fd, off_t offset, int whence);

  • fd: 要设置偏移的文件描述符
  • offset: 偏移量
  • whence: 偏移起始位置(SEEK_SET, SEEK_CUR, SEEK_END)
  • 返回值:成功返回新的文件读写偏移位置,失败返回-1
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {

  int fd = open("file.txt", O_RDONLY);
  if (fd == -1) {
    perror("open file failed");
    return 1;
  }
  
  char buf[1024];
  ssize_t numRead = read(fd, buf, sizeof(buf));
  if (numRead == -1) {
    perror("read file failed");
    close(fd);
    return 1; 
  }

  ssize_t numWritten = write(STDOUT_FILENO, buf, numRead);
  if (numWritten == -1) {
    perror("write failed");
    close(fd);
    return 1;
  }

  if (close(fd) == -1) {
    perror("close file failed");
    return 1;
  } 

  return 0;
}

高级IO:标准C提供的函数有缓冲区的概念(减少系统调用read/write)

fopen,fclose,fputc,fgetc,fputs,fgets,fread,fwrite

C语言FILE指针文件操作相关函数原型及参数返回值说明如下:

FILE *fopen(const char *filename, const char *mode);

  • filename:文件名
  • mode:打开模式如"r",“w”,"a"等
  • 返回值:文件指针,成功则返回文件指针,失败返回NULL

int fclose(FILE *stream);

  • stream:要关闭的文件指针
  • 返回值:成功返回0,失败返回EOF

int fputc(int char, FILE *stream);

  • char:要写入的字符
  • stream:文件指针
  • 返回值:写入的字符,如果失败返回EOF

int fgetc(FILE *stream);

  • stream:文件指针
  • 返回值:读取的字符,如果达到结尾或者失败,返回EOF

int fputs(const char *s, FILE *stream);

  • s:要写入的字符串
  • stream:文件指针
  • 返回值:成功返回非负值,失败返回EOF

char *fgets(char *s, int size, FILE *stream);

  • s:存储读取字符串的缓冲区
  • size:缓冲区大小
  • stream:文件指针
  • 返回值:成功返回缓冲区指针s,失败返回NULL

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  • ptr:数据存储缓冲区指针
  • size:每个元素长度
  • nmemb:读取元素个数
  • stream:文件指针
  • 返回值:成功返回读取的元素总数,失败返回0

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

  • ptr:待写入数据缓冲区指针
  • size:每个元素长度
  • nmemb:写入元素个数
  • stream:文件指针
  • 返回值:成功返回写入的元素总数,失败返回0
#include <stdio.h>
#include <errno.h>

int main() {

  FILE *fp = fopen("file.txt", "r");
  if(fp == NULL) {
    perror("fopen");
    return 1;
  }

  int c = fgetc(fp);
  if(c == EOF) {
    perror("fgetc");
    fclose(fp);
    return 1;
  }

  if(fputc(c, stdout) == EOF) {
    perror("fputc");
    fclose(fp);
    return 1;
  }

  char str[100];
  if(fgets(str, 100, stdin) == NULL) {
    perror("fgets");
    fclose(fp);
    return 1;
  }

  if(fputs(str, fp) == EOF) {
    perror("fputs");
    fclose(fp); 
    return 1;
  }

  char buf[128];
  size_t n = fread(buf, 1, 128, fp);
  if(n == 0) {
    perror("fread");
    fclose(fp);
    return 1;
  }

  if(fwrite(buf, 1, n, stdout) != n) {
    perror("fwrite");
    fclose(fp);
    return 1;
  }

  if(fclose(fp) == EOF) {
    perror("fclose");
    return 1;
  }  

  return 0;
}

C++中,输入输出数据的传送过程我们称之为流,一个流就是一个字节序列,对流可以进行读或写操作。

https://zh.cppreference.com/w/cpp/io
https://www.runoob.com/cplusplus/cpp-files-streams.html

C++中的输入输出都是建立在流的概念之上的。
C++中定义了以下几种流:

  • istream - 输入流,用于从文件或缓冲区读取数据
  • ostream - 输出流,用于向文件或缓冲区写入数据
  • iostream - 输入输出流,既可以读取也可以写入
    所有这些流都是抽象基类,实际使用时常见的派生类有:
  • ifstream - 从文件读取输入
  • ofstream - 向文件写入输出
  • fstream - 读写文件
  • istringstream - 从字符串读取输入
  • ostringstream - 向字符串写入输出
  • stringstream - 读写字符串
    这些流类封装了文件和字符串的输入输出机制,将其作为一个字节序列来读写。我们可以创建这些流对象,然后使用类似cin、cout的方式进行数据传输,使代码更简洁。

cout 是标准输出流对象,定义在 iostream 头文件中。
iostream 是 C++ 标准库中的一个重要的头文件,它提供了许多标准的流对象,比如:

  • cin - 标准输入流对象
  • cout - 标准输出流对象
  • cerr - 标准错误流对象
    cout 对象定义在 iostream 头文件中,需要包含这个头文件才能使用 cout。

#include <fstream>
#include <iostream>
#include <string>

int main() {
  std::fstream fs;//  ifstream input("input.txt", ios::in );
  fs.open("file.txt", std::fstream::in | std::fstream::out);

  if(!fs.is_open()) {
    std::cerr << "Error opening file" << std::endl;
    return 1;
  }

  char c = fs.get();
  if(fs.fail()) {
    std::cerr << "Error reading character" << std::endl;
    fs.close();
    return 1;
  }

  std::cout << c;
  
  std::string s;
  std::getline(std::cin, s);

  fs << s << std::endl;
  if(fs.fail()) {
    std::cerr << "Error writing string" << std::endl;
    fs.close();
    return 1; 
  }

  std::string line;
  std::getline(fs, line);
  if(fs.fail()) {
    std::cerr << "Error reading line" << std::endl;
    fs.close();
    return 1;
  }

  std::cout << line << std::endl;

  fs.close();
  return 0;
}

IO多路复用

I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作

同步IO操作:select

select是通过描述符位图的方式监视文件描述符集合的可读写状态。调用select会阻塞直到有描述符就绪或超时,监视这些描述符,返回就绪的描述符集合。需要调用者主动去轮询描述符的状态。

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

参数解释:

  • nfds:监视的文件描述符最大值+1,表示调用者关心的描述符的范围
  • readfds:监视有读数据可用的文件描述符集合
  • writefds:监视写数据不被阻塞的文件描述符集合
  • exceptfds:监视异常发生的文件描述符集合
  • timeout:超时时间,NULL表示无限等待

返回值:

  • 成功返回就绪的文件描述符数量
  • 失败返回-1,错误码存于errno
  • 超时返回0

#include <stdio.h>

#include <sys/select.h>

#include <fcntl.h>

#include <string.h>

int main(int argc,char **argv)

{

    int maxfd;

    char buf[100];

    int fdtty9,fdtty10;

    int ret;

    fd_set fdsr;

    fdtty9 =open("/dev/tty9",O_RDONLY);

    fdtty10 =open("/dev/tty10",O_RDONLY);

    maxfd =(fdtty9>fdtty10?fdtty9:fdtty10);

    //fdsr 输入

    while(1){

       FD_ZERO(&fdsr);

       FD_SET(fdtty9,&fdsr);

       FD_SET(fdtty10,&fdsr);

       ret =select(maxfd+1,&fdsr,NULL,NULL,NULL);

       //fdsr 输出

       if(ret > 0){

           if(FD_ISSET(fdtty9,&fdsr)){

              bzero(buf,sizeof(buf));

              read(fdtty9,buf,sizeof(buf));

              printf("tty9:%s\n",buf);

           }

           if(FD_ISSET(fdtty10,&fdsr)){

              bzero(buf,sizeof(buf));

              read(fdtty10,buf,sizeof(buf));

              printf("tty10:%s\n",buf);

           }

       }

    }

}

同步IO操作:poll

poll使用链表的数据结构监视描述符集合,可以更高效地监视大数量的描述符。调用poll也会阻塞。监视这些描述符,返回就绪的描述符集合。需要调用者主动去轮询描述符的状态。

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数解释:

  • fds:指向一个pollfd结构体数组的指针,每个结构体描述了一个文件描述符以及需要检查的事件。
  • nfdsfds数组中的结构体数量。
  • timeout:超时时间,以毫秒为单位。如果设置为负数,则表示无限等待;如果设置为0,则表示立即返回;如果设置为正数,则表示等待指定的毫秒数。

返回值解释:

  • 如果成功,返回准备好的文件描述符数量。
  • 如果超时,返回0。
  • 如果出错,返回-1,并设置errno来指示错误的类型。

#include <stdio.h>

#include <poll.h>

#include <fcntl.h>

#include <string.h>

int main(int argc,char **argv)

{

    char buf[100];

    int fdtty9,fdtty10;

    int ret;

    fdtty9 =open("/dev/tty9",O_RDONLY);

    fdtty10 =open("/dev/tty10",O_RDONLY);

    struct pollfd fds[2];

    fds[0].fd = fdtty9;

    fds[0].events = POLLIN;

    fds[1].fd = fdtty10;

    fds[1].events = POLLIN;

    while(1){

       ret = poll(fds,2,-1);

        if(ret >0){

           if(fds[0].revents ==POLLIN){

              bzero(buf,sizeof(buf));

              read(fdtty9,buf,sizeof(buf));

              printf("tty9:%s\n",buf);

           }

           if(fds[1].revents ==POLLIN){

              bzero(buf,sizeof(buf));

              read(fdtty10,buf,sizeof(buf));

              printf("tty10:%s\n",buf);

           }

       }

    }

}

异步IO操作:epoll

epoll使用一个epoll实例来管理多个文件描述符,而不是直接操作文件描述符集合。因此,在使用epoll时需要先创建epoll实例,然后使用epoll_ctl函数来添加、修改或删除事件,最后使用epoll_wait函数来等待事件发生。

epoll函数原型为:

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数解释:

  • size:epoll实例的大小,用于指定内核事件表的容量,通常设置为大于0的整数。
  • epfd:epoll实例的文件描述符,用于标识一个epoll实例。
  • op:epoll_ctl操作类型,可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD或EPOLL_CTL_DEL,用于添加、修改或删除事件。
  • fd:需要添加、修改或删除的文件描述符。
  • event:指向epoll_event结构体的指针,用于描述事件类型和相关的数据。
  • events:指向epoll_event结构体数组的指针,用于存储发生的事件。
  • maxevents:events数组的大小,用于指定最大存储的事件数量。
  • timeout:超时时间,以毫秒为单位。如果设置为负数,则表示无限等待;如果设置为0,则表示立即返回;如果设置为正数,则表示等待指定的毫秒数。

返回值解释:

  • epoll_create:如果成功,返回一个新的epoll实例的文件描述符;如果出错,返回-1,并设置errno来指示错误的类型。
  • epoll_ctl:如果成功,返回0;如果出错,返回-1,并设置errno来指示错误的类型。
  • epoll_wait:如果成功,返回发生的事件数量;如果超时,返回0;如果出错,返回-1,并设置errno来指示错误的类型。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>

int main() {
    int fdtty9, fdtty10;
    int maxfd;
    int ret;

    fdtty9 = open("/dev/tty9", O_RDONLY);
    if (fdtty9 == -1) {
        perror("open tty9");
        exit(EXIT_FAILURE);
    }

    fdtty10 = open("/dev/tty10", O_RDONLY);
    if (fdtty10 == -1) {
        perror("open tty10");
        exit(EXIT_FAILURE);
    }

    maxfd = (fdtty9 > fdtty10 ? fdtty9 : fdtty10);

    int epfd = epoll_create(1);
    if (epfd == -1) {
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }

    struct epoll_event event;
    event.events = EPOLLIN;

    event.data.fd = fdtty9;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fdtty9, &event);
    if (ret == -1) {
        perror("epoll_ctl fdtty9");
        exit(EXIT_FAILURE);
    }

    event.data.fd = fdtty10;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fdtty10, &event);
    if (ret == -1) {
        perror("epoll_ctl fdtty10");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[2];

    while (1) {
        ret = epoll_wait(epfd, events, 2, -1);
        if (ret == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < ret; i++) {
            if (events[i].data.fd == fdtty9) {
                printf("Data is available to read from tty9\n");
            }

            if (events[i].data.fd == fdtty10) {
                printf("Data is available to read from tty10\n");
            }
        }
    }

    close(fdtty9);
    close(fdtty10);
    close(epfd);

    return 0;
}

在上面的示例中,我们使用epoll_create函数创建了一个epoll实例,并使用epoll_ctl函数将fdtty9fdtty10添加到epoll实例中。然后,我们使用epoll_wait函数等待事件发生,并使用events数组来存储发生的事件。在循环中,我们遍历返回的事件数组,并根据文件描述符判断是来自fdtty9还是fdtty10的数据可读。当有数据可读时,打印相应的消息。

#include <sys/epoll.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_EVENTS 10

int main(int argc, char* argv[]) {

  int listenFd = socket(AF_INET, SOCK_STREAM, 0);

  // 设置socket为非阻塞模式
  fcntl(listenFd, F_SETFL, O_NONBLOCK); 

  struct sockaddr_in serverAddr;
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_addr.s_addr = INADDR_ANY;
  serverAddr.sin_port = htons(8000);

  bind(listenFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));

  listen(listenFd, SOMAXCONN);

  int epollFd = epoll_create(MAX_EVENTS);

  struct epoll_event ev;
  ev.events = EPOLLIN;
  ev.data.fd = listenFd;

  epoll_ctl(epollFd, EPOLL_CTL_ADD, listenFd, &ev);

  struct epoll_event events[MAX_EVENTS];

  while (true) {

    int n = epoll_wait(epollFd, events, MAX_EVENTS, -1);

    for(int i=0; i<n; i++) {

      if(events[i].data.fd == listenFd) {
        
        int clientFd = accept(listenFd, NULL, NULL);
        
        ev.events = EPOLLIN; 
        ev.data.fd = clientFd;
        
        epoll_ctl(epollFd, EPOLL_CTL_ADD, clientFd, &ev);

      } else {

        // 处理客户端连接
        processClient(events[i].data.fd);
      }
    }
  }

  close(listenFd);

  return 0;
}

void processClient(int clientFd) {
  // 处理客户端请求逻辑
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10
#define MAX_CLIENTS 100

int main() {
    int server_fd;
    int epoll_fd;
    struct epoll_event event;
    struct epoll_event events[MAX_EVENTS];


    // 创建服务器套接字并绑定、监听等操作...

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 将服务器套接字添加到 epoll 实例中
    event.data.fd = server_fd;
    event.events = EPOLLIN;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
        perror("epoll_ctl EPOLL_CTL_ADD");
        exit(EXIT_FAILURE);
    }

    while (1) {
        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (num_events == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == server_fd) {
                // 处理新的客户端连接请求
                // 接受连接、添加到 epoll 实例中等操作...
            } else {
                // 处理已连接的客户端套接字的数据读取等操作
                // 读取数据、发送响应等操作...
            }
        }
    }

    close(server_fd);
    close(epoll_fd);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值