2009年7月3日 星期五

Apache Filter Module過濾器模組

從應用模組程式開發的角度來看,Apache 2.x的最重要改進與創新就是過濾器(filter)的架構,過濾器使得處理不同的資料可以隨心所欲的運作。

從Apache 2的架構來看,可以分為兩個正交軸,一為處理軸(Processing Axis),另一為資料軸(Data Axis)。每一次的請求皆由處理軸來接受,其中會經過許許多多的掛勾(hook),於是進行一些處理(handler)的呼叫。

過濾器依據過濾資料的處理階段有這幾種不同類型:
  • Content Filter:在接受請求中,用來處理文件的資料。一般來說我們都式設計這類的過濾器居多,例如過濾HTML中特定的文字。
  • Protocol Filter:用來處理通訊協定(protocol)的資料。在Apache中,Protocol Filter用在將HTTP通訊協定轉換request_rec的資料結構。
  • Connection Filter:處理TCP連線的資料。用來處理conn_rec的資料結構,跟HTTP和request_recr皆無關係,你可以看成是操作在傳輸層(Transport Layer)的部分,而Content Filter和Protocol Filter是操作在應用層(Application Layer)。
過濾器依據放置的位置則有這幾種不同類型:
  • Input Filter:處理來自client請求(request)的資料,故稱為輸入過濾器(Input Filter)。
  • Output Filter:處理將回應(response)給client的資料,故稱為輸出過濾器(Output Filter)。
此外,過濾器也有點像掛勾的樣子,有著另一種資料型態來處理過濾器之間的傳遞,是要依靠ap_filter_t的資料結構



/**
* @brief The representation of a filter chain.
*
* Each request has a list
* of these structures which are called in turn to filter the data. Sub
* requests get an exact copy of the main requests filter chain.
*/
struct ap_filter_t {
/** The internal representation of this filter. This includes
* the filter's name, type, and the actual function pointer.
*/
ap_filter_rec_t *frec;

/** A place to store any data associated with the current filter */
void *ctx;

/** The next filter in the chain */
ap_filter_t *next;

/** The request_rec associated with the current filter. If a sub-request
* adds filters, then the sub-request is the request associated with the
* filter.
*/
request_rec *r;

/** The conn_rec associated with the current filter. This is analogous
* to the request_rec, except that it is used for input filtering.
*/
conn_rec *c;
};

如果需要在多個過濾器之間傳送資料,你就需要用ctx這個資料欄位。

2009年6月30日 星期二

Apache WinNT MPM多執行模組的運作方式

WinNT的MPM模組當中,使用一種稱為「I/O Completion Ports」的技術來處理各執行緒的請求分配,「I/O Completion Ports」中文稱為「I/O完全埠」、「I/O完成通訊埠」或「完成端口」等,也有簡稱IOCP

主要資料可以參考MSDN的兩個網址:




2009年6月22日 星期一

Apache MultiThread多執行緒寫法

在WIN32的Apache中,MPM模組(多進程並發處理模塊)使用多執行緒來處理Request,其中避免單一資源被多個執行緒搶奪,必須使用一些技巧,可以參考MSDN中的Synchronization Objects主題文章(http://msdn.microsoft.com/en-us/library/ms686364(VS.85).aspx),常用的Synchronization Object有下列幾種:Event、Mutex、Semaphore和Waitable Timer。利用Synchronization Objects可以被指定給其中之一的Wait Function,讓這些Thread可以等等,如此避掉單一資源被多個執行緒搶奪。

這些Object的特點如下:
除了這四個物件之外,其他可以用來同步執行緒的物件還有:Change notification、Console input、Job、Memory resource notification、Process、Thread等。

這裡還沒有提到一點,為了讓執行緒可以停下來等同步,我們必須使用Wait Function這個功能,Wait Function會在執行的時候block停止住,直到特定的條件(criteria)符合達成才會繼續執行下去,當然你也可以設定一段時間(time-out interval),當條件不符合時,進入等待的狀態,若時間超過則會繼續執行而不等待。


2009年6月12日 星期五

Apache Platform平台運作說明

當我在拆解Apache的同時,發現到一個不能忽略的問題,我們必須將視野提高到較高層次來觀看原始碼(source code),才不會見樹不見林,也較能對Apache的運作有深刻的體悟!樹、林是兩難。

Apache HTTP Server(簡稱Apache)通常在作業系統啟動的時候,就會開始在背後執行,成為 Linux中的Deamon,或是Windows中的Service,有一件事:Apache在啟動的時候,是比較耗費系統資源,所以通常是OS啟動後就Apache提供服務,不太像一般程式,你要用到才去呼叫。

Apache的程式當中分成兩個階段執行,一個是啟動階段(start-up phase),另一個是運行階段(operational pahse)。

啟動階段(start-up phase)的作用有:讀取組態檔(httpd.conf或是.htaccess);載入各個模組(module);初始化系統資源,包含log檔、共享記憶體、資料連結等。

啟動階段完成之後,進入運行階段(operational pahse),運行階段主要由MPM(Multi-Processing Module)模組接管控制,MPM會產生一個或多個的執行緒池(Thread Pool)或是行程(process),像Windows中只會有兩個process(即httpd.exe),其中之一是啟動階段的Master Process,另一個是運行階段的Child Process,由Child Process產生Thread Pool,此後由Thread Pool服務所有的Request請求。主要是因為Windows是支援Multi-Thread的作業系統。


2009年5月31日 星期日

Request Processing in Apache 2.0請求的處理過程

Apache中HTTP Request的程序主要是request.c中的函數來處理,這個函數是ap_process_request_internal,所有的請求都由它處理,包含子請求(subrequest)和轉向(redirect)。

其程式碼如下所示:


/* This is the master logic for processing requests. Do NOT duplicate
* this logic elsewhere, or the security model will be broken by future
* API changes. Each phase must be individually optimized to pick up
* redundant/duplicate calls by subrequests, and redirects.
*/
AP_DECLARE(int) ap_process_request_internal(request_rec *r)
{
int file_req = (r->main && r->filename);
int access_status;

/* Ignore embedded %2F's in path for proxy requests */
if (!r->proxyreq && r->parsed_uri.path) {
core_dir_config *d;
d = ap_get_module_config(r->per_dir_config, &core_module);
if (d->allow_encoded_slashes) {
access_status = ap_unescape_url_keep2f(r->parsed_uri.path);
}
else {
access_status = ap_unescape_url(r->parsed_uri.path);
}
if (access_status) {
if (access_status == HTTP_NOT_FOUND) {
if (! d->allow_encoded_slashes) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"found %%2f (encoded '/') in URI "
"(decoded='%s'), returning 404",
r->parsed_uri.path);
}
}
return access_status;
}
}

ap_getparents(r->uri); /* OK --- shrinking transformations... */

/* All file subrequests are a huge pain... they cannot bubble through the
* next several steps. Only file subrequests are allowed an empty uri,
* otherwise let translate_name kill the request.
*/
if (!file_req) {
if ((access_status = ap_location_walk(r))) {
return access_status;
}

if ((access_status = ap_run_translate_name(r))) {
return decl_die(access_status, "translate", r);
}
}

/* Reset to the server default config prior to running map_to_storage
*/
r->per_dir_config = r->server->lookup_defaults;

if ((access_status = ap_run_map_to_storage(r))) {
/* This request wasn't in storage (e.g. TRACE) */
return access_status;
}

/* Rerun the location walk, which overrides any map_to_storage config.
*/
if ((access_status = ap_location_walk(r))) {
return access_status;
}

