2019独角兽企业重金招聘Python工程师标准>>>

本文由 伯乐在线 - 袁欣 翻译,艾凌风 校稿。未经许可,禁止转载!
英文出处:James Perry。欢迎加入翻译组。

首先感谢大家对上一篇博文《用 C++ 开启技术创业之旅》的反馈。在上篇文章中我提到我曾在一天内就凭借 Facebook 的 Wangle 搭建起一个数据库引擎的原型,在这里我会解释我是如何做到的。到本文最后,你将可以用 Wangle 编写出一个高性能的C++服务器。本文也将作为教程整合进 Wangle 的 ReadMe.md 中。

我将展示如何使用现代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会按照字节解码。

C++

1

2

3

4

5

6

7

8

9

// 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。

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

// 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,绑定一个端口,这样我们已经完成了 基本上所有的工作。

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

#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客户端的实现与我们的服务端非常类似:

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 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在服务端代码中不曾出现,它可以确保我们可以从任意一个线程写入数据。

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 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);

}

};

客户端所有的代码如下图所示

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

#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() 来同步等待一直到请求被响应。

总结

本文我展示了如何用Wangle来编写一个简易的高性能C++服务器。您应该已经掌握了Wangle的一些基本知识,并且有信心写出自己的C++服务器。我建议您深入了解Wangle中的Service概念,它会有助您开发出更加强大的服务器。

转载于:https://my.oschina.net/u/4000302/blog/3047183

用现代 C++ 写一个高性能的服务器相关推荐

  1. 写一个高性能的服务器,C++编写高性能服务器实例教程.pdf

    C++编编写写高高性性能能服服务务器器实实例例教教程程 我将展示如何使用现代C++编写一个Echo服务器,相当于分布式 统开发中的"Hello World".这个服务器会将接收的消 ...

  2. 在unik中,写一个Go HTTP服务器

    写一个Go HTTP服务器 打开一个新的终端窗口,但让守护进程运行的窗口离开.此窗口将用于运行UniK CLI命令. 使用文本编辑器创建文件httpd.go.将以下代码复制并粘贴到该文件中: pack ...

  3. 写一个高性能的敏感词检测组件

    最近写了一个高性能的敏感词检测组件[ToolGood.Words]. 一.高性能,它的效率到底有多快? 如果将正则表达式的算法效率设为1,高性能可达到正则表达式的1.5万倍. 二.选一个巧妙的算法: ...

  4. vue前端用服务器上路径的图片展示_5分钟教你用nodeJS手写一个mock数据服务器

    对于前端开发者而言,javascript正扮演着越来越重要的地位,它不仅能为浏览器端赋能,在web服务器方面也有很大的价值(我们可以用nodeJS来写服务端代码,启动web服务器),因此本文所要描述的 ...

  5. 用C++写一个简单的服务器和客户端

    我们将创建一个服务器节点add_two_ints_server,它将会收到两个整数,并且返回它们的和.切换目录到之前建立的beginner_tutorials包下: cd ~/catkin_ws/sr ...

  6. 手写一个HTTP图片资源服务器,太容易了叭!

    点击关注公众号,实用技术文章及时了解 来源:binhao.blog.csdn.net/article/details/112059406 摘要 web开发一直是行业热门技术,而要做web程序就离不开h ...

  7. 如何用Netty写一个高性能的分布式服务框架?

    byte[] -->堆外内存 / 堆外内存--> byte[] -->java对象. 优化:省去 byte[] 环节,直接 读/写 堆外内存,这需要扩展对应的序列化框架. Strin ...

  8. 原生node写一个静态资源服务器

    myanywhere 用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解. 相关知识 es6及es7语法 http的相关网络知识 响应头 缓存相关 压缩相 ...

  9. ROS的学习(十六)用C++写一个简单的服务器(service)和客户端(client)

    我们将创建一个服务器节点add_two_ints_server,它将会收到两个整数,并且返回它们的和.切换目录到之前建立的beginner_tutorials包下: cd ~/catkin_ws/sr ...

最新文章

  1. python 报错 cannot import name ‘byte_string‘ from ‘Crypto.Util.py3compat‘ 解决方法
  2. Qt5.12过时的类
  3. 三只松鼠:阿里云数据中台基座上的多渠道、多业态生长
  4. HDFS常用Shell命令
  5. 0-13 sudo用户管理
  6. Cloudflare通过集成ENS和IPFS推出通往分布式Web的网关
  7. LAMP配置虚拟目录
  8. 使用React搭建初始化环境(React入门)
  9. knockout.js的简介和简单使用
  10. python神奇功能_16个你毫不知道的Python神奇技能
  11. Gartner 如何看 RASP 和 WAF?
  12. 2021-06-27 对象类型详解.
  13. android 动态壁纸 例子,android 动态壁纸实例(1)【转】
  14. RQNOJ愚蠢的矿工
  15. 如何安装数据库和数据库安装不了如何解决
  16. 智齿科技携手无忧我房 VR+AI新品亮相GTC
  17. 2019小程序发展趋势
  18. leetcode刷题之 剑指offe 面试题05. 替换空格 犯傻记录
  19. FIRST TODY STAR
  20. 实现一个Android锁屏App功能的难点总结

热门文章

  1. 计算机组成原理(三)存储器的层次结构
  2. iOS编辑预览视频小结
  3. indesign选中不了图片删除_word图文设计:如何用图片水印功能制作日历画册
  4. html5 里面的type=”search“ ,h5版,点击手机键盘上的 ‘搜索”,”前往“等按钮,进行搜索
  5. 阿里云账号注册实名认证详细教程(支付宝实名认证)
  6. [buuctf][Zer0pts2020]easy strcmp
  7. Objective-C类别(catagory)
  8. html 向左箭头图标css,使用css实现箭头图标
  9. 使用Microsoft SyncToy 文件同步/备份 自动化处理
  10. java在线截图_JAVA之网页截屏