1. 程式人生 > >自己開發簡單web伺服器一(C++開源庫websocketpp實現)

自己開發簡單web伺服器一(C++開源庫websocketpp實現)

簡要

Web伺服器主要處理的是HTTP請求(這裡忽略HTTPS),HTTP協議建立在TCP上。如果自己實現,無非就是網路程式設計(socket接受、傳送),資料解析(HTTP欄位解析),返回HTTP協議字串給客戶端等。說起來簡單,要做到跨平臺和高效,不得不介紹幾個有名的開源庫。

Websocketpp,開源跨平臺C++ web庫,網路請求使用boost::asio實現(Windows上是IOCP完成埠),字串請求構造都在hpp中實現,沒有一個cpp檔案,全部內聯保證了程式碼執行的效率(IOCP的處理效率也是槓槓的,不知道百度去)。

Web伺服器主要處理客戶端的http請求(GET\POST),這裡主要介紹頁面請求、檔案請求等基本的功能。

基本配置

設定工作執行緒數目,理論上執行緒數目越多處理請求越高效,實際中還要考慮執行緒間切換的問題,所以通常是執行緒數乘以2(用過IOCP完成埠的都知道這個)

size_t num_threads = 4;//使用4個執行緒來處理web請求
監聽80埠,處理Web請求
testee_server.listen(port);

設定回撥函式,所有的客戶端請求都會進入這個回撥函式中,回撥函式在工作執行緒中執行

testee_server.set_http_handler(bind(&on_http, &testee_server, ::_1));

回撥函式處理,一定要加上try.catch,對於Websocketpp中跑出的異常進行捕捉,避免程式崩潰退出。
void on_http(server* s, websocketpp::connection_hdl hdl)
{
	try
	{
		server::connection_ptr con = s->get_con_from_hdl(hdl);
		websocketpp::http::parser::request rt = con->get_request();
		const string& strUri = rt.get_uri();
		const string& strMethod = rt.get_method();
		const string& strBody = rt.get_body();	//只針對post時有資料
		const string& strHost = rt.get_header("host");
		const string& strVersion = rt.get_version();
		std::cout<<"接收到一個"<<strMethod.c_str()<<"請求:"<<strUri.c_str()<<"  執行緒ID="<<::GetCurrentThreadId()<<"  host = "<<strHost<<std::endl;
		if (strMethod.compare("POST") == 0)
		{//對於post提交的,直接返回跳轉
			//websocketpp::http::parser::request r;
			con->set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::found));
			con->append_header("location", "http://blog.csdn.net/mfcing");
		}
		else if (strMethod.compare("GET") == 0)
		{
			if (strUri.compare("/") == 0)
			{//請求主頁
				con->set_body("Hello WebsocketPP!");
				con->set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::ok));
			}
			else if (strUri.compare("/favicon.ico") == 0)
			{//請求網站圖示,讀取圖示檔案,直接返回二進位制資料
				static const string strIcoPath = g_strRunPath + "server\\Update.ico";
				string strBuffer;
				if (ReadFileContent(strIcoPath.c_str(), "rb", strBuffer))
				{
					con->set_body(strBuffer);
				}
				con->set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::ok));//HTTP返回碼 OK
			}
			else if (strUri.compare("/test.html") == 0)
			{//請求一個頁面,讀取這個頁面檔案內容然後返回給瀏覽器
				static const string strHtmlPath = g_strRunPath + "server\\test.htm";
				string strBuffer;
				int code = websocketpp::http::status_code::ok;
				if (ReadFileContent(strHtmlPath.c_str(), "r", strBuffer))
				{
					con->set_body(strBuffer);
				}
				else
				{//頁面不存在,返回404
					code = websocketpp::http::status_code::not_found;
					con->set_body("<html><body><div style=\"color:#F000FF;font:14px;font-family: 'Microsoft YaHei';\">溫馨提示:</div><div style=\"padding-left: 80px;color:#333333;font:14px;font-family: 'Microsoft YaHei';\">頁面被外星人帶走啦!</div></body></html>");
				}
				con->set_status(websocketpp::http::status_code::value(code));//HTTP返回碼
			}
			else if (strUri.compare("/server/test.jpg") == 0)
			{//上面的頁面的HTML中配置了一張圖片,因此瀏覽器回來伺服器請求這張圖
				static const string strImgPath = g_strRunPath + "server\\test.jpg";
				string strBuffer;
				int code = websocketpp::http::status_code::ok;
				if (ReadFileContent(strImgPath.c_str(), "rb", strBuffer))
				{
					con->set_body(strBuffer);
				}
				else
				{//頁面不存在,返回404
					code = websocketpp::http::status_code::not_found;
				}
				con->set_status(websocketpp::http::status_code::value(code));//HTTP返回碼
			}
			else
			{//其他未定義的頁面,返回跳轉
				con->set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::found));
				con->append_header("location", "http://blog.csdn.net/mfcing");
			}
		}
	}
	catch (websocketpp::exception const & e) 
	{
		std::cout << "exception: " << e.what() << std::endl;
	}
	catch(std::exception &e)
	{
		std::cout << "exception: " << e.what() << std::endl;
	}
	catch(...)
	{

	}
}