/* Only on the main request! */
if (r->main == NULL) {
if ((access_status = ap_run_header_parser(r))) {
return access_status;
}
}

/* Skip authn/authz if the parent or prior request passed the authn/authz,
* and that configuration didn't change (this requires optimized _walk()
* functions in map_to_storage that use the same merge results given
* identical input.) If the config changes, we must re-auth.
*/
if (r->main && (r->main->per_dir_config == r->per_dir_config)) {
r->user = r->main->user;
r->ap_auth_type = r->main->ap_auth_type;
}
else if (r->prev && (r->prev->per_dir_config == r->per_dir_config)) {
r->user = r->prev->user;
r->ap_auth_type = r->prev->ap_auth_type;
}
else {
switch (ap_satisfies(r)) {
case SATISFY_ALL:
case SATISFY_NOSPEC:
if ((access_status = ap_run_access_checker(r)) != 0) {
return decl_die(access_status, "check access", r);
}

if (ap_some_auth_required(r)) {
if (((access_status = ap_run_check_user_id(r)) != 0)
|| !ap_auth_type(r)) {
return decl_die(access_status, ap_auth_type(r)
? "check user. No user file?"
: "perform authentication. AuthType not set!",
r);
}

if (((access_status = ap_run_auth_checker(r)) != 0)
|| !ap_auth_type(r)) {
return decl_die(access_status, ap_auth_type(r)
? "check access. No groups file?"
: "perform authentication. AuthType not set!",
r);
}
}
break;

case SATISFY_ANY:
if (((access_status = ap_run_access_checker(r)) != 0)) {
if (!ap_some_auth_required(r)) {
return decl_die(access_status, "check access", r);
}

if (((access_status = ap_run_check_user_id(r)) != 0)
|| !ap_auth_type(r)) {
return decl_die(access_status, ap_auth_type(r)
? "check user. No user file?"
: "perform authentication. AuthType not set!",
r);
}

if (((access_status = ap_run_auth_checker(r)) != 0)
|| !ap_auth_type(r)) {
return decl_die(access_status, ap_auth_type(r)
? "check access. No groups file?"
: "perform authentication. AuthType not set!",
r);
}
}
break;
}
}
/* XXX Must make certain the ap_run_type_checker short circuits mime
* in mod-proxy for r->proxyreq && r->parsed_uri.scheme
* && !strcmp(r->parsed_uri.scheme, "http")
*/
if ((access_status = ap_run_type_checker(r)) != 0) {
return decl_die(access_status, "find types", r);
}

if ((access_status = ap_run_fixups(r)) != 0) {
return access_status;
}

return OK;
}

2009年5月25日 星期一

Apache Portable Runtime可移植執行環境

Apache HTTP Server運作時需要依賴另一個程式庫,也就是APR,全名為「Apache Portable Runtime可移植執行環境」,這其實是Apache Software Foundation(ASF)底下另一個專案計畫,請參考http://apr.apache.org/

APR中有一項重要的程式庫模組,這個模組是APR Pools(apr_pools.h),這是APR和Apchee中管理資源的重要模組!其資料型態是apr_poot_t。在APR當中則是Memory Pool Functions這個模組裡面,原始碼是memory\apr_pools.c,他主要功能其實也就是記憶體管理!

其他定義如下(ver 1.3)
Defines
  • #define APR_POOL_DECLARE_ACCESSOR(type)
  • #define APR_POOL_IMPLEMENT_ACCESSOR(type)
  • #define APR_POOL_DEBUG 0
  • #define APR_POOL__FILE_LINE__ __FILE__ ":" APR_STRINGIFY(__LINE__)

Typedefs
  • typedef struct apr_pool_t apr_pool_t
  • typedef int(* apr_abortfunc_t )(int retcode)

