【网络io与io多路复用(3)】 Linux与文件描述符(FD)

前言

本篇文章主要作为本人对文件描述符的梳理与思考。如果有什么错处或者可指点之处非常乐意接受!


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 -&gt; /proc/self/fd/0
linve@linve-VMware-Virtual-Platform:~$ ls /dev/stdout -l
lrwxrwxrwx 1 root root 15  2月 24 14:33 /dev/stdout -&gt; /proc/self/fd/1
linve@linve-VMware-Virtual-Platform:~$ ls /dev/stderr -l
lrwxrwxrwx 1 root root 15  2月 24 14:33 /dev/stderr -&gt; /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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值