前言
本篇文章主要作为本人对文件描述符的梳理与思考。如果有什么错处或者可指点之处非常乐意接受!
1. 定义
文件描述符是一个非负整数(int
类型),用于标识一个打开的文件或I/O资源。
它是操作系统内核为每个进程维护的一个索引,指向内核中的文件表项。
2. 分配规则
对前篇文章的代码稍作改动并调试运行,启动客户端后,有以下结果。
代码:
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
void *client__thread(void *arg) {
int clientfd = *(int *)arg;
char buffer[1024] = {0};
int count = recv(clientfd, buffer, 1024, 0);
if (count == 0) { //disconnect
printf("client disconnect\n");
}
else {
printf("RECV:%s\n", buffer);
count = send(clientfd, buffer, count, 0);
if (count < 0) {
perror("send failed");
} else {
printf("SEND:%d\n", count);
}
}
close(clientfd); // 关闭客户端连接
return NULL;
}
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(2000); // 0-1023
if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
printf("bind failed:%s\n", strerror(errno));
close(sockfd);
return -1;
}
if (listen(sockfd, 10) < 0) {
perror("listen failed");
close(sockfd);
return -1;
}
printf("listen finished: %d\n",sockfd);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
while (1) {
printf("accept\n");
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("accept finished: %d\n", clientfd);
if (clientfd < 0) {
perror("accept failed");
continue;
}
printf("accept finished\n"); //这里可以删掉,我忘删了
pthread_t thid;
if (pthread_create(&thid, NULL, client__thread, &clientfd) != 0) {
perror("pthread_create failed");
close(clientfd);
} else {
pthread_detach(thid); // 分离线程,使其在结束后自动释放资源
}
}
close(sockfd); // 关闭服务器套接字
printf("exit\n");
return 0;
}
运行结果:
这里的3、4、5、6分别是为sockfd和三个客户端的clientfd分配的int型非负整数。那么,为何是从3开始呢?这里涉及到了它的分配规则:
1. 文件描述符从 0
开始分配。
2. 每次分配时,系统会分配当前可用的最小非负整数。
3. 每个进程默认打开三个标准文件描述符。
从3开始正是因为这三个标准文件描述符分别占位了0、1、2。分别对应:
0:标准输入(stdin),通常对应键盘输入,即通过getchar()、scanf()、终端等获取。
1:标准输出(stdout),通常对应终端输出。
2:标准错误(stderr),通常用于输出错误信息。
在终端中查看方法如下。
linve@linve-VMware-Virtual-Platform:~$ ls /dev/stdin -l
lrwxrwxrwx 1 root root 15 2月 24 14:33 /dev/stdin -> /proc/self/fd/0
linve@linve-VMware-Virtual-Platform:~$ ls /dev/stdout -l
lrwxrwxrwx 1 root root 15 2月 24 14:33 /dev/stdout -> /proc/self/fd/1
linve@linve-VMware-Virtual-Platform:~$ ls /dev/stderr -l
lrwxrwxrwx 1 root root 15 2月 24 14:33 /dev/stderr -> /proc/self/fd/2
3. 文件描述符的标识资源类型
在定义中,我们可以了解到,FD的作用就是标识打开的文件或I/O资源。有哪些资源类型呢?以下简单罗列:
- 普通文件(通过
open()
打开)。 - 套接字(通过
socket()
创建)。 - 管道(通过
pipe()
创建)。 - 设备文件(如
/dev/null
)。 - 符号链接等。
4. 文件描述符的创建与关闭
创建:
使用系统调用如 open()
、socket()
、pipe()
等创建文件描述符。e.g.
int fd = open("file.txt", O_RDONLY); // 打开文件,返回文件描述符
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字,返回文件描述符
关闭:
使用 close()
系统调用关闭文件描述符。e.g.
close(fd); // 关闭文件描述符
5.文件描述符的常见操作
读取数据:
ssize_t read(int fd, void *buf, size_t count);
写入数据:
ssize_t write(int fd, const void *buf, size_t count);
获取文件信息:
int fstat(int fd, struct stat *buf);
6. 文件描述符的限制
主要表现在每个进程可以打开的文件描述符数量是有限制的。
可以通过以下命令查看。
ulimit -n
这里的n就是FD的数量限制。n的大小可以通过以下命令修改。
ulimit -n 1024
尾声
暂时先到这里......!后面应该会单开一篇写FD的分配与重用(因为我有一些点现在也不是很确定有没有搞懂)。应该还会开一篇写一点对“Linux下操作的所有设备一切皆文件,一切皆文件描述符”这句话的简单理解。溜了溜了。
学习资料参考
https://github.com/0voice