Web請求處理

首先是通過strMethod獲取請求方式,POST請求主要用於提交資料,這裡不作特別處理,僅僅是讓瀏覽器跳轉到我的部落格首頁中。HTTP協議得了解一點,返回的code=302時是跳轉(enum value found = 302),跳轉時需要帶上location欄位,指定你要跳轉到哪去。
con->set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::found));
			con->append_header("location", "http://blog.csdn.net/mfcing");

瀏覽器請求都是GET,我們重點關注的是這個。

請求主頁

伺服器開啟後,在80埠上監聽客戶端的http請求。我們在瀏覽器上輸入:127.0.0.1回車時,瀏覽器首先請求主頁資訊,然後請求頁面的圖示。

strUri是獲取請求頁面的內容

const string& strUri = rt.get_uri();
請求主頁時,即strUri="/";請求圖示時:strUri="/",/favicon.ico。

請求主頁時,僅僅返回一段文字:Hello WebsocketPP!,瀏覽器會顯示這段文字。請求圖示時,讀取本地的一個ico檔案,返回二進位制字串,瀏覽器將會在標籤上顯示出這個圖示。

請求html頁面

請求頁面http://127.0.0.1/test.html時,讀取本地配置的一個html檔案內容並返回,html檔案內容如下

<html>
<head>
</head>
<body>
    <p style="align-items:center;">
    <label style="font:20px;font-family:'Microsoft YaHei';color:gold;align-self:center;text-align:center">This is the test page</label>
        </p>
    <p>
        <img src="http://127.0.0.1/server/test.jpg"/>
        </p>
    <div style="padding-left: 80px; font: 14px; font-family: 'Microsoft YaHei';font-weight:bold; color: #0094ff">打賞下
        </div>
    <div>
        <img src="https://img-blog.csdn.net/20161102162003256?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="200" height="200" />
        </div>
</body>
</html>

請求圖片資源

裡面配置了一張本地圖片
<span style="font-size:14px;"><img src="http://127.0.0.1/server/test.jpg"/></span>

瀏覽器解析這個html後會接著請求伺服器的這張圖,也就是收到/server/test.jpg的請求,我們讀取本地圖片,返回二進位制字串給瀏覽器後,瀏覽器將會顯示出這張圖。

客戶端請求的頁面不存在時,我們可以返回404告知not_found = 404。

瀏覽器解析HTML展示圖片後


注意事項

注意一點:對於所有的請求,一定要設定HTTP頭,告知瀏覽器請求的結果(不存在:404,正常:200,跳轉302……),否則瀏覽器會認為伺服器不鳥他,顯示開啟頁面失敗。

set_status(websocketpp::http::status_code::value(websocketpp::http::status_code::ok));

demo程式下載