Functions
  • apr_status_t apr_pool_initialize (void)
  • void apr_pool_terminate (void)
  • apr_status_t apr_pool_create_ex (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator)
  • apr_status_t apr_pool_create_core_ex (apr_pool_t **newpool, apr_abortfunc_t abort_fn, apr_allocator_t *allocator)
  • apr_status_t apr_pool_create_unmanaged_ex (apr_pool_t **newpool, apr_abortfunc_t abort_fn, apr_allocator_t *allocator)
  • apr_status_t apr_pool_create_ex_debug (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator, const char *file_line)
  • apr_status_t apr_pool_create_core_ex_debug (apr_pool_t **newpool, apr_abortfunc_t abort_fn, apr_allocator_t *allocator, const char *file_line)
  • apr_status_t apr_pool_create_unmanaged_ex_debug (apr_pool_t **newpool, apr_abortfunc_t abort_fn, apr_allocator_t *allocator, const char *file_line)
  • apr_status_t apr_pool_create (apr_pool_t **newpool, apr_pool_t *parent)
  • apr_status_t apr_pool_create_core (apr_pool_t **newpool)
  • apr_status_t apr_pool_create_unmanaged (apr_pool_t **newpool)
  • apr_allocator_t * apr_pool_allocator_get (apr_pool_t *pool)
  • void apr_pool_clear (apr_pool_t *p)
  • void apr_pool_clear_debug (apr_pool_t *p, const char *file_line)
  • void apr_pool_destroy (apr_pool_t *p)
  • void apr_pool_destroy_debug (apr_pool_t *p, const char *file_line)
  • void * apr_palloc (apr_pool_t *p, apr_size_t size)
  • void * apr_palloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)
  • void * apr_pcalloc (apr_pool_t *p, apr_size_t size)
  • void * apr_pcalloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)
  • void apr_pool_abort_set (apr_abortfunc_t abortfunc, apr_pool_t *pool)
  • apr_abortfunc_t apr_pool_abort_get (apr_pool_t *pool)
  • apr_pool_t * apr_pool_parent_get (apr_pool_t *pool)
  • int apr_pool_is_ancestor (apr_pool_t *a, apr_pool_t *b)
  • void apr_pool_tag (apr_pool_t *pool, const char *tag)
  • apr_status_t apr_pool_userdata_set (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
  • apr_status_t apr_pool_userdata_setn (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
  • apr_status_t apr_pool_userdata_get (void **data, const char *key, apr_pool_t *pool)

2009年5月24日 星期日

Request in Apache請求處理的流程

我們必須先知道一件事,使用者的瀏覽器(browser)向Apache伺服器請求資料時,必須先建立TPC連線,依照HTTP通訊協定的規範向Apache伺服器請求資料。也就是說,Aapche處理請求的流程可以分為:建立TPC連線之前、建立TPC連線之後(開始用HTTP),這兩個部分。

請求處理的流程
  1. 接收請求(Accept Request)
  2. 中繼資料處理(Metadata Processing),request會傳遞很多個掛勾(hook),呼叫所屬階段的程序(function)
  3. 產生回應內容(Content Generator)
  4. 記錄(Logging)
正常來說,大致上是依據這個流程,如果是錯誤(error)或是產生新請求,則有可能使得request導向其他程序,另外,每個程序也可以讓其他程序呼叫,簡言之,有點複雜!(應該要畫圖解釋...)

處理的流程當中,Apache提供掛勾(hook)這項功能,可讓我們開發應用模組(module)的時候,利用回呼(callback)的方式值型模組中的功能。掛勾的總類依照請求的先後順序為:
  1. ap_hook_post_read_request
  2. ap_hook_translate_name
  3. ap_hook_map_to_storage
  4. ap_hook_header_parser
  5. ap_hook_access_checker
  6. ap_hook_check_user_id
  7. ap_hook_auth_checker
  8. ap_hook_type_checker
  9. ap_hook_fixups
  10. ap_hook_handler
  11. ap_hook_log_transaction
最後,Apache預設處理請求的程式是掛在ap_hook_handler之中,其函數原形為static int default_handler(request_rec *r),位於server\core.c檔案當中。

2009年5月23日 星期六

Apache Core Objects主要物件

Apache程式碼定義了一些結構可以使用,其中有四個結構相當重要,會在開發應用程式模組中使用到,主要是定義在httpd.h這個標頭檔中,其結構名稱有:
  • request_rec每個請求的相關資訊
  • server_rec每個主機的相關資訊
  • conn_rec每次連線的相關資訊
  • process_rec每個程序的相關資訊
詳細程式碼如下:

struct request_rec {
    /** The pool associated with the request */
    apr_pool_t *pool;
    /** The connection to the client */
    conn_rec *connection;
    /** The virtual host for this request */
    server_rec *server;

    /** Pointer to the redirected request if this is an external redirect */
    request_rec *next;
    /** Pointer to the previous request if this is an internal redirect */
    request_rec *prev;

    /** Pointer to the main request if this is a sub-request
     * (see http_request.h) */
    request_rec *main;

    /* Info about the request itself... we begin with stuff that only
     * protocol.c should ever touch...
     */
    /** First line of request */
    char *the_request;
    /** HTTP/0.9, "simple" request (e.g. GET /foo\n w/no headers) */
    int assbackwards;
    /** A proxy request (calculated during post_read_request/translate_name)
     *  possible values PROXYREQ_NONE, PROXYREQ_PROXY, PROXYREQ_REVERSE,
     *                  PROXYREQ_RESPONSE
     */
    int proxyreq;
    /** HEAD request, as opposed to GET */
    int header_only;
    /** Protocol string, as given to us, or HTTP/0.9 */
    char *protocol;
    /** Protocol version number of protocol; 1.1 = 1001 */
    int proto_num;
    /** Host, as set by full URI or Host: */
    const char *hostname;

    /** Time when the request started */
    apr_time_t request_time;

    /** Status line, if set by script */
    const char *status_line;
    /** Status line */
    int status;

    /* Request method, two ways; also, protocol, etc..  Outside of protocol.c,
     * look, but don't touch.
     */

    /** Request method (eg. GET, HEAD, POST, etc.) */
    const char *method;
    /** M_GET, M_POST, etc. */
    int method_number;

    /**
     *  'allowed' is a bitvector of the allowed methods.
     *
     *  A handler must ensure that the request method is one that
     *  it is capable of handling.  Generally modules should DECLINE
     *  any request methods they do not handle.  Prior to aborting the
     *  handler like this the handler should set r->allowed to the list
     *  of methods that it is willing to handle.  This bitvector is used
     *  to construct the "Allow:" header required for OPTIONS requests,
     *  and HTTP_METHOD_NOT_ALLOWED and HTTP_NOT_IMPLEMENTED status codes.
     *
     *  Since the default_handler deals with OPTIONS, all modules can
     *  usually decline to deal with OPTIONS.  TRACE is always allowed,
     *  modules don't need to set it explicitly.
     *
     *  Since the default_handler will always handle a GET, a
     *  module which does *not* implement GET should probably return
     *  HTTP_METHOD_NOT_ALLOWED.  Unfortunately this means that a Script GET
     *  handler can't be installed by mod_actions.
     */
    apr_int64_t allowed;
    /** Array of extension methods */
    apr_array_header_t *allowed_xmethods; 
    /** List of allowed methods */
    ap_method_list_t *allowed_methods; 

    /** byte count in stream is for body */
    apr_off_t sent_bodyct;
    /** body byte count, for easy access */
    apr_off_t bytes_sent;
    /** Last modified time of the requested resource */
    apr_time_t mtime;

    /* HTTP/1.1 connection-level features */

    /** sending chunked transfer-coding */
    int chunked;
    /** The Range: header */
    const char *range;
    /** The "real" content length */
    apr_off_t clength;

    /** Remaining bytes left to read from the request body */
    apr_off_t remaining;
    /** Number of bytes that have been read  from the request body */
    apr_off_t read_length;
    /** Method for reading the request body
     * (eg. REQUEST_CHUNKED_ERROR, REQUEST_NO_BODY,
     *  REQUEST_CHUNKED_DECHUNK, etc...) */
    int read_body;
    /** reading chunked transfer-coding */
    int read_chunked;
    /** is client waiting for a 100 response? */
    unsigned expecting_100;

    /* MIME header environments, in and out.  Also, an array containing
     * environment variables to be passed to subprocesses, so people can
     * write modules to add to that environment.
     *
     * The difference between headers_out and err_headers_out is that the
     * latter are printed even on error, and persist across internal redirects
     * (so the headers printed for ErrorDocument handlers will have them).
     *
     * The 'notes' apr_table_t is for notes from one module to another, with no
     * other set purpose in mind...
     */

    /** MIME header environment from the request */
    apr_table_t *headers_in;
    /** MIME header environment for the response */
    apr_table_t *headers_out;
    /** MIME header environment for the response, printed even on errors and
     * persist across internal redirects */
    apr_table_t *err_headers_out;
    /** Array of environment variables to be used for sub processes */
    apr_table_t *subprocess_env;
    /** Notes from one module to another */
    apr_table_t *notes;

    /* content_type, handler, content_encoding, and all content_languages 
     * MUST be lowercased strings.  They may be pointers to static strings;
     * they should not be modified in place.
     */
    /** The content-type for the current request */
    const char *content_type; /* Break these out --- we dispatch on 'em */
    /** The handler string that we use to call a handler function */
    const char *handler; /* What we *really* dispatch on */

    /** How to encode the data */
    const char *content_encoding;
    /** Array of strings representing the content languages */
    apr_array_header_t *content_languages;

    /** variant list validator (if negotiated) */
    char *vlist_validator;
    
    /** If an authentication check was made, this gets set to the user name. */
    char *user;
    /** If an authentication check was made, this gets set to the auth type. */
    char *ap_auth_type;

    /** This response can not be cached */
    int no_cache;
    /** There is no local copy of this response */
    int no_local_copy;

    /* What object is being requested (either directly, or via include
     * or content-negotiation mapping).
     */

    /** The URI without any parsing performed */
    char *unparsed_uri;
    /** The path portion of the URI */
    char *uri;
    /** The filename on disk corresponding to this response */
    char *filename;
    /* XXX: What does this mean? Please define "canonicalize" -aaron */
    /** The true filename, we canonicalize r->filename if these don't match */
    char *canonical_filename;
    /** The PATH_INFO extracted from this request */
    char *path_info;
    /** The QUERY_ARGS extracted from this request */
    char *args;
    /**  finfo.protection (st_mode) set to zero if no such file */
    apr_finfo_t finfo;
    /** A struct containing the components of URI */
    apr_uri_t parsed_uri;

    /**
     * Flag for the handler to accept or reject path_info on 
     * the current request.  All modules should respect the
     * AP_REQ_ACCEPT_PATH_INFO and AP_REQ_REJECT_PATH_INFO 
     * values, while AP_REQ_DEFAULT_PATH_INFO indicates they
     * may follow existing conventions.  This is set to the
     * user's preference upon HOOK_VERY_FIRST of the fixups.
     */
    int used_path_info;

    /* Various other config info which may change with .htaccess files
     * These are config vectors, with one void* pointer for each module
     * (the thing pointed to being the module's business).
     */

    /** Options set in config files, etc. */
    struct ap_conf_vector_t *per_dir_config;
    /** Notes on *this* request */
    struct ap_conf_vector_t *request_config;

    /**
     * A linked list of the .htaccess configuration directives
     * accessed by this request.
     * N.B. always add to the head of the list, _never_ to the end.
     * that way, a sub request's list can (temporarily) point to a parent's list
     */
    const struct htaccess_result *htaccess;

    /** A list of output filters to be used for this request */
    struct ap_filter_t *output_filters;
    /** A list of input filters to be used for this request */
    struct ap_filter_t *input_filters;

    /** A list of protocol level output filters to be used for this
     *  request */
    struct ap_filter_t *proto_output_filters;
    /** A list of protocol level input filters to be used for this
     *  request */
    struct ap_filter_t *proto_input_filters;

    /** A flag to determine if the eos bucket has been sent yet */
    int eos_sent;

/* Things placed at the end of the record to avoid breaking binary
 * compatibility.  It would be nice to remember to reorder the entire
 * record to improve 64bit alignment the next time we need to break
 * binary compatibility for some other reason.
 */
};

struct server_rec {
    /** The process this server is running in */這個伺服器正在執行的行程,也許是虛擬伺服器
    process_rec *process;
    /** The next server in the list */位於清單中,下一個伺服器組態
    server_rec *next;

    /** The name of the server */這個伺服器的名子
    const char *defn_name;
    /** The line of the config file that the server was defined on */組態檔httpd.conf中,定義伺服器的行號
    unsigned defn_line_number;

    /* Contact information */

    /** The admin's contact information */管理員的聯絡資訊(電子信箱)
    char *server_admin;
    /** The server hostname */這個伺服器的主機名稱
    char *server_hostname;
    /** for redirects, etc. */用於重新導向,埠號
    apr_port_t port;

    /* Log files --- note that transfer log is now in the modules... */

    /** The name of the error log */錯誤記錄檔的名稱
    char *error_fname;
    /** A file descriptor that references the error log */指標,指向錯誤記錄檔
    apr_file_t *error_log;
    /** The log level for this server */記錄檔記錄的層次
    int loglevel;

    /* Module-specific configuration for server, and defaults... */

    /** true if this is the virtual server */是否是虛擬伺服器
    int is_virtual;
    /** Config vector containing pointers to modules' per-server config 
     *  structures. */指向伺服器組態的向量
    struct ap_conf_vector_t *module_config; 
    /** MIME type info, etc., before we start checking per-directory info */MIME型態資訊
    struct ap_conf_vector_t *lookup_defaults;

    /* Transaction handling */

    /** I haven't got a clue */還不知道這是什麼!
    server_addr_rec *addrs;
    /** Timeout, as an apr interval, before we give up */逾時時間
    apr_interval_time_t timeout;
    /** The apr interval we will wait for another request */持續連線的逾時時間
    apr_interval_time_t keep_alive_timeout;
    /** Maximum requests per connection */每個連線的最大請求數目
    int keep_alive_max;
    /** Use persistent connections? */是否使用http的持續連線
    int keep_alive;

    /** Pathname for ServerPath */伺服器路徑的名稱
    const char *path;
    /** Length of path */路徑長度
    int pathlen;

    /** Normal names for ServerAlias servers */別名伺服器的正常名稱
    apr_array_header_t *names;
    /** Wildcarded names for ServerAlias servers */別名伺服器的萬用名稱
    apr_array_header_t *wild_names;

    /** limit on size of the HTTP request line    */請求的行數限制
    int limit_req_line;
    /** limit on size of any request header field */請求標頭欄位大小的限制
    int limit_req_fieldsize;
    /** limit on number of request header fields  */請求標頭欄位數目的限制
    int limit_req_fields; 

    /** The server request scheme for redirect responses */重新導向的請求方法
    const char *server_scheme;
};

struct conn_rec {
    /** Pool associated with this connection */
    apr_pool_t *pool;
    /** Physical vhost this conn came in on */
    server_rec *base_server;
    /** used by http_vhost.c */
    void *vhost_lookup_data;

    /* Information about the connection itself */
    /** local address */
    apr_sockaddr_t *local_addr;
    /** remote address */
    apr_sockaddr_t *remote_addr;

    /** Client's IP address */
    char *remote_ip;
    /** Client's DNS name, if known.  NULL if DNS hasn't been checked,
     *  "" if it has and no address was found.  N.B. Only access this though
     * get_remote_host() */
    char *remote_host;
    /** Only ever set if doing rfc1413 lookups.  N.B. Only access this through
     *  get_remote_logname() */
    char *remote_logname;

    /** Are we still talking? */
    unsigned aborted:1;

    /** Are we going to keep the connection alive for another request?
     * @see ap_conn_keepalive_e */
    ap_conn_keepalive_e keepalive;

    /** have we done double-reverse DNS? -1 yes/failure, 0 not yet, 
     *  1 yes/success */
    signed int double_reverse:2;

    /** How many times have we used it? */
    int keepalives;
    /** server IP address */
    char *local_ip;
    /** used for ap_get_server_name when UseCanonicalName is set to DNS
     *  (ignores setting of HostnameLookups) */
    char *local_host;

    /** ID of this connection; unique at any point in time */
    long id; 
    /** Config vector containing pointers to connections per-server
     *  config structures. */
    struct ap_conf_vector_t *conn_config;
    /** Notes on *this* connection: send note from one module to
     *  another. must remain valid for all requests on this conn */
    apr_table_t *notes;
    /** A list of input filters to be used for this connection */
    struct ap_filter_t *input_filters;
    /** A list of output filters to be used for this connection */
    struct ap_filter_t *output_filters;
    /** handle to scoreboard information for this connection */
    void *sbh;
    /** The bucket allocator to use for all bucket/brigade creations */
    struct apr_bucket_alloc_t *bucket_alloc;
    /** The current state of this connection */
    conn_state_t *cs;
    /** Is there data pending in the input filters? */ 
    int data_in_input_filters;
    
    /** Are there any filters that clogg/buffer the input stream, breaking
     *  the event mpm.
     */
    int clogging_input_filters;
};

struct process_rec {
    /** Global pool. Cleared upon normal exit */
    apr_pool_t *pool;
    /** Configuration pool. Cleared upon restart */
    apr_pool_t *pconf;
    /** Number of command line arguments passed to the program */
    int argc;
    /** The command line arguments */
    const char * const *argv;
    /** The program name used to execute the program */
    const char *short_name;
};

Apache Multi-Processing Modules多行程模組

MPM是Apache中的一個模組,全名為Multi-Processing Module多行程模組,這個模組是介於APR和底層的作業系統(Operation System, OS)的一個模組,其目的是用來最佳化,使得Apache在各式的OS上能有傑出的效能表現。

MPM本身是系統層級(system-level)的模組,意思就是說,開發上和其他模組會不一樣,哪裡不一樣?目前還在研究當中。不過,我們大多數的應用只會按照一般模組的樣子來開發,絕大多數都是這種開發方式(即非系統層級)。

早期Apache發展在UNIX之類的作業系統上,這類系統多為多處理器的伺服器,而作業系統會依照請求(request)分岔(fork)產生其他行程(process)來處理,一般來說,Apache啟動的時候,會產生一個集散區(pool,也有稱為"池")來管理這些行程。之後2.0版本,為了能夠跨平台(cross-platform)的高效率、最佳化運作,所以才設計出MPM這個模組。

實務應用上來說,只有UNIX之類的作業系統有較多的設定可供選擇使用。我目前是以windows開發,真的選項就只有兩個可以設定:MaxRequestsPerChildThreadsPerChild兩個指令。其他指令請參考http://httpd.apache.org/docs/2.2/mod/mpm_common.html

在MPM這個部分當中,因為跟底層OS有密切關係,其實MPM有好幾個類別,有些是給UNIX用的,有些是給Windows(WIN32)用的,有些則給其他OS用的,所以再看這些指令的時候,必須注意它所依賴的模組,否則社定會失敗。另一個問題是,我們可以選擇使用哪個MPM嗎?應該是不行,除非你自己重新complie一次,MPM的設定在編譯Apache時就已經決定了!

以UNIX這群的MPM來說,有Prefork MPM、Worker MPM以及正在試驗的Event MPM。
  • Prefork MPM是非執行緒的架構,和以往1版的類似。
  • Worker MPM是多執行緒的架構,其執行緒和每個請求連線有關。
  • Event MPM是正在試驗當中,將執行緒與請求連線的關係解耦合,可以避免請求連線超出執行緒的問題。
結論:開發Apache的模組、應用程式來說,不需要理會MPM,請忽略MPM這個模組!

我目前以windows開發,所以主要是專注於mpm_winnt,其指令Directives有
  • CoreDumpDirectory
  • Listen
  • ListenBacklog
  • MaxMemFree
  • MaxRequestsPerChild
  • PidFile
  • ReceiveBufferSize
  • ScoreBoardFile
  • SendBufferSize
  • ThreadLimit
  • ThreadsPerChild
  • ThreadStackSize
  • Win32DisableAcceptEx

2009年5月22日 星期五

Deconstruction of Apache Code解構阿帕契程式碼

太久,真的是一個月沒有更新部落格。忙著,也跟著茫了。

目前正在使用Apache Http Server的程式撰寫。我想之後會寫有關於Apache的程式碼內容,希望有這個能力和空閒時間。(忙著活下去,還有時間嗎!好問題~)

先講參考書籍,討論Apache的書籍很少(手指頭的數目之內),中文可以參考下面這本:
Ben Laurie & Peter Laurie原著,郭文生編譯,Apache技術手冊(第三版),台北:歐萊禮,2003。

怎麼開始呢?Deconstruction,解構。應該不稱之為解構主義,一方面我現在不了解何謂解構主義,只是想將Apache的Code猜(拆)解開來看看,這套軟體的運作方式?我們有沒有能力做出這樣的軟體呢?需要哪些能力和人力?如何設計更是大問題!(我的疑問)

Apache的進入點是main.c,在httpd這個專案(Project)裡面,程式碼架構如下:

用到的標頭檔,Apache底層是Apache Portability Runtime(APR),因此會引入許多apr_的header file,而基於APR建置的程式庫則有APR-util,簡稱APU,以及實現iconv()的APR-iconv。另外,http_是關於http protocol的一些處理,也需要因引入。

#include "apr.h" //平台定義
#include "apr_strings.h" //字串處理程式庫
#include "apr_getopt.h" //命令參數
#include "apr_general.h" //混雜的程式庫,未歸類的
#include "apr_lib.h" //通用程式庫
#include "apr_md5.h" //md5的程式庫,屬於APU
#include "apr_time.h" //時間程式庫
#include "apr_version.h" //APR的版本介面
#include "apu_version.h" //APU的版本介面

#define APR_WANT_STDIO
#define APR_WANT_STRFUNC
#include "apr_want.h" //APR對於標準標頭檔支援

#define CORE_PRIVATE
#include "ap_config.h" //定義掛勾hook的標頭檔
#include "httpd.h" //主要基本的定義
#include "http_main.h" //main程式的定義
#include "http_log.h" //記錄檔的定義
#include "http_config.h" //一些設定的定義
#include "http_core.h" //核心模組的定義
#include "http_vhost.h" //虛擬主機的定義
#include "apr_uri.h" //uri的程式庫,屬於APU
#include "util_ebcdic.h" //定義Extended Binary Coded Decimal Interchange Code(EBCDIC)的轉換功能
#include "ap_mpm.h" //定義Multi-Processing Module(MPM),多處理模組
#include "mpm_common.h" //MPM用的程式庫

定義到的函數(function):
static void show_mpm_settings(void)
static void show_compile_settings(void)
static void destroy_and_exit_process(process_rec *process, int process_exit_value)
static process_rec *init_process(int *argc, const char * const * *argv)
static void usage(process_rec *process)

主程式碼:


int main(int argc, const char * const argv[])
{
char c;
int configtestonly = 0;
const char *confname = SERVER_CONFIG_FILE;
const char *def_server_root = HTTPD_ROOT;
const char *temp_error_log = NULL;
const char *error;
process_rec *process;
server_rec *server_conf;
apr_pool_t *pglobal;
apr_pool_t *pconf;
apr_pool_t *plog; /* Pool of log streams, reset _after_ each read of conf */
apr_pool_t *ptemp; /* Pool for temporary config stuff, reset often */
apr_pool_t *pcommands; /* Pool for -D, -C and -c switches */
apr_getopt_t *opt;
apr_status_t rv;
module **mod;
const char *optarg;
APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server;

AP_MONCONTROL(0); /* turn off profiling of startup */

process = init_process(&argc, &argv);
pglobal = process->pool;
pconf = process->pconf;
ap_server_argv0 = process->short_name;

#if APR_CHARSET_EBCDIC
if (ap_init_ebcdic(pglobal) != APR_SUCCESS) {
destroy_and_exit_process(process, 1);
}
#endif

apr_pool_create(&pcommands, pglobal);
apr_pool_tag(pcommands, "pcommands");
ap_server_pre_read_config = apr_array_make(pcommands, 1, sizeof(char *));
ap_server_post_read_config = apr_array_make(pcommands, 1, sizeof(char *));
ap_server_config_defines = apr_array_make(pcommands, 1, sizeof(char *));

error = ap_setup_prelinked_modules(process);
if (error) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, "%s: %s",
ap_server_argv0, error);
destroy_and_exit_process(process, 1);
}

ap_run_rewrite_args(process);

/* Maintain AP_SERVER_BASEARGS list in http_main.h to allow the MPM
* to safely pass on our args from its rewrite_args() handler.
*/
apr_getopt_init(&opt, pcommands, process->argc, process->argv);

while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg))
== APR_SUCCESS) {
char **new;

switch (c) {
case 'c':
new = (char **)apr_array_push(ap_server_post_read_config);
*new = apr_pstrdup(pcommands, optarg);
break;

case 'C':
new = (char **)apr_array_push(ap_server_pre_read_config);
*new = apr_pstrdup(pcommands, optarg);
break;

case 'd':
def_server_root = optarg;
break;

case 'D':
new = (char **)apr_array_push(ap_server_config_defines);
*new = apr_pstrdup(pcommands, optarg);
/* Setting -D DUMP_VHOSTS is equivalent to setting -S */
if (strcmp(optarg, "DUMP_VHOSTS") == 0)
configtestonly = 1;
/* Setting -D DUMP_MODULES is equivalent to setting -M */
if (strcmp(optarg, "DUMP_MODULES") == 0)
configtestonly = 1;
break;

case 'e':
if (strcasecmp(optarg, "emerg") == 0) {
ap_default_loglevel = APLOG_EMERG;
}
else if (strcasecmp(optarg, "alert") == 0) {
ap_default_loglevel = APLOG_ALERT;
}
else if (strcasecmp(optarg, "crit") == 0) {
ap_default_loglevel = APLOG_CRIT;
}
else if (strncasecmp(optarg, "err", 3) == 0) {
ap_default_loglevel = APLOG_ERR;
}
else if (strncasecmp(optarg, "warn", 4) == 0) {
ap_default_loglevel = APLOG_WARNING;
}
else if (strcasecmp(optarg, "notice") == 0) {
ap_default_loglevel = APLOG_NOTICE;
}
else if (strcasecmp(optarg, "info") == 0) {
ap_default_loglevel = APLOG_INFO;
}
else if (strcasecmp(optarg, "debug") == 0) {
ap_default_loglevel = APLOG_DEBUG;
}
else {
usage(process);
}
break;

case 'E':
temp_error_log = apr_pstrdup(process->pool, optarg);
break;

case 'X':
new = (char **)apr_array_push(ap_server_config_defines);
*new = "DEBUG";
break;

case 'f':
confname = optarg;
break;

case 'v':
printf("Server version: %s\n", ap_get_server_description());
printf("Server built: %s\n", ap_get_server_built());
destroy_and_exit_process(process, 0);

case 'V':
show_compile_settings();
destroy_and_exit_process(process, 0);

case 'l':
ap_show_modules();
destroy_and_exit_process(process, 0);

case 'L':
ap_show_directives();
destroy_and_exit_process(process, 0);

case 't':
configtestonly = 1;
break;

case 'S':
configtestonly = 1;
new = (char **)apr_array_push(ap_server_config_defines);
*new = "DUMP_VHOSTS";
break;

case 'M':
configtestonly = 1;
new = (char **)apr_array_push(ap_server_config_defines);
*new = "DUMP_MODULES";
break;

case 'h':
case '?':
usage(process);
}
}

