小型web伺服器thttpd的學習總結(上)

2022-08-29 05:24:13 字數 4221 閱讀 3150

軟體的檔案布局比較清晰,主要分為6個模組,主模組是thttpd.c檔案,這個檔案中包含了web server的主要邏輯,並呼叫了其他模組的函式。其他的5個模組都是單一的功能模組,之間沒有任何耦合。

2.1 fdwatch模組

該模組對外提供了6個函式,就是對一般的select/poll類函式的使用方法進行了相應的抽象,包括

//獲得系統可使用的fd的最大數目,並初始化資料結構

extern int fdwatch_get_nfiles(void)

//清除fdwatch中的資料結構

extern void fdwatch_clear( void )

//對fd set中的fds進行操作,其中rw表示是否可讀可寫

extern int fdwatch_add_fd(int fd, int rw)

extern int fdwatch_del_fd(int fd, int rw)

//fd多路復用的主迴圈函式 引數是超時時間

extern int fdwatch(long timeout_msecs)

//對fd狀態的檢查

extern void fdwatch_check_fd(int fd, int rw)

//提取出fdwatch當前的狀態

extern void fdwatch(long* nselectp, long* npollp)

如果想自己簡單實現的話,可以按照如下進行實現:

//首先設定模組全域性變數

static int nfiles; //可以watch的最大fd數目

static int maxfd; //當前watch的最大fd值

static int conn_nums; //當前連線的數目

static long nselect; //當前select的次數

由於如果使用select的話,需要首先有乙個fd_set來標記需要關注哪些fd可讀,關注哪些fd可寫。而將標記fd_set傳入之後,該fd_set返回的指將是當前可讀或者可寫的fd列表,會改變標記set的值,因此,這裡設定了兩個fd_set,乙個用於標記需要關注的fd,另乙個用於傳入select函式,獲得當前可處理的fd情況。

//標記 set

static fd_set master_rfdset;

static fd_set master_wfdset;

//工作 set

static fd_set working_rfdset;

static fd_set working_wfdset;

//內部函式的宣告

static int fdwatch_select(long timeout_msecs);

該函式用於獲得當前可以復用的fd的最大個數,這個最大個數受制於幾個因素,乙個是程序可以開啟的最大的檔案描述符數,getdtablesize()返回的值,還有資源限制的最大fd數,另外還不能超過fd_setsize值,一般現在的fd_set型別都是long int的陣列,每一位代表乙個fd的讀寫情況,取值一般為1024。

int fdwatch_get_nfiles( void )

#endif

//如果是select不能超過fd_setsize的值

nfiles = min(nfiles, fd_setsize);

nselect = 0;

return nfiles;

}

清除標誌位,直接呼叫fd_zero函式:

void fdwatch_clear( void )

增加標誌位,則是根據rw的情況呼叫fd_set函式:

void fdwatch_add_fd( int fd, int rw )

}

檢查標誌位,同樣根據rw的情況呼叫fd_isset函式:

int fdwatch_check_fd( int fd, int rw)

}

在大迴圈中,將master_fdset的值賦值給working_fdset然後呼叫select傳入working_fdset進行檢測,檢測的時候由引數timeout_msecs決定。

int fdwatch( long timeout_msecs )

static int fdwatch_select( long timeout_msecs )

} else

} }

下面兩個函式主要是永遠檢測當前select模組的情況,方便後面打log。

void fdwatch_status( long* nselectp )

int fdwatch_get_conn_nums(void)

可以看到fdwatch模組僅僅是對select做了乙個簡單的封裝,從而可以更加靈活的使用介面進行fd復用的操作,從而可以正常的處理小規模的伺服器併發。

2.1 定時器模組

定時器模組主要是提供乙個定時服務,而採用的時間則是從gettimeofday庫函式來獲得。建立起定時器模組,主要需要首先確定下模組中的幾個結構體,如定時器觸發後,響應的函式和該函式的引數選擇:

//響應函式定義 

typedef void timeout_func(timeout_args args, struct timeval* now);

//傳入引數

typedef union

timeout_args;

這裡將引數定義為乙個聯合體,從而可以傳入多樣型別的值,同時節省空間。所以,定時器結構的定義為:

typedef struct timer_struct

timer;

然後可以看下定時器模組需要提供的模組介面,大抵也就是如下幾種,建立乙個定時器,執行乙個定時器,重置乙個定時器,取消乙個定時器,這裡還提供了乙個檢視最近定時器的觸發時間的介面,用來在這段時間內通過select進行檢視各個連線的情況,也就是說這個時間作為上述fdwatch函式的引數傳入。此外在本定時器模組中,實際上是建立了兩個鍊錶,乙個是當前定時器的列表,乙個是被取消的定時器的列表。因此,還提供了tmr_clean函式用於合理釋放無用定時器所占用的記憶體。而tmr_destroy函式則是銷毀所有的定時器結構。

//建立乙個定時器

extern timer* tmr_create(timeout_func* timer_proc, timeout_args args, struct timeval* now, long msecs, int periodic);

//執行乙個定時器

extern void tmr_run(struct timeval* now);

//檢視最近的定時器觸發時間-毫秒

extern long tmr_timeout_ms(struct timeval* now);

//檢視最近的定時器觸發時間-struct timeval

extern struct timeval* tmr_timeout(struct timeval* now);

//重置乙個定時器

extern void tmr_reset(timer* timer, struct timeval* now);

//取消乙個定時器

extern void tmr_cancle(timer* timer);

//清除定時器結構

extern void tmr_clean(void);

//銷毀所以定時器記憶體

extern void tmr_destroy(void);

具體的函式實現,這裡就簡單的闡述一下過程,不展開**敘述了。建立定時器時,如果無用定時器列表中有內容,就直接使用其資料,否則malloc乙個,然後初始化後,插入列表中。執行乙個定時器,則是根據當前時刻的時間,在列表中依次比對,對於超時的定時器執行其**函式,接著根據週期性選擇**這個定時器還是重新設定這個定時器。最近觸發時間也是在鍊錶中找出最近的時間返回。重置定時器就是根據傳入的時間,重新確定定時器的觸發時間。取消定時器就是將該定時器從當前定時器列表轉移到無用定時器列表中。清除定時器和銷毀定時器上面已經介紹過了,就是銷毀某些記憶體。

一次性寫很多真的看著都煩呀,那另外兩個主要的模組就在下篇來介紹吧。

另註:本文中的**是自己手寫,和原**並非都是一致的。

小型伺服器

在cmd或者shell中啟動python自帶的http伺服器 python m httpserver 80 指定80埠正常啟動會看到如下輸出 serving http on 0.0.0.0 port 8000 說明http伺服器啟動成功,預設埠是8000 在瀏覽器輸入,http 可以省略 或者 ht...

golang編寫的小型靜態web伺服器

我本來打算把cocos creator打包出來的web程式發布給別人玩,主要涉及的是是 靜態的內容,那麼就需要乙個開啟web的伺服器,之後瀏覽器來執行遊戲,臨時找不到,直接用golang弄乙個 如下 func main 我的資源目錄是這樣的 main.exe main.go game res src...

thttpd伺服器移植到ARM Linux教程

cd thttpd 2.26 命令中的 表示當前使用者目錄,比如在 linux 主機上工作的使用者是 veryarm,那麼命令中的 就代表路徑 home veryarm 目錄。cc usr bin arm linux gnueabi gcc configure make 編譯make 後,當前目錄下...