1. 程式人生 > >淺析dup()和dup2()函式

淺析dup()和dup2()函式

前言

  在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伺服器的基本工作原理。