/* bad cmdline option? then we die */
if (rv != APR_EOF || opt->ind < opt->argc) {
usage(process);
}

apr_pool_create(&plog, pglobal);
apr_pool_tag(plog, "plog");
apr_pool_create(&ptemp, pconf);
apr_pool_tag(ptemp, "ptemp");

/* Note that we preflight the config file once
* before reading it _again_ in the main loop.
* This allows things, log files configuration
* for example, to settle down.
*/

ap_server_root = def_server_root;
if (temp_error_log) {
ap_replace_stderr_log(process->pool, temp_error_log);
}
server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
if (!server_conf) {
destroy_and_exit_process(process, 1);
}

if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
NULL, "Pre-configuration failed");
destroy_and_exit_process(process, 1);
}

rv = ap_process_config_tree(server_conf, ap_conftree,
process->pconf, ptemp);
if (rv == OK) {
ap_fixup_virtual_hosts(pconf, server_conf);
ap_fini_vhost_config(pconf, server_conf);
apr_hook_sort_all();

if (configtestonly) {
ap_run_test_config(pconf, server_conf);
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK");
destroy_and_exit_process(process, 0);
}
}

signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server);
if (signal_server) {
int exit_status;

if (signal_server(&exit_status, pconf) != 0) {
destroy_and_exit_process(process, exit_status);
}
}

