赞
踩
最近调试一款网关设备,它部署在客户端和服务端之间。在工作时,它同时接收来自客户端的连接,同时又向服务端建立连接。
网关在完全接收来自客户端的数据后,会校验数据合法性,只有数据合法,网关才会向服务器建立连接并转发数据。
这样,当存在一个客户端和服务端的通信时,网关有可能需要同时建立两个连接,占用两个fd。这对网关的数据处理能力提出了较高的要求。
在调试中出现的现象是,当客户端以较多的并发的速率向网关建立连接时,网关会因为已经打开的fd过多而拒绝连接,导致来自客户端的连接建立失败。
本文就对此现象进行了简略分析,同时复习一下linux下的文件描述符。
linux系统中通常会对每个进程所能打开的文件数据有一个限制,当进程中已打开的文件描述符超过这个限制时,open()等获取文件描述符的系统调用都会返回失败。
linux下最大文件描述符的限制有两个方面,一个是用户级的限制,另外一个则是系统级限制。
ulimit -n
-> % ulimit -n
1024
-> % sysctl -a | grep file-max
sysctl: fs.file-max = 100262
-> % cat /proc/sys/fs/file-max
100262
ulimit -HSn 65536
/etc/security/limits.conf
-> % ulimit -SHn 2048
yao@yao-virtual-machine [10时49分18秒] [~/work/util]
-> % ulimit -n
2048
vi /etc/security/limits.conf
* hard nofile 65536
* soft nofile 65536
sysctl -w fs.file-max=2048
,完成后执行sysctl -p
即可在Linux通用I/O模型中,I/O操作系列函数(系统调用)都是围绕一个叫做文件描述符的整数展开。
I/O操作系统调用都以文件描述符(一个非负整数),指代打开的文件。每个进程都有一个打开文件表,可以理解成一个数组,文件描述符可以理解成数组的下标。
相关I/O操作系统调用以文件描述符为参数,便可以通过数组访问定位到指定的文件对象,进而进行I/O操作。
当某个程序打开文件时,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符。所谓的文件描述符是一个低级的正整数。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应,如下表。
文件描述符 | 用途 | POSIX名称 | stdio流 |
---|---|---|---|
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准出错 | STDERR_FILENO | stderr |
正常情况下,程序在开始运行之前,由shell准备好这3个文件描述符。更准确的说法是,程序继承了shell文件描述符的副本,一般是指向shell所在的终端。当然了,可以通过在shell中对输入/输出进行重定向或者在程序启动后关闭并重新打开文件描述符,修改文件描述符指向。
在linux系统中,内核维护了三个数据结构,分别是进程级文件描述符表、系统级打开文件表和文件系统i-node表。
内核为每个进程维护一个文件描述符表,该表每一条目都记录了单个文件描述符的相关信息,包括:
内核对所有打开的文件维护一个系统级别的打开文件描述表(open file description table),简称打开文件表。表中条目称为打开文件描述体(open file description),存储了与一个打开文件相关的全部信息,包括:
每个文件系统会为存储于其上的所有文件(包括目录)维护一个i-node表,单个i-node包含以下信息:
i-node存储在磁盘设备上,内核在内存中维护了一个副本,这里的i-node表为后者。副本除了原有信息,还包括:引用计数(从打开文件描述体)、所在设备号以及一些临时属性,例如文件锁。
文件描述符的复制和重定向非常简单,使用dup()系统调用即可完成。在shell中,使用>即可进行重定向。
流程如下:
在程序中使用fork()创建子进程时,父进程中已经打开的fd也会自动在子进程中打开。子进程可以直接对这些文件进行操作。
此时,需要分别在子进程和父进程中关闭fd。一般父进程创建子进程后,父进程会直接关闭掉fd,子进程处理完成后再关闭fd。
使用unix域套接字也可以进行文件描述符的传递,但从一个进程传递到另一个进程后,fd可能会发生变化。
注意使用完毕后,分别关闭fd。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。