文件操作
低级别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
- 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
结构体数组的指针,每个结构体描述了一个文件描述符以及需要检查的事件。nfds
:fds
数组中的结构体数量。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
函数将fdtty9
和fdtty10
添加到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;
}