/* If our config failed, deal with that here. */
if (rv != OK) {
destroy_and_exit_process(process, 1);
}

apr_pool_clear(plog);

if ( ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
0, NULL, "Unable to open logs");
destroy_and_exit_process(process, 1);
}

if ( ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
NULL, "Configuration Failed");
destroy_and_exit_process(process, 1);
}

apr_pool_destroy(ptemp);

for (;;) {
apr_hook_deregister_all();
apr_pool_clear(pconf);

for (mod = ap_prelinked_modules; *mod != NULL; mod++) {
ap_register_hooks(*mod, pconf);
}

/* This is a hack until we finish the code so that it only reads
* the config file once and just operates on the tree already in
* memory. rbb
*/
ap_conftree = NULL;
apr_pool_create(&ptemp, pconf);
apr_pool_tag(ptemp, "ptemp");
ap_server_root = def_server_root;
server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
if (!server_conf) {
destroy_and_exit_process(process, 1);
}

if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
0, NULL, "Pre-configuration failed");
destroy_and_exit_process(process, 1);
}

if (ap_process_config_tree(server_conf, ap_conftree, process->pconf,
ptemp) != OK) {
destroy_and_exit_process(process, 1);
}
ap_fixup_virtual_hosts(pconf, server_conf);
ap_fini_vhost_config(pconf, server_conf);
apr_hook_sort_all();
apr_pool_clear(plog);
if (ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
0, NULL, "Unable to open logs");
destroy_and_exit_process(process, 1);
}

