1. 程式人生 > >TinyHttpd(小型伺服器程式)原始碼閱讀記錄

TinyHttpd(小型伺服器程式)原始碼閱讀記錄

執行效果圖

啟動伺服器

當有連線訪問時

 

訪問介面

程式碼詳解

主要函式

void accept_request(void *); //接收請求並進行簡單的處理
void bad_request(int); //返回400狀態給客戶端
void cat(int, FILE *); //讀取檔案發給客戶端
void cannot_execute(int); //返回500狀態給客戶端
void error_die(const char *); //程式出錯退出
void execute_cgi(int, const char *, const char *, const char *); //調整環境變數,執行cgi
int get_line(int, char *, int); //從套接字流中讀取一行內容(一個位元組一個位元組的讀)
void headers(int, const char *);//填充http響應頭
void not_found(int); //返回404狀態給客戶端
void serve_file(int, const char *); //處理檔案請求
int startup(u_short *); //初始化伺服器監聽套接字
void unimplemented(int); //返回501狀態給客戶端

程式碼(帶有詳細註釋)

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>

#define ISspace(x) isspace((int)(x))//如果x是一個空白字元,則該函式返回非零值(true),否則返回 0(false)。

#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
#define STDIN   0
#define STDOUT  1
#define STDERR  2

void accept_request(void *); //接收請求並進行簡單的處理
void bad_request(int); //返回400狀態給客戶端
void cat(int, FILE *); //讀取檔案發給客戶端
void cannot_execute(int); //返回500狀態給客戶端
void error_die(const char *); //程式出錯退出
void execute_cgi(int, const char *, const char *, const char *); //調整環境變數,執行cgi
int get_line(int, char *, int); //從套接字流中讀取一行內容(一個位元組一個位元組的讀)
void headers(int, const char *);//填充http響應頭
void not_found(int); //返回404狀態給客戶端
void serve_file(int, const char *); //處理檔案請求
int startup(u_short *); //初始化伺服器監聽套接字
void unimplemented(int); //返回501狀態給客戶端

/**********************************************************************/

 /*請求導致伺服器埠上的呼叫被接受。適當地處理請求。引數:連線到客戶端的套接字*/

