前言
在linux下,一切皆文件。当文件被打开时,会返回文件描述符用于操作该文件,从shell中运行一个进程,默认会有3个文件描述符存在(0、1、2);)0表示标准输入,1表示标准输出,2表示标准错误。一个进程当前有哪些打开的文件描述符可以通过/proc/进程ID/fd目录查看。
今天,我们主要说两个用于复制文件描述符的函数dup()和dup2()。
dup()、dup2()函数
函数原形
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
功能
复制文件描述符,使多个文件描述符指向同一个文件。
返回值
成功:dup函数返回当前系统可用的最小整数值。
dup2函数返回第一个不小于newfd的整数值。也就是分为两种情况:
①、如果newfd已经打开,则先将其关闭,再复制文件描述符。
②、如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。
失败:均返回-1,并设置errno。
注意:通过dup和dup2创建的文件描述符并不继承原文件描述符的属性。比如close-on-exec和non-blocking
示例程序
dup.c
//测试dup函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("a.txt", O_RDWR | O_CREAT);
if(fd == -1)
{
perror("open");
exit(1);
}
printf("file open fd = %d\n", fd);
// 找到进程文件描述表中第一个可用的文件描述符
// 将参数指定的文件复制到该描述符后,返回这个描述符
int ret = dup(fd); //使ret和fd指向同一个文件
if(ret == -1)
{
perror("dup");
exit(1);
}
printf("dup fd = %d\n", ret);
char* buf = "hello";
char* buf1 = " world\n";
write(fd, buf, strlen(buf));
write(ret, buf1, strlen(buf1));
close(fd);
return 0;
}
dup2.c
//测试dup2函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("b.txt", O_RDWR | O_CREAT);
if(fd == -1)
{
perror("open");
exit(1);
}
int fd1 = open("a.txt", O_RDWR);
if(fd1 == -1)
{
perror("open");
exit(1);
}
printf("fd = %d\n", fd);
printf("fd1 = %d\n", fd1);
int ret = dup2(fd, fd1); //让fd1和fd同时指向b.txt
if(ret == -1)
{
perror("dup2");
exit(1);
}
printf("current fd = %d\n", ret);
char* buf = "hello ";
char* buf1 = " world!";
write(fd, buf, strlen(buf));
write(fd1, buf1 , strlen(buf1));
close(fd);
close(fd1);
return 0;
}
CGI服务器的原理
在CGI程序,当浏览器使用post方法提交表单数据时,CGI读数据是从标准输入stdin,写数据是写到标准输出stdout(C中使用printf)。按照我们正常的理解,printf的输出应该在终端显示,这是什么原因呢?也是和我们今天介绍的这两个复制文件描述符的函数有关。我们先来看一下代码:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( sock, 5 );
assert( ret != -1 );
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
close( STDOUT_FILENO );
dup( connfd );
//dup2(connfd, STDOUT_FILENO);
printf( "hello world!\n" );
close( connfd );
}
close( sock );
return 0;
}
在上述代码中,我们先关闭标准输出STDOUT_FILENO(1),然后通过dup函复制connfd,因为dup函数返回的系统第一个可用的文件描述符,所以其返回的是1,这样一来,服务器输出到标准输出的内容就会被客户端所获得而不是输出到服务器的终端上。这就是CGI服务器的基本工作原理。