if (ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
0, NULL, "Configuration Failed");
destroy_and_exit_process(process, 1);
}

apr_pool_destroy(ptemp);
apr_pool_lock(pconf, 1);

ap_run_optional_fn_retrieve();

if (ap_mpm_run(pconf, plog, server_conf))
break;

apr_pool_lock(pconf, 0);
}

apr_pool_lock(pconf, 0);
destroy_and_exit_process(process, 0);

return 0; /* Termination 'ok' */
}


2009年4月21日 星期二

Multithreading in Windows Forms

當你寫一個Windows Forms的Application時候,如果再用上Thread的功能,這時候可能就會發生:跨執行緒作業無效: 存取控制項 'controls_name' 時所使用的執行緒與建立控制項的執行緒不同。哇!我也是第一次遇到,程式寫的少了...還好有解,並得到建德兄的幫助來解決。


2009年4月19日 星期日

TPC-W基準

TPC-W是TPC所制定的一個基準(benchmark),主要是用在交易型網路電子商務(transactional web e-Commerce)上面,目的為測試系統的效能並給予量化(給出數據)。

TPC-W所測量出來的數據單位為WIPS(Web Interaction Per Second),以表示效能(performance)的程度,這就是throughput的多寡。2.0版本之後,是用SIPS(Service Interaction Per Second)的單位,不過,目前的資料多以WIPS為單位就是了,可能是2.0版本還未實際應用。

除了WIPS之外,也有將總體成本除以throughput的方式,用以評斷設備是不是划算(即Cost/Performance, CP值)!其單位為$/WIPS。

TPC-W的量測以一個應用伺服器(application server)為主,避免cluster的問題使得benchmark的數據有所差異。也就是說在TPC-W個規範當中,你是不能用來測量cluster的架構!

TPC-W中有些專有名詞,其定義如下:
  • Emulated Browser (EB), Emulated Business (EB)
    模擬的瀏覽器,功能是向SUT發送工作量(workload)。2.0版的文件改用Emulated Business 表示。
  • Remote Browser Emulator (RBE), Remote Business Emulator (RBE)
    這是用來管理和控制EB的軟體。
  • Business Session Length (BSL)
    交易的數量,也就是REB和SUT之間的交易數量。
  • System Under Test (SUT)
    欲測試的系統,可能包含多個server和router。
  • Payment Gateway Emulator (PGE)
    模擬SUT外面的付費授權機制的服務,提供帳務處理功能。
目前網路上已有實作的程式碼,有需要研究的人可以參考,分別如下:
根據我最近的研究,2.0版本似乎不夠完全,1.8版的benchmark比較正常一點!而且實做的程式目前只到1.8版。另外,2.0個好像跟1.x完全不一樣。