/**********************************************************************/
void accept_request(void *arg)
{
    int client = *(int*)arg;
    char buf[1024];//讀寫用的緩衝區
    size_t numchars;//訊號量
    char method[255];//請求方法
    char url[255];//統一資源定位符
    char path[512];//路徑
    size_t i, j;
    struct stat st;//struct stat這個結構體是用來描述一個linux系統檔案系統中的檔案屬性的結構
    int cgi = 0;      /* becomes true if server decides this is a CGI
                       * program */
    char *query_string = NULL;//請求行
//讀http請求的第一行資料(request line),把請求方法存進method中
    numchars = get_line(client, buf, sizeof(buf));//從套接字流中讀取一行內容(一個位元組一個位元組的讀)到buf陣列中
    i = 0; j = 0;
    while (!ISspace(buf[i]) && (i < sizeof(method) - 1))//請求方法讀入method
    {
        method[i] = buf[i];
        i++;
    }
    j=i;
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    //strcasecmp(const char *s1, const char *s2)用來比較引數s1 和s2 字串,比較時會自動忽略大小寫的差異。相同返回0
    //如果請求方法既不是get也不是post就返回501狀態給客戶端
    {
        unimplemented(client);
        return;
    }
//如果是 POST 方法就將 cgi 標誌變數置一(true)
    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    //跳過空白字元
    while (ISspace(buf[j]) && (j < numchars))
        j++;
    //將url資訊讀入url陣列
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
    {
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';
//如果這個請求是一個 GET 方法的話
    if (strcasecmp(method, "GET") == 0)
    {
        //用一個指標指向 url
        query_string = url;
        //去遍歷這個 url,跳過字元 ?前面的所有字元,如果遍歷完畢也沒找到字元 ?則退出迴圈
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        //退出迴圈後檢查當前的字元是 ?還是字串(url)的結尾
        if (*query_string == '?')
        {
            //如果是 ? 的話,證明這個請求需要呼叫 cgi,將 cgi 標誌變數置一(true)
            cgi = 1;
             //從字元 ? 處把字串 url 給分隔會兩份
            *query_string = '\0';
            query_string++;
        }
    }
/*srpintf()函式的功能非常強大:效率比一些字串操作函式要高;
而且更具靈活性;可以將想要的結果輸出到指定的字串中,也可作為緩衝區,而printf只能輸出到命令列上~
標頭檔案:stdio.h
函式功能:格式化字串,將格式化的資料寫入字串中。
函式原型:int sprintf(char *buffer, const char *format, [argument]...)
引數:
(1)buffer:是char型別的指標,指向寫入的字串指標;
(2)format:格式化字串,即在程式中想要的格式;
(3)argument:可選引數,可以為任意型別的資料;
函式返回值:buffer指向的字串的長度;
用處:
(1)格式化數字字串:在這點上sprintf和printf的用法一樣,只是列印到的位置不同而已,
前者列印給buffer字串,後者列印給標準輸出,所以sprintf也可以用來將整型轉化為字串,
比itoa效率高且如此地簡便~比如:sprintf(buffer, "%d", 123456);執行後buffer即指向字串“123456”~
(2)連線字元:*/

    //將前面分隔兩份的前面那份字串,拼接在字串htdocs的後面之後就輸出儲存到陣列 path 中。相當於現在 path 中儲存著一個字串
    sprintf(path, "htdocs%s", url);
    //如果 path 陣列中的這個字串的最後一個字元是以字元 / 結尾的話,就拼接上一個"index.html"的字串。首頁的意思
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    
    /*struct stat這個結構體是用來描述一個linux系統檔案系統中的檔案屬性的結構
    獲取一個檔案的屬性int stat(const char *path, struct stat *struct_stat);
    第一個引數都是檔案的路徑,第二個引數是struct stat的指標。
    返回值為0,表示成功執行。執行失敗返回-1,error被自動設定為下面的值:
    EBADF: 檔案描述詞無效
    EFAULT: 地址空間不可訪問
    ELOOP: 遍歷路徑時遇到太多的符號連線
    ENAMETOOLONG:檔案路徑名太長
    ENOENT:路徑名的部分元件不存在,或路徑名是空字串
    ENOMEM:記憶體不足
    ENOTDIR:路徑名的部分元件不是目錄 
    */
    //在系統上去查詢該檔案是否存在
    if (stat(path, &st) == -1) {
         //如果不存在,那把這次 http 的請求後續的內容(head 和 body)全部讀完並忽略
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
        //沒有找到資源,返回404狀態給客戶端
        not_found(client);
    }
    else
    {
/* st_mode檔案保護模式 
         
S_IFMT      0170000     檔案型別的位遮罩
S_IFSOCK    0140000     socket
S_IFLNK     0120000     符號連結(symbolic link)
S_IFREG     0100000     一般檔案
S_IFBLK     0060000     區塊裝置(block device)
S_IFDIR     0040000     目錄
S_IFCHR     0020000     字元裝置(character device)
S_IFIFO     0010000     先進先出(fifo)
S_ISUID     0004000     檔案的(set user-id on execution)位
S_ISGID     0002000     檔案的(set group-id on execution)位
S_ISVTX     0001000     檔案的sticky位
S_IRWXU     00700       檔案所有者的遮罩值(即所有許可權值)
S_IRUSR     00400       檔案所有者具可讀取許可權
S_IWUSR     00200       檔案所有者具可寫入許可權
S_IXUSR     00100       檔案所有者具可執行許可權
S_IRWXG     00070       使用者組的遮罩值(即所有許可權值)
S_IRGRP     00040       使用者組具可讀取許可權
S_IWGRP     00020       使用者組具可寫入許可權
S_IXGRP     00010       使用者組具可執行許可權
S_IRWXO     00007       其他使用者的遮罩值(即所有許可權值)
S_IROTH     00004       其他使用者具可讀取許可權
S_IWOTH     00002       其他使用者具可寫入許可權
S_IXOTH     00001       其他使用者具可執行許可權
*/

//檔案存在,那去跟常量S_IFMT相與,相與之後的值可以用來判斷該檔案是什麼型別的
//S_IFMT參讀《TLPI》P281,與下面的三個常量一樣是包含在<sys/stat.h>
        if ((st.st_mode & S_IFMT) == S_IFDIR)
            //如果這個檔案是個目錄,那就需要再在 path 後面拼接一個"/index.html"的字串
            strcat(path, "/index.html");
        if ((st.st_mode & S_IXUSR) ||
                (st.st_mode & S_IXGRP) ||
                (st.st_mode & S_IXOTH)    )
             //如果這個檔案是一個可執行檔案,不論是屬於使用者/組/其他這三者型別的,就將 cgi 標誌變數置一
            cgi = 1;
        //如果不需要 cgi 機制的話,
        if (!cgi)
            serve_file(client, path);////處理檔案請求
        else
        //如果需要則呼叫
            execute_cgi(client, path, method, query_string);////調整環境變數,執行cgi
    }
    close(client);
}

/**********************************************************************/
 //返回400狀態給客戶端
 //引數:客戶端套接字
/**********************************************************************/

void bad_request(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "<P>Your browser sent a bad request, ");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "such as a POST without a Content-Length.\r\n");
    send(client, buf, sizeof(buf), 0);
}

