您好,登錄后才能下訂單哦!
這篇文章主要用實例解析C++如何編寫高性能服務(wù)器,內(nèi)容簡而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會有收獲的,下面讓小編帶大家一起來看看吧。
我將展示如何使用現(xiàn)代C++編寫一個Echo服務(wù)器,相當(dāng)于分布式系統(tǒng)開發(fā)中的“Hello World”。這個服務(wù)器會將接收的消息直接返回。我們同時需要一個可以向我們的服務(wù)器發(fā)動消息的客戶端,在這里可以發(fā)現(xiàn)客戶端的源碼。
Wangle是一個用來搭建事件驅(qū)動的現(xiàn)代異步C++服務(wù)的C/S應(yīng)用框架。Wangle最基本的抽象概念就是Pipeline(管線)。能夠理解這種抽象,將會很容易寫出各種復(fù)雜的現(xiàn)代C++服務(wù),另一個重要的概念是Service(服務(wù)),其可以看作一種更高級的Pipeline,不過超出了本文我們關(guān)注的范疇。
PipeLine
pipeline 是 Wangle 中最重要也是最強大的抽象,可以讓用戶在定制 request 和 response 的實現(xiàn)時擁有很大的自由。一個pipeline就是一系列request/response控制程序的嵌套。我試圖尋找一個真實世界中pipeline的類比,唯一我能想到的就是現(xiàn)實世界工廠中的生產(chǎn)線。一條生產(chǎn)線工作在一種順序模式下,所有的工人取得一個物體,并且只添加一種修改,再將其發(fā)送給上游的工人直到整個產(chǎn)品制造完成。這可能不是一個特別好的比喻,因為流水線上產(chǎn)品的流動是單向的,而一個pipeline能控制反方向的數(shù)據(jù)流動--就好像將成品分解成原材料。
一個Wangle handler可以同時掌控上游和下游的兩個方向的數(shù)據(jù)流動。當(dāng)你把所有的handler連接在一起,就可以用一種靈活的方式將原始數(shù)據(jù)組裝為想要的數(shù)據(jù)類型或者將已有的數(shù)據(jù)拆分。
在我們的服務(wù)器的pipeline中大致將會有下面幾種handler:
1.Handler 1 (下文的上游下游是指對一同個handler而言,根據(jù)其在pipeline中的位置不同,輸入輸出相反) 上游:將從socket中接收的二進制數(shù)據(jù)流寫入一個零拷貝(zero-copy,指省略了Applicaion context和Kernel context之間的上下文切換,避免了CPU對Buffer的冗余拷貝,直接在Kernel級別進行數(shù)據(jù)傳輸?shù)募夹g(shù),詳情請參閱維基百科)的字節(jié)緩存中,發(fā)送給handler2
下游:接收一個零拷貝的字節(jié)緩存,將其內(nèi)容寫入socket中
2.Handler2 上游:接收handler1的緩存對象,解碼為一個string對象傳遞給handler3 下游:接收handler3的string對象,將其轉(zhuǎn)碼為一個零拷貝的字節(jié)緩存,發(fā)送給handler1
3.Handler3 上游:接收handler2中的string對象,再向下發(fā)送至pipeline等待寫回客戶端。string會發(fā)回handler2 下游:接收上游的string對象,傳遞給handler2
需要注意的一點是,每一個handler應(yīng)當(dāng)只做一件事并且只有一件,如果你有一個handler里做了多項任務(wù),比如從二進制流離直接解碼出string,那么你需要學(xué)會將它拆分。這對提升代碼的可維護性和擴展性非常重要。
另外,沒錯,handler不是線程安全的,所以不要輕易的在其中使用任何沒有經(jīng)過mutex,atomic lock保護的數(shù)據(jù),如果你確實需要一個線程安全的環(huán)境,F(xiàn)olly提供了一種免于加鎖的數(shù)據(jù)結(jié)構(gòu), Folly依賴于Wangle,你可以很容易的在項目中引入并使用它。
如果你還不是很明白所有的步驟,不用著急,在看到下面的具體實現(xiàn)時你會更加清楚。
Echo Server
下面我會展示服務(wù)器的具體實現(xiàn)。我假定您已經(jīng)安裝好Wangle。需要注意的是截至目前Wangle還不能在Mac OS上安裝,我建議您可以安裝虛擬機,使用Ubuntu來安裝Wangle。
這就是echo handler:接收一個string,打印到stdout中,再發(fā)送回pipeline。要注意write語句中的定界符不可以省略,因為pipeline會按照字節(jié)解碼。
// the main logic of our echo server; receives a string and writes it straight // back class EchoHandler : public HandlerAdapter { public: virtual void read(Context* ctx, std::string msg) override { std::cout << "handling " << msg << std::endl; write(ctx, msg + "rn"); } };
Echohandler其實是我們pipeline的最后一個handler,現(xiàn)在我們需要創(chuàng)建一個PipelineFactory來控制所有的request和response。
// where we define the chain of handlers for each messeage received class EchoPipelineFactory : public PipelineFactory { public: EchoPipeline::Ptr newPipeline(std::shared_ptr sock) { auto pipeline = EchoPipeline::create(); pipeline->addBack(AsyncSocketHandler(sock)); pipeline->addBack(LineBasedFrameDecoder(8192)); pipeline->addBack(StringCodec()); pipeline->addBack(EchoHandler()); pipeline->finalize(); return pipeline; } };
pipeline中每一個handler的插入順序都需要嚴格注意,因為它們是按照先后排序的,此處我們有4個handler
1.AsyncSocketHandler: 上游:讀取scoket中的二進制流轉(zhuǎn)換成零拷貝字節(jié)緩存 下游:將字節(jié)緩存內(nèi)容寫入底層socket
2. LineBasedFrameDecoder: 上游:接收字節(jié)緩存,按行分割數(shù)據(jù) 下游:將字節(jié)緩存發(fā)送給AsyncSocketHandler
3. StringCodec: 上游:接收字節(jié)緩存,解碼為std:string傳遞給EchoHandler 下游:接收std:string, 編碼為字節(jié)緩存,傳遞給LineBasedFrameDecoder
4. EchoHandler: 上游:接收std:string對象,將其寫入pipeline-將消息返回給Echohandler。 下游:接收一個std:string對象,轉(zhuǎn)發(fā)給StringCodec Handler。 現(xiàn)在我們所需要做的就是將pipeline factory關(guān)聯(lián)到ServerBootstrap,綁定一個端口,這樣我們已經(jīng)完成了 基本上所有的工作。
#include <gflags/gflags.h> #include <wangle/bootstrap/ServerBootstrap.h> #include <wangle/channel/AsyncSocketHandler.h> #include <wangle/codec/LineBasedFrameDecoder.h> #include <wangle/codec/StringCodec.h> using namespace folly; using namespace wangle; DEFINE_int32(port, 8080, "echo server port"); typedef Pipeline<IOBufQueue&, std::string> EchoPipeline; // the main logic of our echo server; receives a string and writes it straight // back class EchoHandler : public HandlerAdapter<std::string> { public: virtual void read(Context* ctx, std::string msg) override { std::cout << "handling " << msg << std::endl; write(ctx, msg + "\r\n"); } }; // where we define the chain of handlers for each messeage received class EchoPipelineFactory : public PipelineFactory<EchoPipeline> { public: EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) { auto pipeline = EchoPipeline::create(); pipeline->addBack(AsyncSocketHandler(sock)); pipeline->addBack(LineBasedFrameDecoder(8192)); pipeline->addBack(StringCodec()); pipeline->addBack(EchoHandler()); pipeline->finalize(); return pipeline; } }; int main(int argc, char** argv) { google::ParseCommandLineFlags(&argc, &argv, true); ServerBootstrap<EchoPipeline> server; server.childPipeline(std::make_shared<EchoPipelineFactory>()); server.bind(FLAGS_port); server.waitForStop(); return 0; }
至此我們一共只寫了48行代碼就完成了一個高性能的異步C++服務(wù)器。
Echo Client
echo客戶端的實現(xiàn)與我們的服務(wù)端非常類似:
// the handler for receiving messages back from the server class EchoHandler : public HandlerAdapter { public: virtual void read(Context* ctx, std::string msg) override { std::cout << "received back: " << msg; } virtual void readException(Context* ctx, exception_wrapper e) override { std::cout << exceptionStr(e) << std::endl; close(ctx); } virtual void readEOF(Context* ctx) override { std::cout << "EOF received :(" << std::endl; close(ctx); } };
注意我們重載了readException和readEOF兩個方法,還有其他一些方法可以被重載。如果你需要控制某個特別的事件,只需要重載對應(yīng)的虛函數(shù)即可。
這是客戶端的pipeline factory的實現(xiàn),與我們的服務(wù)端結(jié)構(gòu)基本一致,只有EventBaseHandler這個handler在服務(wù)端代碼中不曾出現(xiàn),它可以確保我們可以從任意一個線程寫入數(shù)據(jù)。
// the handler for receiving messages back from the server class EchoHandler : public HandlerAdapter { public: virtual void read(Context* ctx, std::string msg) override { std::cout << "received back: " << msg; } virtual void readException(Context* ctx, exception_wrapper e) override { std::cout << exceptionStr(e) << std::endl; close(ctx); } virtual void readEOF(Context* ctx) override { std::cout << "EOF received :(" << std::endl; close(ctx); } };
客戶端所有的代碼如下圖所示
#include <gflags/gflags.h> #include #include <wangle/bootstrap/ClientBootstrap.h> #include <wangle/channel/AsyncSocketHandler.h> #include <wangle/channel/EventBaseHandler.h> #include <wangle/codec/LineBasedFrameDecoder.h> #include <wangle/codec/StringCodec.h> using namespace folly; using namespace wangle; DEFINE_int32(port, 8080, "echo server port"); DEFINE_string(host, "::1", "echo server address"); typedef Pipeline<folly::IOBufQueue&, std::string> EchoPipeline; // the handler for receiving messages back from the server class EchoHandler : public HandlerAdapter { public: virtual void read(Context* ctx, std::string msg) override { std::cout << "received back: " << msg; } virtual void readException(Context* ctx, exception_wrapper e) override { std::cout << exceptionStr(e) << std::endl; close(ctx); } virtual void readEOF(Context* ctx) override { std::cout << "EOF received :(" << std::endl; close(ctx); } }; // chains the handlers together to define the response pipeline class EchoPipelineFactory : public PipelineFactory { public: EchoPipeline::Ptr newPipeline(std::shared_ptr sock) { auto pipeline = EchoPipeline::create(); pipeline->addBack(AsyncSocketHandler(sock)); pipeline->addBack( EventBaseHandler()); // ensure we can write from any thread pipeline->addBack(LineBasedFrameDecoder(8192, false)); pipeline->addBack(StringCodec()); pipeline->addBack(EchoHandler()); pipeline->finalize(); return pipeline; } }; int main(int argc, char** argv) { google::ParseCommandLineFlags(&argc, &argv, true); ClientBootstrap client; client.group(std::make_shared(1)); client.pipelineFactory(std::make_shared()); auto pipeline = client.connect(SocketAddress(FLAGS_host, FLAGS_port)).get(); try { while (true) { std::string line; std::getline(std::cin, line); if (line == "") { break; } pipeline->write(line + "rn").get(); if (line == "bye") { pipeline->close(); break; } } } catch (const std::exception& e) { std::cout << exceptionStr(e) << std::endl; } return 0; }
程序用一個While循環(huán)不斷監(jiān)測用戶的輸入,并且依靠調(diào)用.get() 來同步等待一直到請求被響應(yīng)。
以上就是關(guān)于用實例解析C++如何編寫高性能服務(wù)器的內(nèi)容,如果你們有學(xué)習(xí)到知識或者技能,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。