2009年4月13日 星期一

Benchmark

今天談談Benchmark,Benchmark的中文是基準,也有人說是標竿測試(應該用benchmark test)、測試基準,中文的意思實在很難照字面解釋。Benchmark的意思是:一些基本的方法來量測系統的效能(這是一個活動,所以英文用benchmarking)。而Benchmark通常指的是測試用的軟體工具。

在量測系統效能當中,有兩項數據重要:時間(time)比率(rate)。時間指的是反應時間(response time);比率指的是工作處理量,通常是單位時間的交易量、單位時間的回應次數,或者說是throughput。其實一個是從使用者的角度來觀看,另一個是從管理者的角度來觀看。

比率的量測中以MIPS為單位,什麼是MIPS?MIPS即Millions of instructions per second,每秒條百萬指令,通常是用來測試CPU(而CPU不太會用頻率Hz為基準比較)。要注意的是MIPS也不一定準確,因為CPU有區分RISCCISC兩種,所以用一件工作來比較,有可能產生不同效能結果。

系統效能(System Performance)是一件很複雜的事情,因為系統包含多個軟體與硬體,要量測更是困難(使用benchmark),就算我們得到其結果,要解釋其效能結果更不是一件容易的事情。

Benchmark從量測的性質來看,可以分為兩大類:粗糙基準(coarse-grain benchmark),細微基準(fine-grain benchmark)。另外,以系統角度來看可以分為四個階層(hierarchy),由內而外分別是。
  1. Basic Operations(最內層)
    加減法運算,測量CPU之類。對系統效能量測實用性低。
  2. Toy Benchmark
    用一些難解的問題(puzzle)計算。
  3. Kernels
    主要是一些程式的片段,或者函數之類。測量系統某部分的效能。
  4. Real Programs(最外層)
    最趨於真實程式的測量,也是對系統的效能量測實用性最高的。(有點廢話...)
要注意的是,每一種benchmark有不同的運用場合(domain-oriented),好比你不能拿測量CPU的軟體去測量網路頻寬,可能不能測量,就算可以,其結果也沒有什麼意義!(有點誇張的例子,但要注意且記得!)

常用的benchmark有下列單位制訂:(中文看看就好,沒有官方的中文名字,而且翻譯也怪怪的)
  • Transaction Processing Performance Council (交易處理效能機構),簡稱TPC
    有TPC-C, TPC-H, TPC-R, TPC-W
  • Standard Performance Evaluation Corporation(標準性能評估機構),簡稱SPEC。
    有SPECxxxx, SPECweb
效能測試(Performance Test)的類型基本上有3種:
  • Load Testing(負載測試)
    模擬實際的使用情形,取得測試結果,看看是不是符合要求。
  • Stress Testing(壓力測試)
    在最糟的情況下,對服務施加最大負載量。看看是不是能正常運作。
  • Spike Testing(突波測試)
    施加比平均負載高出好幾倍的量,通常施加的時間為一段小週期。
以上簡短介紹Benchmark和Performance Test。

HttpWebRequest and HttpWebResponse

之前有介紹.NET Framework的WebClient這個類別,用了用了發現我還需要更多功能,只好往下一層類別來找答案。WebClient用起來真的很容易,程式沒有幾行就可以做到一個功能,所以學習的時候,建議先用這個類別來玩玩看,先從簡單的開始,這是解決問題的第一步。

HttpWebRequest和HttpWebResponse兩個類別,是從另外兩個抽象類別繼承而來,各分別WebRequestWebResponse兩個,你待會看程式的時候,就會發現比起WebClient要多幾個步驟才能做一件事。

在開始使用HttpWebRequest和HttpWebResponse之前,我們先來看看HTTP(Hypertext Transfer Protocol)這個通訊協定,似乎HTTP隨著WWW的發展越來越顯得重要,太多地方都用了!HTTP所傳送的message有兩種格式,一個是request message,另一個是response message。(這不是廢話,client-server的架構大概都是這樣。下面列出message的大概格式,主要是給個view,要詳細的資料請google)

Request Message
  • Request Line
    method+sp+URL+sp+Version+cr+lf
  • Header Lines
    header field name:+sp+value+cr+lf
    header field name:+sp+value+cr+lf
    ...
  • Blank Line
    cr+lf
  • Entity Body
    要求的上傳資料
Response Message
  • Status Line
    version+sp+status code+sp+phrase+cr+lf
  • Header Lines
    header field name:+sp+value+cr+lf
    header field name:+sp+value+cr+lf
    ...
  • Blank Line
    cr+lf
  • Entity Body
    回傳的資料(網頁內容)
說明一下上面兩個message的意思,因為畫表格說明不太容易,只好用條列式的方式展現。注意:所有的HTTP message都是用ASCII文字。(+號是為了區分欄位,實際上的message沒有出現)
  • cr= carriage return(回到最前面)
  • lf=line feed(換行)
  • sp=space(空白)
接下來看看程式,用同步(Synchronous)的方式:


//取得URI識別
Uri thisUri = new Uri(requestTextBox.Text);

HttpWebRequest thisHttpWebRequest = (HttpWebRequest)WebRequest.Create(thisUri);

//設定快取原則
HttpRequestCachePolicy thisHttpRequestCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
thisHttpWebRequest.CachePolicy = thisHttpRequestCachePolicy;

//提出同步要求,並且傳回包含回應的HttpWebResponse物件
HttpWebResponse thisHttpWebResponse = (HttpWebResponse)thisHttpWebRequest.GetResponse();

//取得回應的header相關資料
messageTextBox.Text += thisHttpWebResponse.IsFromCache.ToString() + Environment.NewLine;
messageTextBox.Text += thisHttpWebResponse.StatusCode.ToString() + Environment.NewLine;

//將多行的TextBox移到最後一列
messageTextBox.Select(messageTextBox.Text.Length, 0);
messageTextBox.ScrollToCaret();//將控制項的內容捲動到目前插入號的位置

//關閉資料流,並釋放連線以重複使用
thisHttpWebResponse.Close();

2009年4月11日 星期六

System Properties系統特性

系統特性(System Properties)當中有下列幾項定義,這邊介紹主要是用在效能評估上面,因為排隊理論通常就是用來解決這樣的問題,我們先定義,之後才有辦法討論。(這是參考Queueing Networks and Markov Chains: Modeling and Performance Evaluation with Computer Science Applications, 2nd Edition)


  • Performance:(效能)
    The degree to which a system or component accomplishes its designated functions within given constraints, such as speed, accuracy, or memory usage.
  • Reliability:(可靠性)
    The probability that the software will not cause the failure of the system for a specified time under specified condition.
  • Availability:(可利用性)
    The ability of a system to perform its required function at a stated instant or over a stated period of time.
  • Dependability:(可依賴性)
    The dependability of a computer system is the ability to deliver a service that can justifiably be trusted.
  • Performability:(可執行性)
    The probability that the system reaches an accomplishment level y over a utilization interval (0,t).

2009年4月10日 星期五

Computer Simulation電腦模擬

在工程和科學的領域當中,除了實際測量或是試驗之外,我們還可以模擬自然或人為(man-made)的現象。其中所模擬的對象是系統(system),什麼是系統呢?一個系統可以視為元件的集合,組織這些元件使得它們可以完成一件目的。好比說一台電腦就是一個系統,包含無數個元件(CPU、記憶體、硬碟、主機板、鍵盤...),這些元件可以完成你要的一個目的,比方說上網、玩遊戲、看電影...。