/**********************************************************************/
 /* 在套接字上顯示檔案的全部內容。這個函式是以unix的cat命令命名的
 ,因為做一些像管道、叉子和執行cat之類的事情可能比較容易。
 引數:客戶端套接字描述器。檔案指標為cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
    char buf[1024];
 //從檔案檔案描述符中讀取指定內容
    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

/**********************************************************************/
 /*通知客戶端cgi指令碼無法執行。引數:客戶端套接字描述符。*/
/**********************************************************************/
void cannot_execute(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
 /*打印出帶有perror()的錯誤訊息(用於系統錯誤;基於errno的值,該值表示系統呼叫錯誤),
 並退出表示錯誤的程式。*/
/**********************************************************************/
void error_die(const char *sc)
{
    perror(sc);
    exit(1);
}

/**********************************************************************/
 /*執行cgi指令碼。將需要酌情設定環境變數。
   cgi指令碼的路徑*/
/**********************************************************************/
void execute_cgi(int client, const char *path,
        const char *method, const char *query_string)
{
    char buf[1024];
    int cgi_output[2];
    int cgi_input[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;
     //往 buf 中填東西以保證能進入下面的 while
    buf[0] = 'A'; buf[1] = '\0';
     //如果是 http 請求是 GET 方法的話讀取並忽略請求剩下的內容
    if (strcasecmp(method, "GET") == 0)
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
    else if (strcasecmp(method, "POST") == 0) /*POST*/
    {
        //只有 POST 方法才繼續讀內容
        numchars = get_line(client, buf, sizeof(buf));
        //這個迴圈的目的是讀出指示 body 長度大小的引數,並記錄 body 的長度大小。其餘的 header 裡面的引數一律忽略
        //注意這裡只讀完 header 的內容,body 的內容沒有讀
        while ((numchars > 0) && strcmp("\n", buf))
        {
            buf[15] = '\0';
            if (strcasecmp(buf, "Content-Length:") == 0)
                content_length = atoi(&(buf[16]));//記錄 body 的長度大小
            numchars = get_line(client, buf, sizeof(buf));
        }
        //如果 http 請求的 header 沒有指示 body 長度大小的引數,則報錯返回
        if (content_length == -1) {
            bad_request(client);
            return;
        }
    }
    else/*HEAD or other*/
    {
    }
/*
伺服器從URL中獲取cgi的名字,然後fork一個子程序,設定好
環境變數和管道(使用dup2函式將STDIN、STDOUT重定向到管道)之後,
使用exec系列函式執行cgi程式。cgi程式可以從STDIN或環境變量表中
讀取資料,然後把結果寫到STDOUT就完事了。 
而伺服器會從管道讀取cgi程式的處理結果,返回給瀏覽器。
*/

 //下面這裡建立兩個管道,用於兩個程序間通訊

    if (pipe(cgi_output) < 0) {
        cannot_execute(client);
        return;
    }
    if (pipe(cgi_input) < 0) {
        cannot_execute(client);
        return;
    }
 //建立一個子程序
    if ( (pid = fork()) < 0 ) {
        cannot_execute(client);
        return;
    }
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    //子程序用來執行 cgi 指令碼
    if (pid == 0)  /* child: CGI script */
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];
        //將子程序的輸出由標準輸出重定向到 cgi_ouput 的管道寫端上
        dup2(cgi_output[1], STDOUT);
         //將子程序的輸出由標準輸入重定向到 cgi_ouput 的管道讀端上
        dup2(cgi_input[0], STDIN);
         //關閉 cgi_ouput 管道的讀端與cgi_input 管道的寫端
        close(cgi_output[0]);
        close(cgi_input[1]);
        //構造一個環境變數
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
          //將這個環境變數加進子程序的執行環境中
        putenv(meth_env);
          //根據http 請求的不同方法,構造並存儲不同的環境變數
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        }
        else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }
         //最後將子程序替換成另一個程序並執行 cgi 指令碼
        execl(path, NULL);
        exit(0);
    } else {    /* parent */
    //父程序則關閉了 cgi_output管道的寫端和 cgi_input 管道的讀端
        close(cgi_output[1]);
        close(cgi_input[0]);
        if (strcasecmp(method, "POST") == 0)
            for (i = 0; i < content_length; i++) {
                recv(client, &c, 1, 0);
                write(cgi_input[1], &c, 1);
            }
        //然後從 cgi_output 管道中讀子程序的輸出,併發送到客戶端去
        while (read(cgi_output[0], &c, 1) > 0)
            send(client, &c, 1, 0);
        //關閉管道
        close(cgi_output[0]);
        close(cgi_input[1]);
         //等待子程序的退出
        waitpid(pid, &status, 0);
    }
}

