C++编写高性能服务器实例教程

我将展示如何使用现代C++编写一个Echo服务器,相当于分布式系统开发中的“Hello World”。这个服务器会将接收的消息直接返回。我们同时需要一个可以向我们的服务器发动消息的客户端,在这里可以发现客户端的源码。

Wangle是一个用来搭建事件驱动的现代异步C++服务的C/S应用框架。Wangle最基本的抽象概念就是Pipeline(管线)。能够理解这种抽象,将会很容易写出各种复杂的现代C++服务,另一个重要的概念是Service(服务),其可以看作一种更高级的Pipeline,不过超出了本文我们关注的范畴。

PipeLine

pipeline 是 Wangle 中最重要也是最强大的抽象,可以让用户在定制 request 和 response 的实现时拥有很大的自由。一个pipeline就是一系列request/response控制程序的嵌套。我试图寻找一个真实世界中pipeline的类比,唯一我能想到的就是现实世界工厂中的生产线。一条生产线工作在一种顺序模式下,所有的工人取得一个物体,并且只添加一种修改,再将其发送给上游的工人直到整个产品制造完成。这可能不是一个特别好的比喻,因为流水线上产品的流动是单向的,而一个pipeline能控制反方向的数据流动--就好像将成品分解成原材料。

一个Wangle handler可以同时掌控上游和下游的两个方向的数据流动。当你把所有的handler连接在一起,就可以用一种灵活的方式将原始数据组装为想要的数据类型或者将已有的数据拆分。

在我们的服务器的pipeline中大致将会有下面几种handler:

1.Handler 1 (下文的上游下游是指对一同个handler而言,根据其在pipeline中的位置不同,输入输出相反) 上游:将从socket中接收的二进制数据流写入一个零拷贝(zero-copy,指省略了Applicaion context和Kernel context之间的上下文切换,避免了CPU对Buffer的冗余拷贝,直接在Kernel级别进行数据传输的技术,详情请参阅维基百科)的字节缓存中,发送给handler2

下游:接收一个零拷贝的字节缓存,将其内容写入socket中

2.Handler2 上游:接收handler1的缓存对象,解码为一个string对象传递给handler3 下游:接收handler3的string对象,将其转码为一个零拷贝的字节缓存,发送给handler1

3.Handler3 上游:接收handler2中的string对象,再向下发送至pipeline等待写回客户端。string会发回handler2 下游:接收上游的string对象,传递给handler2

需要注意的一点是,每一个handler应当只做一件事并且只有一件,如果你有一个handler里做了多项任务,比如从二进制流离直接解码出string,那么你需要学会将它拆分。这对提升代码的可维护性和扩展性非常重要。

另外,没错,handler不是线程安全的,所以不要轻易的在其中使用任何没有经过mutex,atomic lock保护的数据,如果你确实需要一个线程安全的环境,Folly提供了一种免于加锁的数据结构, Folly依赖于Wangle,你可以很容易的在项目中引入并使用它。

如果你还不是很明白所有的步骤,不用着急,在看到下面的具体实现时你会更加清楚。

Echo Server

下面我会展示服务器的具体实现。我假定您已经安装好Wangle。需要注意的是截至目前Wangle还不能在Mac OS上安装,我建议您可以安装虚拟机,使用Ubuntu来安装Wangle。

这就是echo handler:接收一个string,打印到stdout中,再发送回pipeline。要注意write语句中的定界符不可以省略,因为pipeline会按照字节解码。

// 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,现在我们需要创建一个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中的二进制流转换成零拷贝字节缓存 下游:将字节缓存内容写入底层socket

2. LineBasedFrameDecoder: 上游:接收字节缓存,按行分割数据 下游:将字节缓存发送给AsyncSocketHandler

3. StringCodec: 上游:接收字节缓存,解码为std:string传递给EchoHandler 下游:接收std:string, 编码为字节缓存,传递给LineBasedFrameDecoder

