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);
}