/***********************************************************************/
/*
函式原型:int recv( SOCKET s, char *buf, int  len, int flags)

功能:不論是客戶還是伺服器應用程式都用recv函式從TCP連線的另一端接收資料。
引數一:指定接收端套接字描述符;
引數二:指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;
引數三:指明buf的長度;
引數四 :一般置為0。

阻塞與非阻塞recv返回值沒有區分,都是
 >  0  成功接收資料大小。
 =  0  另外一端關閉了套接字
 = -1     錯誤,需要獲取錯誤碼errno(win下是通過WSAGetLastError())
*/
*****************************************************************/
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
            if (c == '\r')//如果是回車符,繼續判斷下一個字元,如果下一個字元是換行符,c=再下一個字元,否則c='\0'
            {
                n = recv(sock, &c, 1, MSG_PEEK);
                /*MSG_PEEK標誌會將套接字接收佇列中的可讀的資料拷
                貝到緩衝區,但不會使套接子接收佇列中的資料減少*/
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else
            c = '\n';//如果讀取失敗則c='\n'
    }
    buf[i] = '\0';//新增'\0'結尾

    return(i);
}

/**********************************************************************/
 /*返回關於檔案的資訊http標頭。
 引數:要在檔名稱上列印頁首的套接字*/
/**********************************************************************/
void headers(int client, const char *filename)
{
    char buf[1024];
    (void)filename;  /* could use filename to determine file type */

    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    strcpy(buf, SERVER_STRING);//SERVER_STRING="Server: jdbhttpd/0.1.0\r\n"
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    strcpy(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
////返回400狀態給客戶端
/**********************************************************************/
void not_found(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "your request because the resource specified\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "is unavailable or nonexistent.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Send a regular file to the client.  Use headers, and report
 * errors to client if they occur.
 * Parameters: a pointer to a file structure produced from the socket
 *              file descriptor
 *             the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[1024];
    //確保 buf 裡面有東西,能進入下面的 while 迴圈
    buf[0] = 'A'; buf[1] = '\0';
    //迴圈作用是讀取並忽略掉這個 http 請求後面的所有內容
    while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        numchars = get_line(client, buf, sizeof(buf));
    //開啟這個傳進來的這個路徑所指的檔案
    resource = fopen(filename, "r");
    if (resource == NULL)
        not_found(client);
    else
    {
        //開啟成功後,將這個檔案的基本資訊封裝成 response 的頭部(header)並返回
        headers(client, filename);
         //接著把這個檔案的內容讀出來作為 response 的 body 傳送到客戶端
        cat(client, resource);
    }
    fclose(resource);
}

/**********************************************************************/
/*
 此函式啟動在指定埠上監聽網路連線的過程。如果埠為0,
 則動態分配一個埠並修改原始埠變數以反映實際埠。
 引數:指標指向包含埠的變數,以返回連線:套接字
 */
/**********************************************************************/
/***************************************************************************/
/* typedef unsigned short sa_family_t;
struct sockaddr {
        sa_family_t     sa_family;    //address family, AF_xxx       
        char            sa_data[14];   //  14 bytes of protocol address 
};
/***********ipv4的套接字地址*********************************/
/*
struct sockaddr_in {
  __kernel_sa_family_t sin_family; // AF_INET short int
  __be16 sin_port; // Port number 埠號 unsigned short int
  struct in_addr sin_addr; // Internet address 32位unsigned int ipv4 網路地址

  //Pad to size of `struct sockaddr'.結構體sockaddr_in的size
  unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
                        sizeof(unsigned short int) - sizeof(struct in_addr)];
}; 
*/
/*
socket(PF_INET, SOCK_STREAM,0)---TCP連線 或socket(AF_INET, SOCK_STREAM,0)---UDP連線
socket()系統呼叫,帶有三個引數: 
1、引數domain指明通訊域,如PF_UNIX(unix域),PF_INET(IPv4),PF_INET6(IPv6)等
2、type 指明通訊型別,最常用的如SOCK_STREAM(面向連線可靠方式,  比如TCP)、SOCK_DGRAM(非面向連線的非可靠方式,比如
UDP)等。  SOCK_STREAM 是資料流,一般是tcp/ip協議的程式設計,SOCK_DGRAM分是資料抱,是udp協議網路程式設計。
3、引數protocol指定需要使用的協議。雖然可以對同一個協議  家族(protocol family)(或者說通訊域(domain))指定不同的協議  引數, 
但是通常只有一個。對於TCP引數可指定為IPPROTO_TCP,對於  UDP可以用IPPROTO_UDP。你不必顯式制定這個引數,使用0則根據前
兩個引數使用預設的協議

返回值:如果函式呼叫成功,會返回一個標識這個套接字的檔案描述符,失敗的時候返回-1。
*/
int startup(u_short *port)
{
    int httpd = 0;
     //sockaddr_in 是 IPV4的套接字地址結構。
    struct sockaddr_in name;
    //socket()用於建立一個用於 socket 的描述符,函式包含於<sys/socket.h>
    //這裡的PF_INET其實是與 AF_INET同義
    httpd = socket(PF_INET, SOCK_STREAM, 0);
    if (httpd == -1)
        error_die("socket");
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    //htons(),ntohs() 和 htonl()包含於<arpa/inet.h>
    //將*port 轉換成以網路位元組序表示的16位整數
    name.sin_port = htons(*port);
    //INADDR_ANY是一個 IPV4通配地址的常量,包含於<netinet/in.h>
    //大多實現都將其定義成了0.0.0.0 
    if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
        error_die("bind");
    //如果呼叫 bind 後端口號仍然是0,則手動呼叫getsockname()獲取埠號
    if (*port == 0)  /* if dynamically allocating a port */
    {
        
        socklen_t namelen = sizeof(name);
        //getsockname()包含於<sys/socker.h>中,參讀《TLPI》P1263
        //呼叫getsockname()獲取系統給 httpd 這個 socket 隨機分配的埠號
        if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
            error_die("getsockname");
        *port = ntohs(name.sin_port);
    }
    if (listen(httpd, 5) < 0)
        error_die("listen");
    return(httpd);
}

/**********************************************************************/
 //返回501狀態給客戶端(伺服器發生錯誤)
/**********************************************************************/
void unimplemented(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</TITLE></HEAD>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/

int main(void)
{
    int server_sock = -1;
    u_short port = 4000;
    int client_sock = -1;
    struct sockaddr_in client_name;
    socklen_t  client_name_len = sizeof(client_name);
    pthread_t newthread;

    server_sock = startup(&port);
    printf("httpd running on port %d\n", port);
    //阻塞等待客戶端的連線
    while (1)
    {
        client_sock = accept(server_sock,
                (struct sockaddr *)&client_name,
                &client_name_len);
        if (client_sock == -1)
            error_die("accept");
        /* accept_request(client_sock); */
        if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock) != 0)
            perror("pthread_create");
    }

    close(server_sock);

    return(0);
}