CGI服务器

https://blog.csdn.net/silent123go/article/details/71108501
1、文件描述符在内核中数据结构

  在具体说dup/dup2之前,我认为有必要先了解一下文件描述符在内核中的形态。一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,从shell中运行一个进程,默认会有3个文件描述符存在(0、1、2),0与进程的标准输入相关联,1与进程的标准输出相关联,2与进程的标准错误输出相关联,一个进程当前有哪些打开的文件描述符可以通过/proc/进程ID/fd目录查看。 
  下图可以清楚的说明问题(图片来源于百度百科):
图片说明
文件表中包含:文件状态标志、当前文件偏移量、v节点指针,这些不是本文讨论的重点,我们只需要知道每个打开的文件描述符(fd标志)在进程表中都有自己的文件表项,由文件指针指向。

2、dup/dup2函数

  APUE和man文档都用一句话简明的说出了这两个函数的作用:复制一个现存的文件描述符。

include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);
1
2
3
  当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
  dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
  APUE用另外一个种方法说明了这个问题:
  实际上,调用dup(oldfd)等效于,fcntl(oldfd, F_DUPFD, 0)
  而调用dup2(oldfd, newfd)等效于,close(oldfd);fcntl(oldfd, F_DUPFD, newfd);

3、CGI中dup2

  写过CGI程序的人都清楚,当浏览器使用post方法提交表单数据时,CGI读数据是从标准输入stdin,写数据是写到标准输出stdout(C语言利用printf函数)。按照我们正常的理解,printf的输出应该在终端显示,原来CGI程序使用dup2函数将STDOUT_FINLENO(这个宏在unitstd.h定义,为1)这个文件描述符重定向到了连接套接字:dup2(connfd, STDOUT_FILENO)。
  如第一节所说,一个进程默认的文件描述符1(STDOUT_FILENO)是和标准输出stdout相关联的,对于内核而言,所有打开的文件都通过文件描述符引用,而内核并不知道流的存在(比如stdin、stdout),所以printf函数输出到stdout的数据最后都写到了文件描述符1里面。至于文件描述符0、1、2与标准输入、标准输出、标准错误输出相关联,这只是shell以及很多应用程序的惯例,而与内核无关。
  用下面的流图可以说明问题(ps: 虽然不是流图关系,但是还是有助于理解):
  printf -> stdout -> STDOUT_FILENO(1) -> 终端(tty)
  printf最后的输出到了终端设备,文件描述符1指向当前的终端可以这么理解:
  STDOUT_FILENO = open(“/dev/tty”, O_RDWR);
  使用dup2之后STDOUT_FILENO不再指向终端设备,而是指向connfd, 所以printf的输出最后写到了connfd。是不是很优美?
4、如何在CGI程序的fork子进程中还原STDOUT_FILENO

  如果你能看到这里,感谢你的耐心,我知道很多人可能感觉有点复杂,其实复杂的问题就是一个个小问题的集合。所以弄清楚每个小问题就OK了,第三节中说道,STDOUT_FILENO被重定向到了connfd套接字,有时候我们可能想在CGI程序中调用后台脚本执行,而这些脚本中难免会有一些输入输出,我们知道fork之后,子进程继承了父进程的所有文件描述符,所以这些脚本的输入输出并不会如我们愿输出到终端设备,而是和connfd想关联了,这个显然会扰乱网页的输出。那么如何恢复STDOUT_FILENO和终端关联呢?
  方法1:在dup2之前保存原有的文件描述符,然后恢复。
  代码实现如下:
  savefd = dup(STDOUT_FILENO); /savefd此时指向终端/
  dup2(connfd, STDOUT_FILENO);
  …..
  dup2(savefd, STDOUT_FILENO);
  很遗憾CGI程序无法使用这种方法,因为dup2这些不是在CGI程序中完成的,而是在web server中实现的,修改web server并不是个好主意。
  方法2: 追本溯源,打开当前终端恢复STDOUT_FILENO。
  分析第三节的流图,STDOUT_FILENO是如何和终端关联的?我们重头做一遍不就行了,代码实现如下:
  ttyfd = open(“/dev/tty”, O_RDWR);
  dup2(ttyfd, STDOUT_FILENO);
  close(ttyfd);
  /dev/tty是程序运行所在的终端,这个应该通过一种方法获得。实践证明这种方法是可行的,但是个人总感觉有些不妥,不知道为什么,可能一些潜在的问题还没出现。

c++服务器 文章被收录于专栏

主要以分享服务器相关知识为主

全部评论

相关推荐

昨天 12:43
已编辑
小码王_运维
蚂蚁岗位内推官:1 你觉得你有哪些缺点和优点? 2 你怎么评价你面试的这家公司? 3 你在校期间,有没有哪段时间或者某件事情让你受挫? 4 在校期间遇到最有挑战的事情是什么? 5 目前手上有 offer 吗? 6 自我介绍 7 职业规划 8 报学校专业是怎么考虑的? 9 工作城市 10 你是独生子女吗? 11 那你男朋友吗? 12 那你们出来面试都了解过哪些企业? 13 到后期你们每个人手上有好几个offer,哪些因素决定你们选择这家公司? 14 你更倾向哪种公司?有什么特别的点? 15 你大学有没有特别难忘的经历或者项目分享一下的? 16 团队合作中遇到什么问题? 17 对互联网加班有什么看法? 18 那你现在的技术薄弱点在哪里,怎么去突破? 19 你的兴趣爱好有哪些? 20 现在进度最快的公司是哪家? 21 拿到哪几家offer,是否谈过薪资等
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务