4. EchoHandler: 上游:接收std:string对象,将其写入pipeline-将消息返回给Echohandler。 下游:接收一个std:string对象,转发给StringCodec Handler。 现在我们所需要做的就是将pipeline factory关联到ServerBootstrap,绑定一个端口,这样我们已经完成了 基本上所有的工作。

#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++服务器。

Echo Client

echo客户端的实现与我们的服务端非常类似:

// 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两个方法,还有其他一些方法可以被重载。如果你需要控制某个特别的事件,只需要重载对应的虚函数即可。

这是客户端的pipeline factory的实现,与我们的服务端结构基本一致,只有EventBaseHandler这个handler在服务端代码中不曾出现,它可以确保我们可以从任意一个线程写入数据。

// 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&amp;, 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(&amp;argc, &amp;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&amp; e) {

 std::cout << exceptionStr(e) << std::endl;

 }

 return 0;

}

程序用一个While循环不断监测用户的输入,并且依靠调用.get() 来同步等待一直到请求被响应。

以上就是C++编写高性能服务器实例教程的详细内容,更多关于C++高性能服务器的资料请关注我们其它相关文章!

(0)

相关推荐

  • 使用C++制作简单的web服务器(续)

    增加功能: 1.从文件中读取网页并返回给客户端,而不是把网页代码写死在代码中. 局限: 1.还不能根据URL地址中的参数,访问指定页面 2.仍然是单线程 复制代码 代码如下: //***************************************************************************** //@ProjectName      ZYhttpd //@Description      my http server //@Author          

  • C++简单QQ程序服务器端的实现代码

    本文实例为大家分享了C++简单QQ程序服务器端的实现代码,供大家参考,具体内容如下 // ServerDlg.cpp : implementation file // #include "StdAfx.h" #include "Server.h" #include "ServerDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] =

  • C++实现简单的HTTP服务器

    本文实例为大家分享了C++实现HTTP服务器的相关代码,供大家参考,具体内容如下 #include <Winsock2.h> #include <windows.h> #include <malloc.h> #include <stdio.h> #include <string.h> #include <time.h> #pragma comment (lib,"ws2_32") #define uPort 80

  • 使用C++制作简单的web服务器

    仅仅实现了web的功能,局限性比较大,由于只是想要理解web服务器的原理,所以地址和端口是固定的,也不支持多线程,每次访问的页面也是一致的,后面自己会慢慢改进这些 复制代码 代码如下: //***************************************************************************** //@ProjectName      ZYhttpd //@Description      my http server //@Author     

  • C++编写高性能服务器实例教程

    我将展示如何使用现代C++编写一个Echo服务器,相当于分布式系统开发中的"Hello World".这个服务器会将接收的消息直接返回.我们同时需要一个可以向我们的服务器发动消息的客户端,在这里可以发现客户端的源码. Wangle是一个用来搭建事件驱动的现代异步C++服务的C/S应用框架.Wangle最基本的抽象概念就是Pipeline(管线).能够理解这种抽象,将会很容易写出各种复杂的现代C++服务,另一个重要的概念是Service(服务),其可以看作一种更高级的Pipeline,不

  • 利用Ruby的SOAP4R编写SOAP服务器的教程

     什么是SOAP ? 简单对象访问协议(SOAP)是一个跨平台和语言无关的,基于XML的RPC协议,通常(但不一定)是HTTP. 它使用XML来编码信息使远程过程调用,HTTP在网络上从客户机到服务器来传输信息,反之亦然. SOAP有几个优势超过其他技术,如COM,CORBA等为例,其相对廉价的部署和调试成本,它的可扩展性和易于使用,存在几种不同的语言和平台实现. 请参阅出简单的教程了解 SOAP 本教程将熟悉SOAP实现Ruby(SOAP4R).这是一个基本的教程,所以如果需要深入细节,那么需

  • Android中使用DialogFragment编写对话框的实例教程

    Android提供alert.prompt.pick-list,单选.多选,progress.time-picker和date-picker对话框,并提供自定义的dialog.在Android 3.0后,dialog基于fragment,并对之前版本提供兼容支持库,也就是说对于开发者而言,dialog是基于DialogFragment的,但此时需要在应用中加入相关的兼容库. 和Windows或者网页JS的Dialog不同,Android的dialog是异步的,而不是同步的.对于同步的dialog

  • Python使用SocketServer模块编写基本服务器程序的教程

    SocketServer简化了网络服务器的编写.它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer.这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步. 创建服务器的步骤.首先,你必须创建一个请求处理类,它是BaseRequestHandler的子类并重载其handle()方法.其次,你必须实例化一个服务器类,传入服务器的地址和请求处理程序类.最后,调用handle_requ

  • ReactJS入门实例教程详解

    目录 一.ReactJS简介 二.对ReactJS的认识及ReactJS的优点 1.ReactJS的背景和原理 2.组件化 三.下载ReactJS,编写Hello,world 四.Jsx语法 五.ReactJS组件 1.组件属性 2.组件状态 3.组件的生命周期 4.组件的嵌套 六.ReactJS小结 七.参考资料 现在最热门的前端框架有AngularJS.React.Bootstrap等.自从接触了ReactJS,ReactJs的虚拟DOM(Virtual DOM)和组件化的开发深深的吸引了我

  • 微信小程序开发之入门实例教程篇

    前言 近日,在工作闲暇之余,阅读了一些关于微信小程序的文章,忍不住,想动手试他一试.本文就以"我的第一个微信小程序"为例,简单的介绍下,微信小程序的入门级用法. 一.注册小程序账号 1.进入微信公众平台(https://mp.weixin.qq.com/),注册小程序账号,根据提示填写对应的信息即可. 2.注册成功后进入首页,在 小程序发布流程->小程序开发与管理->配置服务器中,点击"开发者设置". 3.会获得一个AppID,记录AppID,后面创建项

  • Git 命令行教程及实例教程(附github注册)

    本篇博客主要讲解以下问题: Git 常用命令 创建新仓库 检出仓库 添加与提交 推送改动 分支 更新与合并 标签 替换本地改动 Git实例教程 操作小技巧 Git 常用命令常用命令 创建新仓库 创建新文件夹,打开,然后执行 git init 以创建新的 git 仓库. 检出仓库 执行如下命令以创建一个本地仓库的克隆版本: git clone /path/to/repository 如果是远端服务器上的仓库,你的命令会是这个样子: git clone username@host:/path/to/

  • web标准布局实例教程,用定位轻松解决CSS复杂布局

    我相信来经典论坛学习标准的朋友,99%都不是为了进W3C,不是为了成为专业的研究人员,那么大家来的目的是什么呢?很简单,其实都是想通过标准的学习让自己多增加一项技能而已,让自己在找工作的时候不会在被"需要熟练掌握web标准"而难倒,在下面的实例中xhtml并没有什么语意,文章的目的只是让然大家能更好的掌握CSS中相对.绝对定位的用法(新手问此问题的确实很多),及如何有效的结合背景,实现比较复杂的布局.不足之处还望前辈们能指点一二,感谢! ------------------------

  • Python实现命令行通讯录实例教程

    1.实现目标 编写一个命令行通讯录程序,可以添加.查询.删除通讯录好友及电话 2.实现方法 创建一个类来表示一个人的信息.使用字典存储每个人的对象,名字作为键. 使用pickle模块永久地把这些对象存储下来. 使用字典内建的方法添加.删除修改人员信息. 3.思维导图 4.编写伪代码 # 1.创建字典用来存储通讯录信息 # 2.创建人员类,包含姓名.关系.电话三个属性 # 3.创建操作类,包含增加.查询.删除人员,退出,保存并退出五个方法 # 4.程序运行 # 5.判断通讯录文件是否存在 # 6.

  • Vue.js快速入门实例教程

    什么是vue vue是法语中视图的意思,Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API. 一.基本结构 index.html代码: <script src="../vue.js"></script> <div id="app"> {{ message }} </div> <script src="app.js"></script> <

随机推荐