Tinywebserver:一個簡易的web伺服器
這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。
首先回顧程式中的核心內容和主要問題,最後給出相關程式碼。
0. 功能和I/O模型
實現簡易的HTTP服務端,現僅支援GET方法,通過瀏覽器訪問可以返回相應內容。
I/O模型採用Reactor(I/O複用 + 非阻塞I/O) + 執行緒池。 使用epoll事件迴圈用作事件通知,如果listenfd上可讀,則呼叫accept,把新建的fd加入epoll中;
是已連線sockfd,將其加入到執行緒池中由工作執行緒競爭執行任務。
1. 執行緒池怎麼實現?
程式採用c++編寫,要自己封裝一個簡易的執行緒池類。大致思路是建立固定數目的執行緒(如跟核數相同),然後類內部維護一個生產者—消費者佇列。
提供相應的新增任務(生產者)和執行任務介面(消費者)。按照作業系統書中典型的生產者—消費者模型維護增減佇列任務(使用mutex和semaphore)。
mutex用於互斥,保證任意時刻只有一個執行緒讀寫佇列,semaphore用於同步,保證執行順序(佇列為空時不要讀,佇列滿了不要寫)。
2. epoll用條件觸發(LT)還是邊緣觸發(ET)?
考慮這樣的情況,一個工作執行緒在讀一個fd,但沒有讀完。如果採用LT,則下一次事件迴圈到來的時候,又會觸發該fd可讀,此時執行緒池很有可能將該fd分配給其他的執行緒處理資料。
這顯然不是我們想要看到的,而ET則不會在下一次epoll_wait的時候返回,除非讀完以後又有新資料才返回。所以這裡應該使用ET。
當然ET用法在《Tinychatserver: 一個簡易的命令列群聊程式》也有總結過。用法的模式是固定的,把fd設為nonblocking,如果返回某fd可讀,迴圈read直到EAGAIN。
3. 繼續上面的問題,如果某個執行緒在處理fd的同時,又有新的一批資料發來(不是老資料沒讀完,是來新資料了),即使使用了ET模式,因為新資料的到來,仍然會觸發該fd可讀,所以仍然存在將該fd分給其他執行緒處理的情況。
這裡就用到了EPOLLONESHOT事件。對於註冊了EPOLLONESHOT事件的檔案描述符,作業系統最大觸發其上註冊的一個可讀、可寫或者異常事件,且只觸發一次。
除非我們使用epoll_ctl函式重置該檔案描述符上註冊的EPOLLONESHOT事件。這樣,當一個執行緒處理某個socket時,其他執行緒是不可能有機會操作該socket的,
即可解決該問題。但同時也要注意,如果註冊了EPOLLONESHOT的socket一旦被某個執行緒處理完畢,則應該立即重置這個socket上的EPOLLONESHOT事件,
以確保下一次可讀時,其EPOLLIN事件能夠觸發。
4. HTTP協議解析怎麼做?資料讀到一半怎麼辦?
首先理解這個問題。HTTP協議並未提供頭部欄位的長度,判斷頭部結束依據是遇到一個空行,該空行只包含一對回車換行符(<CR><LF>)。同時,如果一次讀操作沒有讀入整個HTTP請求
的頭部,我們必須等待使用者繼續寫資料再次讀入(比如讀到 GET /index.html HTT
就結束了,必須維護這個狀態,下一次必須繼續讀‘P’)。
即我們需要判定當前解析的這一行是什麼(請求行?請求頭?訊息體?),還需要判斷解析一行是否結束?
解決上述問題,可以採取有限狀態機。
參考【1】中設計方式,設計主從兩個狀態機(主狀態機解決前半部分問題,從狀態機解決後半部分問題)。
先分析從狀態機,從狀態機用於處理一行資訊(即parse_line函式)。其中包括三個狀態:LINE_OPEN, LINE_OK,LINE_BAD,轉移過程如下所示:
當從狀態機parse_line讀到完整的一行,就可以將改行內容遞交給process_read函式中的主狀態機處理。
主狀態機也有三種狀態表示正在分析請求行(CHECK_STATE_REQUESTINE),正在分析頭部欄位(CHECK_STATE_HEADER),和正在分析內容(CHECK_CONTENT)。
主狀態機使用checkstate變數來記錄當前的狀態。
如果當前的狀態是CHECK_STATE_REQUESTLINE,則表示parse_line函式解析出的行是請求行,於是主狀態機呼叫parse_requestline來分析請求行;
如果當前的狀態是CHECK_STATE_HEADER,則表示parse_line函式解析出來的是頭部欄位,於是主狀態機呼叫parse_header來分析頭部欄位。
如果當前狀態是CHECK_CONTENT,則表示parse_line函式解析出來的是訊息體,我們呼叫parse_content來分析訊息體(實際上實現時候並沒有分析,只是判斷是否完整讀入)
checkstate變數的初始值是CHECK_STATE_REQUESTLINE,呼叫相應的函式(parse_requestline,parse_header)後更新checkstate實現狀態轉移。
與主狀態機有關的核心函式如下:
http_conn::HTTP_CODE http_conn::process_read()//完整的HTTP解析 { LINE_STATUS line_status = LINE_OK; HTTP_CODE ret = NO_REQUEST; char* text = 0; while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK ) ) || ( ( line_status = parse_line() ) == LINE_OK ) ){//滿足條件:正在進行HTTP解析、讀取一個完整行 text = get_line();//從讀緩衝區(HTTP請求資料)獲取一行資料 m_start_line = m_checked_idx;//行的起始位置等於正在每行解析的第一個位元組 printf( "got 1 http line: %s", text ); switch ( m_check_state )//HTTP解析狀態跳轉 { case CHECK_STATE_REQUESTLINE://正在分析請求行 { ret = parse_request_line( text );//分析請求行 if ( ret == BAD_REQUEST ) { return BAD_REQUEST; } break; } case CHECK_STATE_HEADER://正在分析請求頭部 { ret = parse_headers( text );//分析頭部 if ( ret == BAD_REQUEST ) { return BAD_REQUEST; } else if ( ret == GET_REQUEST ) { return do_request();//當獲得一個完整的連線請求則呼叫do_request分析處理資源頁檔案 } break; } case CHECK_STATE_CONTENT:// 解析訊息體 { ret = parse_content( text ); if ( ret == GET_REQUEST ) { return do_request(); } line_status = LINE_OPEN; break; } default: { return INTERNAL_ERROR;//內部錯誤 } } } return NO_REQUEST; }
5. HTTP響應怎麼做?怎麼傳送效率高一些?
首先介紹readv和writev函式。其功能可以簡單概括為對資料進行整合傳輸及傳送,即所謂分散讀,集中寫。
也就是說,writev函式可以把分散儲存在多個緩衝中的資料一併傳送,通過readv函式可以由多個緩衝分別接收。因此適當採用這兩個函式可以減少I/O次數。
例如這裡要做的HTTP響應。其包含一個狀態行,多個頭部欄位,一個空行和文件的內容。前三者可能被web伺服器放置在一塊記憶體中,
而文件的內容則通常被讀入到另外一塊單獨的記憶體中(通過read函式或mmap函式)。這裡可以採用writev函式將他們一併發出。
相關介面如下:
ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt); 其中第二個引數為如下結構體的陣列 struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; 第三個引數為第二個引數的傳遞的陣列的長度。
這裡還可以再學習一下mmap與munmap函式。但是這裡關於mmap與read效率的比較,應該沒有那麼簡單的答案。mmap可以減少系統呼叫和記憶體拷貝,但是其引發的pagefault也是開銷。效率的比較取決於不同系統對於這兩個效率實現的不同,所以這裡就簡單談一談用法。
#include <sys/mman.h> /**addr引數允許使用者使用某個特定的地址作為這段記憶體的起始地址,設定為NULL則自動分配地址。 *length引數指定記憶體段的長度. *prot引數用來設定內*存段的訪問許可權,比如PROT_READ可讀, PROT_WRITE可寫。 *flags控制記憶體段內容被修改後程式的行為。如MAP_PRIVATE指記憶體段為呼叫程序所私有,對該記憶體段的修改不會反映到被對映的檔案中。 */ void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
所以根據不同情況(200,404)填充HTTP的程式如下:
bool http_conn::process_write( HTTP_CODE ret )//填充HTTP應答 { switch ( ret ) { case INTERNAL_ERROR: { add_status_line( 500, error_500_title ); add_headers( strlen( error_500_form ) ); if ( ! add_content( error_500_form ) ) { return false; } break; } case BAD_REQUEST: { add_status_line( 400, error_400_title ); add_headers( strlen( error_400_form ) ); if ( ! add_content( error_400_form ) ) { return false; } break; } case NO_RESOURCE: { add_status_line( 404, error_404_title ); add_headers( strlen( error_404_form ) ); if ( ! add_content( error_404_form ) ) { return false; } break; } case FORBIDDEN_REQUEST: { add_status_line( 403, error_403_title ); add_headers( strlen( error_403_form ) ); if ( ! add_content( error_403_form ) ) { return false; } break; } case FILE_REQUEST://資源頁檔案可用 { add_status_line( 200, ok_200_title ); if ( m_file_stat.st_size != 0 ) { add_headers( m_file_stat.st_size );//m_file_stat資源頁檔案狀態 m_iv[ 0 ].iov_base = m_write_buf;//寫緩衝區 m_iv[ 0 ].iov_len = m_write_idx;//長度 m_iv[ 1 ].iov_base = m_file_address;//資源頁資料記憶體對映後在m_file_address地址 m_iv[ 1 ].iov_len = m_file_stat.st_size;//檔案長度就是該塊記憶體長度 m_iv_count = 2; return true; } else { const char* ok_string = "<html><body></body></html>";//請求頁位空白 add_headers( strlen( ok_string ) ); if ( ! add_content( ok_string ) ) { return false; } } } default: { return false; } } m_iv[ 0 ].iov_base = m_write_buf; m_iv[ 0 ].iov_len = m_write_idx; m_iv_count = 1; return true; }填充HTTP應答
bool http_conn::write()//將資源頁檔案傳送給客戶端 { int temp = 0; int bytes_have_send = 0; int bytes_to_send = m_write_idx; if ( bytes_to_send == 0 ) { modfd( m_epollfd, m_sockfd, EPOLLIN );//EPOLLONESHOT事件每次需要重置事件 init(); return true; } while( 1 )// { temp = writev( m_sockfd, m_iv, m_iv_count );//集中寫,m_sockfd是http連線對應的描述符,m_iv是iovec結構體陣列表示記憶體塊地址,m_iv_count是陣列的長度即多少個記憶體塊將一次集中寫到m_sockfd if ( temp <= -1 )//集中寫失敗 { if( errno == EAGAIN ) { modfd( m_epollfd, m_sockfd, EPOLLOUT );//重置EPOLLONESHOT事件,註冊可寫事件表示若m_sockfd沒有寫失敗則關閉連線 return true; } unmap();//解除記憶體對映 return false; } bytes_to_send -= temp;//待發送資料 bytes_have_send += temp;//已傳送資料 if ( bytes_to_send <= bytes_have_send ) { unmap();//該資源頁已經發送完畢該解除對映 if( m_linger )//若要保持該http連線 { init();//初始化http連線 modfd( m_epollfd, m_sockfd, EPOLLIN ); return true; } else { modfd( m_epollfd, m_sockfd, EPOLLIN ); return false; } } } }將應答傳送給客戶端
6.忽略SIGPIPE
這是一個看似很小,但是如果不注意會直接引發bug的地方。如果往一個讀端關閉的管道或者socket中寫資料,會引發SIGPIPE,程式收到SIGPIPE訊號後預設的操作時終止程序。
這也就是說,如果客戶端意外關閉,那麼伺服器可能也就跟著直接掛了,這顯然不是我們想要的。所以網路程式中服務端一般會忽略SIGPIPE訊號。
7. 程式程式碼
程式中有比較詳細的註釋,雖然主幹在上面問題中分析過了,但是諸如如何解析一行資料之類的操作,還是很煩的...可以直接參考程式碼
1 #ifndef THREADPOOL_H 2 #define THREADPOOL_H 3 4 #include <list> 5 #include <cstdio> 6 #include <exception> 7 #include <pthread.h> 8 #include "locker.h" //簡單封裝了互斥量和訊號量的介面 9 10 //執行緒池類模板引數T是任務型別,T中必須有介面process 11 template< typename T > 12 class threadpool 13 { 14 public: 15 threadpool( int thread_number = 8, int max_requests = 10000 );//執行緒數目和最大連線處理數 16 ~threadpool(); 17 bool append( T* request ); 18 19 private: 20 static void* worker( void* arg );//執行緒工作函式 21 void run(); //啟動執行緒池 22 23 private: 24 int m_thread_number;//執行緒數量 25 int m_max_requests;//最大連線數目 26 pthread_t* m_threads;//執行緒id陣列 27 std::list< T* > m_workqueue;//工作佇列:各執行緒競爭該佇列並處理相應的任務邏輯T 28 locker m_queuelocker;//工作佇列互斥量 29 sem m_queuestat;//訊號量:用於工作佇列 30 bool m_stop;//終止標誌 31 }; 32 33 template< typename T > 34 threadpool< T >::threadpool( int thread_number, int max_requests ) : 35 m_thread_number( thread_number ), m_max_requests( max_requests ), m_stop( false ), m_threads( NULL ) 36 { 37 if( ( thread_number <= 0 ) || ( max_requests <= 0 ) ) 38 { 39 throw std::exception(); 40 } 41 42 m_threads = new pthread_t[ m_thread_number ];//工作執行緒陣列 43 if( ! m_threads ) 44 { 45 throw std::exception(); 46 } 47 48 for ( int i = 0; i < thread_number; ++i )//建立工作執行緒 49 { 50 printf( "create the %dth thread\n", i ); 51 if( pthread_create( m_threads + i, NULL, worker, this ) != 0 ) 52 { 53 delete [] m_threads; 54 throw std::exception(); 55 } 56 if( pthread_detach( m_threads[i] ) ) //分離執行緒使得其它執行緒回收和殺死該執行緒 57 { 58 delete [] m_threads; 59 throw std::exception(); 60 } 61 } 62 } 63 64 template< typename T > 65 threadpool< T >::~threadpool() 66 { 67 delete [] m_threads; 68 m_stop = true; 69 } 70 71 template< typename T > 72 bool threadpool< T >::append( T* request )//向工作佇列新增任務T 73 { 74 m_queuelocker.lock();//對工作佇列操作前加鎖 75 if ( m_workqueue.size() > m_max_requests )//任務佇列滿,不能加進去 76 { 77 m_queuelocker.unlock(); 78 return false; 79 } 80 m_workqueue.push_back( request ); 81 m_queuelocker.unlock(); 82 m_queuestat.post();//訊號量的V操作,多了一個工作任務T使得訊號量+1 83 return true; 84 } 85 86 template< typename T > 87 void* threadpool< T >::worker( void* arg )//工作執行緒函式 88 { 89 threadpool* pool = ( threadpool* )arg; //獲取執行緒池物件,之前建立的時候傳的this 90 pool->run(); //呼叫執行緒池run函式 91 return pool; 92 } 93 94 template< typename T > 95 void threadpool< T >::run() //工作執行緒真正工作邏輯:從任務佇列領取任務T並執行任務T,消費者 96 { 97 while ( ! m_stop ) 98 { 99 m_queuestat.wait();//訊號量P操作,申請訊號量獲取任務T 100 m_queuelocker.lock();//對工作佇列操作前加鎖 101 if ( m_workqueue.empty() ) 102 { 103 m_queuelocker.unlock();//任務佇列空無法消費 104 continue; 105 } 106 T* request = m_workqueue.front();//獲取任務T 107 m_workqueue.pop_front(); 108 m_queuelocker.unlock(); 109 if ( ! request ) 110 { 111 continue; 112 } 113 request->process();//執行任務T的相應邏輯,任務T中必須有process介面 114 } 115 } 116 117 #endifthreadpool.h
#ifndef HTTPCONNECTION_H #define HTTPCONNECTION_H #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/epoll.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <sys/stat.h> #include <string.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <stdarg.h> #include <errno.h> #include "locker.h" class http_conn { public: static const int FILENAME_LEN = 200;//檔名最大長度,檔案是HTTP請求的資源頁檔案 static const int READ_BUFFER_SIZE = 2048;//讀緩衝區,用於讀取HTTP請求 static const int WRITE_BUFFER_SIZE = 1024;//寫緩衝區,用於HTTP回答 enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH };//HTTP請求方法,本程式只定義了GET邏輯 enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };//HTTP請求狀態:正在解析請求行、正在解析頭部、解析中 enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };//HTTP請求結果:未完整的請求(客戶端仍需要提交請求)、完整的請求、錯誤請求...只用了前三個 enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };//HTTP每行解析狀態:改行解析完畢、錯誤的行、正在解析行 public: http_conn(){} ~http_conn(){} public: void init( int sockfd, const sockaddr_in& addr );//初始化新的HTTP連線 void close_conn( bool real_close = true ); void process();//處理客戶請求,這是HTTP請求的入口函式,與線上程池中呼叫!!! bool read();//讀取客戶傳送來的資料(HTTP請求) bool write();//將請求結果返回給客戶端 private: void init();//初始化連線,用於內部呼叫 HTTP_CODE process_read();//解析HTTP請求,內部呼叫parse_系列函式 bool process_write( HTTP_CODE ret );//填充HTTP應答,通常是將客戶請求的資源頁傳送給客戶,內部呼叫add_系列函式 HTTP_CODE parse_request_line( char* text );//解析HTTP請求的請求行 HTTP_CODE parse_headers( char* text );//解析HTTP頭部資料 HTTP_CODE parse_content( char* text );//獲取解析結果 HTTP_CODE do_request();//處理HTTP連線:內部呼叫process_read(),process_write() char* get_line() { return m_read_buf + m_start_line; }//獲取HTTP請求資料中的一行資料 LINE_STATUS parse_line();//解析行內部呼叫parse_request_line和parse_headers //下面的函式被process_write填充HTTP應答 void unmap();//解除記憶體對映,這裡記憶體對映是指將客戶請求的資源頁檔案對映通過mmap對映到記憶體 bool add_response( const char* format, ... ); bool add_content( const char* content ); bool add_status_line( int status, const char* title ); bool add_headers( int content_length ); bool add_content_length( int content_length ); bool add_linger(); bool add_blank_line(); public: static int m_epollfd;//所有socket上的事件都註冊到一個epoll事件表中所以用static static int m_user_count;//使用者數量 private: int m_sockfd;//HTTP連線對應的客戶在服務端的描述符m_sockfd和地址m_address sockaddr_in m_address; char m_read_buf[ READ_BUFFER_SIZE ];//讀緩衝區,讀取HTTP請求 int m_read_idx;//已讀入的客戶資料最後一個位元組的下一個位置,即未讀資料的第一個位置 int m_checked_idx;//當前已經解析的位元組(HTTP請求需要逐個解析) int m_start_line;//當前解析行的起始位置 char m_write_buf[ WRITE_BUFFER_SIZE ];//寫緩衝區 int m_write_idx;//寫緩衝區待發送的資料 CHECK_STATE m_check_state;//HTTP解析的狀態:請求行解析、頭部解析 METHOD m_method;//HTTP請求方法,只實現了GET char m_real_file[ FILENAME_LEN ];//HTTP請求的資源頁對應的檔名稱,和服務端的路徑拼接就形成了資源頁的路徑 char* m_url;//請求的具體資源頁名稱,如:www.baidu.com/index.html char* m_version;//HTTP協議版本號,一般是:HTTP/1.1 char* m_host;//主機名,客戶端要在HTTP請求中的目的主機名 int m_content_length;//HTTP訊息體的長度,簡單的GET請求這個為空 bool m_linger;//HTTP請求是否保持連線 char* m_file_address;//資源頁檔案記憶體對映後的地址 struct stat m_file_stat;//資源頁檔案的狀態,stat檔案結構體 struct iovec m_iv[2];//呼叫writev集中寫函式需要m_iv_count表示被寫記憶體塊的數量,iovec結構體存放了一段記憶體的起始位置和長度, int m_iv_count;//m_iv_count是指iovec結構體陣列的長度即多少個記憶體塊 }; #endifhttp_conn.h
1 #include "http_conn.h" 2 3 const char* ok_200_title = "OK"; 4 const char* error_400_title = "Bad Request"; 5 const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n"; 6 const char* error_403_title = "Forbidden"; 7 const char* error_403_form = "You do not have permission to get file from this server.\n"; 8 const char* error_404_title = "Not Found"; 9 const char* error_404_form = "The requested file was not found on this server.\n"; 10 const char* error_500_title = "Internal Error"; 11 const char* error_500_form = "There was an unusual problem serving the requested file.\n"; 12 const char* doc_root = "/var/www/html";//服務端資源頁的路徑,將其和HTTP請求中解析的m_url拼接形成資源頁的位置 13 14 int setnonblocking( int fd )//將fd設定為非阻塞 15 { 16 int old_option = fcntl( fd, F_GETFL ); 17 int new_option = old_option | O_NONBLOCK; 18 fcntl( fd, F_SETFL, new_option ); 19 return old_option; 20 } 21 22 void addfd( int epollfd, int fd, bool one_shot )//將fd新增到事件表epollfd 23 { 24 epoll_event event; 25 event.data.fd = fd; 26 event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; 27 if( one_shot ) 28 { 29 event.events |= EPOLLONESHOT; 30 } 31 epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); 32 setnonblocking( fd ); 33 } 34 35 void removefd( int epollfd, int fd )//將fd從事件表epollfd中移除 36 { 37 epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 ); 38 close( fd ); 39 } 40 41 void modfd( int epollfd, int fd, int ev )//EPOLLONESHOT需要重置事件後事件才能進行下次偵聽 42 { 43 epoll_event event; 44 event.data.fd = fd; 45 event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; 46 epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event ); 47 } 48 49 int http_conn::m_user_count = 0;//連線數 50 int http_conn::m_epollfd = -1;//事件表,注意是static故所有http_con類物件共享一個事件表 51 52 void http_conn::close_conn( bool real_close )//關閉連線,從事件表中移除描述符 53 if( real_close && ( m_sockfd != -1 ) ) 54 { 55 //modfd( m_epollfd, m_sockfd, EPOLLIN ); 56 removefd( m_epollfd, m_sockfd ); 57 m_sockfd = -1; 58 m_user_count--; 59 } 60 } 61 62相關推薦
Tinywebserver:一個簡易的web伺服器
這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。 首先回顧程式中的核心內容和主要問題,最後給出相關程式碼。 0. 功能和I/O模型 實現簡易的HTTP服務端,現僅支援GET方法,通過瀏覽器訪問可以返回相應內容。 I/O模型採用Reactor(
Tinywebserver-一個簡易的web伺服器
這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。 程式程式碼是基於《Linux高效能伺服器程式設計》一書編寫的。 首先回顧程式中的核心內容和主要問題,最後給出相關程式碼。 0. 功能和I/O模型 實現簡易的H
TinyWS —— 一個C++寫的簡易WEB伺服器(一)
寫在前面 每個碼農可能都會偶爾有自己做一個常用軟體的想法,比如作業系統,編譯器,郵件伺服器/客戶端,文字編輯器等等。這裡面有些很難,比如作業系統,做一個最簡單的也要付出很大的努力,可是大部分常用工具都是可以比較容易的做一個簡易版本(當然也是隻能玩玩而已)。於是我做了一個非常簡陋的WEB伺服器 —— Tiny
一個簡易git伺服器的搭建
檢視本機ssh公鑰,生成公鑰 檢視ssh公鑰方法: 1. 開啟git bash視窗 2. 進入.ssh目錄: cd ~/.ssh 3. 找到id_rsa.pub檔案: ls 4. 檢視公鑰:cat id_rsa.pub 或者 vim id_rsa.pub 何為公鑰: 1. 很多
使用Socket模擬簡易Web伺服器
Web伺服器大家應該都很熟悉了,web伺服器的原理可以看這裡。講的挺詳細的。 本篇主要是模擬一下簡單的互動,通過socket通道,瀏覽器傳送請求,伺服器返回資源。 簡單圖例如上,然後來看程式碼吧 1、首先啟動服務監聽埠,使用執行緒池來完成互動 package
執行一個本地web伺服器
本文記錄安裝一個Python伺服器的流程。 首先安裝python,地址python.org。確保選中了“將Python 3.xxx新增到PATH”複選框。 進入專案目錄,在終端中輸入python -m http.server以啟動伺服器。預設情況下,這將在本地Web伺服器上的埠8000上執行目
go搭建一個簡單web伺服器
Go語言裡面提供了一個完善的net/http包,通過http包可以很 方便的就搭建起來一個可以執行的web服務。同時使用這個包能很簡單地對web的路由,靜態檔案,模版,cookie等數 據進行設定和操
用原生Node實現一個靜態web伺服器
之前一直用過Apache nginx等靜態web伺服器。 但強大的node.js本身就能作為獨立的web伺服器,不依賴與Apache nginx 下面我們看看怎麼用Node去寫一個靜態伺服器吧 首先,先來看看我的專案結構吧
從零開始搭建一個簡易的伺服器(二)
超級大坑 第一篇部落格到現在拖坑有半年了(不過估計也沒人記得我),原本的打算是既然要寫伺服器,那自然要設計一門語言,類似於php這樣的工作於伺服器後端負責後端渲染,然後到目前為止的時間基本都花在寫編譯器上了囧,編譯器的專案在這裡。如果真的等編譯器全部寫
一個簡單web伺服器的java實現
一個簡單的web伺服器在不考慮其效能及健壯性的情況下,通常只需實現的功能包括伺服器的啟動,它用於監聽某一個埠,接收客戶端發來的請求,並將響應結果返回給客戶端。本文將介紹一個簡單web伺服器的實現原理,它本身只能處理某個目錄下的靜態資原始檔(文字、圖片等)。採用java
從零開始搭建一個簡易的伺服器(一)
前言 其實大家大可不必被伺服器這三個字嚇到,一個入門級後端框架,所需的僅僅是HTTP相關的知識與應用這些知識的程式設計工具。據本人的經驗,絕大多數人擁有搭建後端所涉及到的基礎理論知識,但是缺乏能將之應用出去的工具,而本文即是交給讀者這樣一個工具,並能夠運用之來
手寫一個 Java web 伺服器
關於 web 伺服器 Java 中有很多優秀的 web 伺服器(容器),如 Tomcat、Weblogic、JBOSS 等等。我們都知道 web 伺服器是用於接受外部請求並給予迴應(響應)的一個玩意兒。所以今天造一個可以接受請求並響應請求的輪子,大致思路是使用
Linux下簡易web伺服器實現
今天突然對http的web伺服器感興趣了,就研究了一下,發現linux下的web伺服器就是一個socket程式設計的伺服器端,而我們用的ie,chrome等瀏覽器就是客戶端,只不過傳送和接收資料按照http網頁格式,就相當於對資料進行了封裝,相當於加上了檔案頭和檔案
用HTTP核心模組配置一個靜態web伺服器
1. 虛擬主機與請求的分發2. 檔案路徑的定義3. 記憶體幾磁碟資源的分配4. 網路連結的設定5. MIME型別的設定6. 對客戶端請求的限制7. 檔案操作的優化8. 對客戶端請求的特殊處理9. ngx
Python搭建簡易web伺服器,超好用~
有時需要手機除錯一些web頁面,於是~找到了這個超好用的方法 首先,你要有python 然後,命令列進入web資料夾根目錄,這裡假設是index.html所在目錄 輸入python命令: python -m SimpleHTTPServer 8080 8080是埠號,可以任
【node.js】使用node.js搭建一個本地web伺服器
操作步驟 1、到node官網(https://nodejs.org/en/)下載node.js安裝檔案,X64代表執行環境為windows64位 2、雙擊安裝檔案安裝node.js 3、等待安裝 4、測試是否安裝成功,按【windows+R】,執行cmd 5、輸入n
nginx教程第六篇:用HTTP核心模組配置一個靜態Web伺服器(二)
網路連線的設定 下面介紹網路連線的設定配置項: 1. 讀取HTTP頭部的超時時間 語法: client_header_timeout time( 預設單位: 秒) ; 預設: client_header_timeout 60; 配置塊: http、 serve
筆記:學習go語言的網路基礎庫,並嘗試搭一個簡易Web框架
![走你~!](https://images.cnblogs.com/cnblogs_com/tanshaoshenghao/1910827/o_210113093044go%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80%E5%BA%93%E6%B0%B4%E5%8D%B0%E7%8
NodeJs實現一個簡易的WEB上傳下載伺服器
專案上的需求是叢集均可生成PDF檔案或是訪問PDF檔案,但是沒有檔案伺服器,故做一個簡易的檔案伺服器。解決方案:叢集內的機器(客戶端)生成PDF檔案之後將PDF檔案推給檔案伺服器,我們暫且稱它為服務端;如果某個客戶端需要訪問到這個PDF檔案,則去服務端獲取(因為可能其他客戶端
ASP.NET一個簡易的WebServer,用控制臺程序模擬IIS 托起web服務
public 程序 控制臺 ProcessRequestHandler( page, query, TextWriter writer); WebServer : MarshalByRefObject, IRegisteredObject { Pro