每一個碼農可能都會偶爾有本身作一個經常使用軟件的想法,好比操做系統,編譯器,郵件服務器/客戶端,文字編輯器等等。這裏面有些很難,好比操做系統,作一個最簡單的也要付出很大的努力,但是大部分經常使用工具都是能夠比較容易的作一個簡易版本(固然也是隻能玩玩而已)。因而我作了一個很是簡陋的WEB服務器 —— TinyWS。這裏主要是記錄下本身整個過程當中的一些想法。html
TinyWS是用C++」從頭開始「作的,也就是說,除了C/C++的標準庫和操做系統的系統調用,並無使用第三方庫。我並不喜歡C++(甚至有些厭惡其紛繁複雜的語法規則),正因如今後,雖然其是個人工做語言,但我也學的很粗糙。此次使用它主要也是爲了本身能學習一下吧,畢竟拿了公司的錢,hee。linux
若是使用Python等其餘」高級「的語言,會更快的實現,事實上幾乎全部的WEB框架都會自帶一個(固然都比TinyWS強大的多)。但若是使用這些語言,恐怕也很難真正的」從頭開始「。git
目前,代碼已經託管在 https://git.oschina.net/augustus/TinyWS.gitubuntu
能夠用git clone下來。因爲我可能會偶爾作一些修改,不能保證git 庫上的代碼與blog裏的徹底一致(實際上也不可能把全部的代碼都貼在這裏)。另外,TinyWS是基於linux寫的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git庫),故在Windows上可能沒法正常編譯(主要是系統調用 部分可能會不一樣)。瀏覽器
WEB的原理很簡單,你們都懂,我就簡單寫幾句,不然直接貼代碼可能比較突兀。服務器
WEB實際上也是一個客戶端/服務器的程序,而它們之間基本使用HTTP/HTTPS/FTP等協議通訊。協議不過是數據傳輸的一種方式,而對於傳輸的內容來講,WEB基本是html文檔,固然也能夠傳其餘的任何文件,不過做爲一個玩具,TinyWS只支持HTTP協議。網絡
WEB的客戶端就是瀏覽器,實質是一個html的解釋器,而咱們要作的,就是提供一個服務器,讓瀏覽器能夠訪問到HTML文檔。瀏覽器是經過uri來訪問服務器端的資源,好比一個保存在服務器上的index.html文檔,在瀏覽器端,可使用http://serverip:port/index.html 這樣的方式就能夠取回這個文檔並解析。咱們要解決的問題其實就是瀏覽器發出這個請求以後,給予正確的迴應。框架
咱們知道主機之間的網絡通訊實際上最終都是經過socket傳數據。而socket的本質是操做系統內核實現一個映射,使得用戶程序使用網絡就像使用本地文件同樣。即便用socket打開一個端口後,會返回一個文件描述符,以後全部的操做都和讀寫一個本地文件徹底相同了。瞭解了這個,實際上咱們就已經解決了一半的問題。eclipse
另外一半的問題就是咱們如何實現HTTP協議。好在HTTP是一個比較簡單的協議,其核心是一個」請求與應答「的過程,」請求「是一些稱爲」方法「的操做過程,實際上就是告訴服務器,要請求服務器返回某資源(uri)或者對資源進行某些操做。經常使用的方法就是GET和POST,目前TinyWS只實現了GET方法,其餘的方法可能後面也會作一下吧。socket
對於socket和HTTP,有許多專題能夠查,這裏就不羅嗦了。
TinyWS核心的業務實際就是接收HTTP請求,並給予正確的應答,因此這裏先從上層業務講起吧。TinyWS運行以後,首先會打開socket並監聽某端口,以後就會運行RequestManager的run方法,不斷的等待HTTP請求到來。請求到來以後,會解析內容,分析出客戶端的請求方法和uri,從而交給相關的」方法「去處理。
// RequestManager.h class RequestManager { public: RequestManager(int connfd); void run(); private: Request* getRequestHandle(); private: int fileDescriptor; Request* request; };
其中Request 就是具體方法的基類,其子類能夠是GET,POST等等。
// RequestManager.cpp namespace { class Parser { public: Parser(int connfd) { parseRequestHeaders(connfd); } const std::string getMethodName() { return method; } const std::string getUri() { return uri; } private: void parseRequestHeaders(int fd) { IoReader reader(fd); std::vector<std::string> header; reader.getLineSplitedByBlank(header); method = header[0]; uri = header[1]; version = header[2]; } private: std::string method; std::string uri; std::string version; }; } RequestManager::RequestManager(int connfd) : fileDescriptor(connfd), request(0) { } void RequestManager::run() { if(getRequestHandle()) request->execute(); } Request* RequestManager::getRequestHandle() { Parser parser(fileDescriptor); return request = RequestCreater::getRequestHandler(parser.getMethodName(), fileDescriptor, parser.getUri()); }
在CPP文件中,首先要解析客戶端的請求數據,分析出method,uri,version(協議版本,這裏實際上並無用到)。這個工做有Parser類完成,因爲只有這一處使用,封在了匿名namespace中。解析中使用了IoReader類,它負責從socket讀入數據,封裝了底層的IO操做,這個後面再說。
回到正題。RequestManager的實現中,其實使用了一個工廠類( RequestCreater),根據解析出的method,創造不一樣的方法實例,這裏雖然只支持GET,但仍然使用了工廠,是考慮到後面還會實現POST等其餘方法,應該也不算過分設計吧,hee。
// Request.h
class Request { public: void init(int fd, std::string uri); void execute(); virtual ~Request() { } protected: int getFileDescriptor() const; const std::string& getUri() const; private: virtual void doExecute() = 0; private: int fileDescriptor; std::string uri; };
Request是一個抽象類,每個子類都須要實現doExecute方法才能實例化。這裏也使用了一個簡單的」模板方法「,讓整個繼承體系對外接口統一。
// Request.cpp void Request::init(int fd, std::string uri) { this->fileDescriptor = fd; this->uri = uri; } void Request::execute() { doExecute(); } int Request::getFileDescriptor() const { return fileDescriptor; } const std::string& Request::getUri() const { return uri; }
真正幹活的是Request的子類GetRequest。不過不早了,今天先到這裏,下次再說吧。