所謂的模擬(simulation)是根據以往經驗(empirically)來決定系統特性(characteristics)的一個實驗,主要方式為產生(generation)與觀察(observation)系統的資料,於是就能夠知道系統的一些結論(inference)。

為何說是根據經驗呢?因為人類所知有限,根據人類的觀察和所推討的理論,盡可能來推測真實的發生情形。例如我們都知道東西會往下掉,推導出萬有引力的理論,那是不是全宇宙都是符合這個定律?我們不曉得,根據以往經驗來看是這樣的(東西會往下掉)。同樣的道理,模擬也是基於這樣的觀念,模擬必須有個模型(model),而此模型則是基於經驗與理論的基礎,並且要盡可能模仿(mimic)仿真(emulate)系統的行為。

一個模型中的狀態以狀態變數(state variable)來表示,電腦上則是程式的變數(variable),一個模擬軟體事實上就是改變這些狀態變數,最後來觀測這些變數的數值,那要怎麼改變呢?則依照系統的模型(數學關係)。

模擬所需的模型有下列幾種分類方法:
  1. Continuous/Discrete
  2. Deterministic/Stochastic
  3. Terminating/Steady-State
  4. Synthetic(Distribution-Driven)/Trace-Driven
  5. Sequential/Distributed
  6. Symbolic
  7. Event-Oriented/Process-Oriented
回到基本問題(模擬哲學),如果我們已經建立模型,而且證實這個模型式可用的、可靠的,我們要如何求解這個模型呢?方法有兩種,一個是模擬解(Simulative Solution),另一個是分析解(Analytic Solution),到底該用哪一種方法呢?

2009年4月9日 星期四

WebClient Class

MSDN
英:http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx
中:http://msdn.microsoft.com/zh-tw/library/system.net.webclient.aspx

先做個簡單的程式來學學網路應用程式,System.Net內的類別當中,我先用最高層次的類別來玩玩看,這個類別是WebClient,只能讓你當作client來使用。

控制項
  • requestTextBox=輸入的網址
  • messageTextBox=訊息輸出
程式


try
{
//取得URI識別
Uri thisUri = new Uri(requestTextBox.Text);

WebClient thisWebClient = new WebClient();
//設定快取原則
thisWebClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
//設定用於上載和下載字串的Encoding
thisWebClient.Encoding = Encoding.UTF8;

//下載回應的body資料為string,呼叫DownloadString這會開始執行下載動作
String thisDownloadString = thisWebClient.DownloadString(thisUri);

//取得回應的header資料
messageTextBox.Text += thisWebClient.ResponseHeaders.ToString() + Environment.NewLine;
messageTextBox.Text += thisDownloadString + Environment.NewLine;

//將多行的TextBox移到最後一列
messageTextBox.Select(messageTextBox.Text.Length, 0);
messageTextBox.ScrollToCaret();//將控制項的內容捲動到目前插入號的位置

}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
//clean up
}



上述是同步(Synchronous)的方式,程式需等待下載命令執行完之後,才能繼續run,你的成是有可能會當掉鎖住,解決方式就是使用非同步(Asynchronous )方式,非同步方法不會封鎖呼叫執行緒



try
{
//取得URI識別
Uri thisUri = new Uri(requestTextBox.Text);

WebClient thisWebClient = new WebClient();
//設定快取原則
thisWebClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
//設定用於上載和下載字串的Encoding
thisWebClient.Encoding = Encoding.UTF8;

//委派處理WebClient的DownloadStringCompleted 事件的方法
thisWebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(thisWebClient_DownloadStringCompleted);

//呼叫DownloadString這會開始執行下載動作
thisWebClient.DownloadStringAsync(thisUri);

}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
//clean up
}

void thisWebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//throw new NotImplementedException();
try
{
//取得回應的body資料
String thisDownloadString = e.Result.ToString() + Environment.NewLine;

//取得回應的header資料
messageTextBox.Text += ((WebClient)sender).ResponseHeaders.ToString() + Environment.NewLine;
messageTextBox.Text += thisDownloadString + Environment.NewLine;

//將多行的TextBox移到最後一列
messageTextBox.Select(messageTextBox.Text.Length, 0);
messageTextBox.ScrollToCaret();//將控制項的內容捲動到目前插入號的位置

}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
//clean up
}
}

2009年4月6日 星期一

Networking Programming網路程式設計

本篇文章介紹使用.NET Framework來撰寫網路應用程式,參考書籍可以看「Visual C# 2008網路程式設計之道」(真的是一本參考書),基峯資訊出版,黃嘉輝著,訂價台幣550元整。如果不想買的話,可以參考MSDN的網站,書裡面的內容和MSDN是差不多的,可以省下一些錢。

黃嘉輝(2008),Visual C# 2008網路程式設計之道,台北:碁峰。

要寫網路程式的話,最主要利用兩個namespace的類別(class),一個是System.Net,另一個是System.Net.Sockets

其中,建德兄說網路程式的設計與撰寫,可以利用非同步程式設計模式,參考http://msdn.microsoft.com/zh-tw/library/ms228969.aspx,這會是比較優秀的方法,避免視窗程式鎖住當機。

首先介紹OSI七層,貼在這裡當作參考(關鍵字);
  • Application
  • Presentation
  • Session
  • Transport
  • Network
  • Data Link
  • Physical
TCP/IP類的模型為五層:(多數英文教科書的分類)
  • Application Layer(HTTP, FTP, SMTP, DNS...)
  • Transport Layer(TCP, UDP)
  • Network Layer(IP)
  • Data Link Layer(Ethernet)
  • Physical Layer
在.NET Framework中有許多類別可以使用,依照逞用程度和功能性來區分:(其中符號||左邊為client用,右邊為server用)
  • System.Net.WebClient||無
  • System.Net.WebRequest, System.Net.WebResponse||無
  • System.Net.TcpClient,System.Net.UdpClient||System.Net.TcpListner,System.Net.UdpListner
  • System.Net.Sockets(沒有區分client-server)
依照容易使用(ease of use)的程度來看,最上面最簡單容易使用,而最下面System.Net.Sockets使用較複雜,相對的,因為System.Net.Sockets較複雜,原因是功能性(Functionality)最完善,你可以自己處理最多功能。到底要用哪一個類別來開發,依照需求來決定。

WebClient類別來說,WebClient使用下層的WebRequestWebResponse製作,避免讓我們接觸下層的複雜功能,也因此限制了一些!所以使用上要注意,進階功能還是要使用下層的類別。另外,WebClient類別僅支援HTTP、FTP、file的通訊協定,這可能是因為WebRequest和WebResponse的繼承類別只有這三類,所以僅支援HTTP、FTP、file囉!

如果要操作TCP和UDP的話,就使用System.Net.TcpClient ,System.Net.UdpClient, System.Net.TcpListner, System.Net.UdpListner這些類別。如果不要通訊協定的話,那就用最底層的System.Net.Sockets類別,Sockets類別跟任何通訊協定沒有關係。

另外,由於抽象類別WebRequestWebResponse有其他類別繼承,所謂的抽象類別(Abstract Class)是僅可以讓其他類別繼承(inherit),不能產生實體,你不能直接拿WebRequest和WebResponse來用,記住抽象類別這個特性。其關係為:

System.Net.WebRequest的子類別:
  • HttpWebRequest
  • FtpWebRequest
  • FileWebRequest

System.Net.WebResponse的子類別:
  • HttpWebResponse
  • FtpWebResponse
  • FileWebResponse