在 Linux 系统下,IO(输入/输出)操作是程序与外部设备(如文件、网络等)交互的重要方式。Linux 提供了丰富的系统调用和库函数来支持各种 IO 操作。以下是对 Linux 下 IO 操作的详细解析,包括文件 IO、网络 IO 和缓冲机制等内容。
一、文件 IO 操作
1. 打开文件(open
系统调用)
open
系统调用用于打开一个文件,返回一个文件描述符(file descriptor
),后续操作都通过这个文件描述符进行。
cpp
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
int main() {
int fd = open("example.txt", O_RDONLY); // 打开文件用于只读
if (fd == -1) {
perror("open failed");
return 1;
}
std::cout << "File opened successfully, file descriptor: " << fd << std::endl;
close(fd); // 关闭文件
return 0;
}
-
O_RDONLY
:只读模式。 -
O_WRONLY
:只写模式。 -
O_RDWR
:读写模式。 -
O_CREAT
:如果文件不存在,则创建文件。 -
O_TRUNC
:如果文件已存在,将其长度截断为 0。 -
O_APPEND
:写入时将数据追加到文件末尾。
2. 读取文件(read
系统调用)
read
系统调用从文件描述符指定的文件中读取数据。
cpp
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open failed");
return 1;
}
char buffer[128];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead == -1) {
perror("read failed");
close(fd);
return 1;
}
buffer[bytesRead] = '\0'; // 确保字符串以 null 结尾
std::cout << "Read " << bytesRead << " bytes: " << buffer << std::endl;
close(fd);
return 0;
}
read
返回读取的字节数,如果返回 0 表示已到达文件末尾,返回 -1 表示出错。
3. 写入文件(write
系统调用)
write
系统调用将数据写入到文件描述符指定的文件中。
cpp
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
int main() {
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open failed");
return 1;
}
const char* msg = "Hello, world!";
ssize_t bytesWritten = write(fd, msg, strlen(msg));
if (bytesWritten == -1) {
perror("write failed");
close(fd);
return 1;
}
std::cout << "Wrote " << bytesWritten << " bytes." << std::endl;
close(fd);
return 0;
}
-
O_CREAT
:如果文件不存在,则创建文件。 -
0644
:文件权限,表示所有者有读写权限,组用户和其他用户有读权限。
4. 关闭文件(close
系统调用)
close
系统调用用于关闭文件描述符,释放资源。
close(fd);
二、网络 IO 操作
1. 创建套接字(socket
系统调用)
socket
系统调用用于创建一个套接字,用于网络通信。
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字
if (sockfd == -1) {
perror("socket failed");
return 1;
}
std::cout << "Socket created successfully, file descriptor: " << sockfd << std::endl;
close(sockfd);
return 0;
}
-
AF_INET
:IPv4 地址族。 -
SOCK_STREAM
:TCP 套接字。 -
SOCK_DGRAM
:UDP 套接字。
2. 绑定地址(bind
系统调用)
bind
系统调用将套接字绑定到一个本地地址和端口。
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket failed");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // 端口号
addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用地址
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind failed");
close(sockfd);
return 1;
}
std::cout << "Socket bound to port 8080." << std::endl;
close(sockfd);
return 0;
}
3. 监听连接(listen
系统调用)
listen
系统调用将套接字设置为监听状态,等待客户端连接。
cpp
if (listen(sockfd, 5) == -1) { // 最大连接队列长度为 5
perror("listen failed");
close(sockfd);
return 1;
}
std::cout << "Server is listening on port 8080." << std::endl;
4. 接受连接(accept
系统调用)
accept
系统调用接受一个客户端连接,返回一个新的套接字用于与客户端通信。
cpp
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (clientfd == -1) {
perror("accept failed");
close(sockfd);
return 1;
}
std::cout << "Client connected." << std::endl;
5. 读写网络数据
使用 read
和 write
系统调用(或 recv
和 send
函数)进行网络数据的读写。
cpp
char buffer[1024];
ssize_t bytesRead = read(clientfd, buffer, sizeof(buffer) - 1);
if (bytesRead == -1) {
perror("read failed");
close(clientfd);
close(sockfd);
return 1;
}
buffer[bytesRead] = '\0';
std::cout << "Received message: " << buffer << std::endl;
const char* response = "Hello, client!";
write(clientfd, response, strlen(response));
三、缓冲机制
1. 标准 IO 缓冲
C 标准库提供了缓冲机制,通过 FILE*
指针操作文件。例如:
cpp
#include <cstdio>
#include <iostream>
int main() {
FILE* file = fopen("example.txt", "r");
if (!file) {
perror("fopen failed");
return 1;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), file)) {
std::cout << buffer;
}
fclose(file);
return 0;
}
-
fgets
从文件中读取一行,自动处理缓冲。 -
fputs
将字符串写入文件,也使用缓冲机制。
2. 系统调用与缓冲
系统调用(如 read
和 write
)通常不使用缓冲,直接与内核交互。如果需要缓冲,可以手动实现,例如:
cpp
char buffer[1024];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
3. 非阻塞 IO 和异步 IO
-
非阻塞 IO :通过设置文件描述符为非阻塞模式,使
read
和write
在数据未准备好时立即返回cppint flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-
异步 IO :使用
aio
或io_uring
等机制,允许程序在 IO 操作完成之前继续执行。