C/C++网络编程总结与ZeroMQ

204 篇文章 27 订阅

现在几乎所有C/C++的后台程序都需要进行网络通讯,其实现方法无非有两种:使用系统底层socket或者使用已有的封装好的网络库。本文对两种方式进行总结,并介绍一个轻量级的网络通讯库ZeroMQ。

 

1.基本的Scoket编程

关于基本的scoket编程网络上已有很多资料,作者在这里引用一篇文章中的内容进行简要说明。

基于socket编程,基本上就是以下6个步骤:

 

1、socket()函数 2、bind()函数 3、listen()、connect()函数 4、accept()函数 5、read()、write()函数等 6、close()函数 下面直接引用文章里的代码说明。

 

 

 
  1. 服务器端

  2.  
  3. #include

  4. #include

  5. #include

  6. #include

  7. #include

  8. #include

  9. #include

  10.  
  11. #define MAXLINE 4096

  12.  
  13. int main(int argc, char** argv)

  14. {

  15. int listenfd, connfd;

  16. struct sockaddr_in servaddr;

  17. char buff[4096];

  18. int n;

  19.  
  20. if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){

  21. printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);

  22. exit(0);

  23. }

  24.  
  25. memset(&servaddr, 0, sizeof(servaddr));

  26. servaddr.sin_family = AF_INET;

  27. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  28. servaddr.sin_port = htons(6666);

  29.  
  30. if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){

  31. printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);

  32. exit(0);

  33. }

  34.  
  35. if( listen(listenfd, 10) == -1){

  36. printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);

  37. exit(0);

  38. }

  39.  
  40. printf("======waiting for client's request======\n");

  41. while(1){

  42. if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){

  43. printf("accept socket error: %s(errno: %d)",strerror(errno),errno);

  44. continue;

  45. }

  46. n = recv(connfd, buff, MAXLINE, 0);

  47. buff[n] = '\0';

  48. printf("recv msg from client: %s\n", buff);

  49. close(connfd);

  50. }

  51.  
  52. close(listenfd);

  53. }

 
  1. 客户端

  2.  
  3. #include

  4. #include

  5. #include

  6. #include

  7. #include

  8. #include

  9. #include

  10.  
  11. #define MAXLINE 4096

  12.  
  13. int main(int argc, char** argv)

  14. {

  15. int sockfd, n;

  16. char recvline[4096], sendline[4096];

  17. struct sockaddr_in servaddr;

  18.  
  19. if( argc != 2){

  20. printf("usage: ./client \n");

  21. exit(0);

  22. }

  23.  
  24. if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){

  25. printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);

  26. exit(0);

  27. }

  28.  
  29. memset(&servaddr, 0, sizeof(servaddr));

  30. servaddr.sin_family = AF_INET;

  31. servaddr.sin_port = htons(6666);

  32. if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){

  33. printf("inet_pton error for %s\n",argv[1]);

  34. exit(0);

  35. }

  36.  
  37. if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){

  38. printf("connect error: %s(errno: %d)\n",strerror(errno),errno);

  39. exit(0);

  40. }

  41.  
  42. printf("send msg to server: \n");

  43. fgets(sendline, 4096, stdin);

  44. if( send(sockfd, sendline, strlen(sendline), 0) < 0)

  45. {

  46. printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);

  47. exit(0);

  48. }

  49.  
  50. close(sockfd);

  51. exit(0);

  52. }


具体可参考: Linux Socket编程(不限Linux)

 

小结:直接基于socket编程看起来十分简单,但是要写出一个稳定、高性能的程序还是十分考验水平的,比如数据分包与重组,异步IO,并发访问,poll/select等,非大牛不能为之。鉴于此出现很多封装好的C/C++网络通讯库,或者叫消息中间件,下面进行介绍。

2.流行的通讯库/消息中间件

网络上各种各样的通讯中间件/MQ多不胜数。具作者所知,比较有名的有ACE、ICE、Boost::ASIO、MSMQ、ActiveMQ、RabbitMQ、ZeroMQ等等。

其中ACE、ICE是经典,网上资料很丰富,不过入门门槛较高,说白了就是有点难学难精。属于高端大气上档次的货。

Boost::ASIO作为大名鼎鼎的Boost模块之一,感觉很不错,只需引用.hpp文件,不需要动态库,性能据说也不错,跨平台,值得推荐和学习。不过没有最简单只有更简单,当有更好的选择时,Boost::ASIO的语法就略显复杂了。(不过还是强力推荐)。

MSMQ 微软的东西,用起来还可以,不过一般不推荐,毕竟linux下没人会用它。

JSM、ActiveMQ、RabbitMQ、ZeroMQ基本上是一类东西。activemq,基于jms稳定可靠安全。rabbitmq,基于erlang,充分利用并发和分布式特性。zeromq,号称世上最快消息内核。

 

lActiveMQ pActiveMQ是Apache开发的开源消息中间件,纯Java实现,基于JMS1.1及J2EE 1.4规范。 lRabbitMQ pRabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol (AMQP) 的开源实现,由以高性能、健壮以及可伸缩性出名的 Erlang 写成,因此也是继承了这些优点。 lZeroMQp是由iMatix公司使用C语言开发的高性能消息中间件,是对socket的封装,在发送端缓存消息。

 

 

 

ActiveMQRabbitMQZeroMQ
遵循规范JMS1.1及J2EE1.4AMPQ---
架构模型消息代理架构Broker消息代理架构BrokerC/S架构
实现语言JavaErlangC/C++
支持消息协议StompAMPQ、Stomp等---
主要推动力量Apache、RedhatLshift、Vmware、SpringSourceiMatix
支持编程语言C,Java,PythonC,Java,PythonC,Java,Python
编程复杂度复杂简单中等
持久化支持支持,不支持第三方数据库发送端缓存
性能NormalNormalHigh
内存使用率HighHighNormal

引用自邱志刚的Message oriented middleware analysis,抱歉未找到链接。

 

下面是一个网上对消息系统的总结,也可以参考以下。

 

常见开源消息系统

 

 

3.最快的消息中间件zeroMQ

ZeroMQ简介

参考1: ?MQ(ZeroMQ)简介

参考2:ZeroMQ研究与应用分析[推荐]

参考3:高性能的通讯库-zeroMQ的几个高性能特征

参考4:ZeroMQ详细教程,从入门到深入,34篇

4.ZeroMQ的C版本nanomsg,更快

为啥又整出个ZeroMQ的C版本呢?

具本人所知,ZeroMQ作者在实现ZeroMQ后,有一天幡然醒悟“如果用C来实现ZeroMQ会不会更快呢?”所以他就用C语言重新实现了ZeroMQ,即nanomsg,目前是alpha2版本。官方网站:http://nanomsg.org/index.html

关于为什么要用C实现zeromq,其实上面是作者的杜撰。具体原因可以参照以下分析:)

为什么我希望用C而不是C++来实现ZeroMQ

为什么我希望用C而不是C++来实现ZeroMQ(第二篇)

 

从网上的资料来看,nanomsg确实比zeromq更快。

5.ZeorMQ优点,我为什么使用MQ?

1)使用简单,不需要部署服务器什么的,直接编译后作为一个动态库使用;

2) 编程开发简单

以下是zeromq的一个“helloword”示例:

Server

 

 
  1. //

  2. // Hello World server in C++

  3. // Binds REP socket to tcp://*:5555

  4. // Expects "Hello" from client, replies with "World"

  5. //

  6. #include

  7. #include

  8. #include

  9. #ifndef _WIN32

  10. #include

  11. #else

  12. #include <windows.h>

  13. #endif

  14.  
  15. int main () {

  16. // Prepare our context and socket

  17. zmq::context_t context (1);

  18. zmq::socket_t socket (context, ZMQ_REP);

  19. socket.bind ("tcp://*:5555");

  20.  
  21. while (true) {

  22. zmq::message_t request;

  23.  
  24. // Wait for next request from client

  25. socket.recv (&request);

  26. std::cout << "Received Hello" << std::endl;

  27.  
  28. // Do some 'work'

  29. #ifndef _WIN32

  30. sleep(1);

  31. #else

  32. Sleep (1);

  33. #endif

  34.  
  35. // Send reply back to client

  36. zmq::message_t reply (5);

  37. memcpy ((void *) reply.data (), "World", 5);

  38. socket.send (reply);

  39. }

  40. return 0;

  41. }

 

Client

 

 
  1. //

  2. // Hello World client in C++

  3. // Connects REQ socket to tcp://localhost:5555

  4. // Sends "Hello" to server, expects "World" back

  5. //

  6. #include

  7. #include

  8. #include

  9. int main ()

  10. {

  11. // Prepare our context and socket

  12. zmq::context_t context (1);

  13. zmq::socket_t socket (context, ZMQ_REQ);

  14. std::cout << "Connecting to hello world server..." << std::endl;

  15. socket.connect ("tcp://localhost:5555");

  16. // Do 10 requests, waiting each time for a response

  17. for (int request_nbr = 0; request_nbr != 10; request_nbr++) {

  18. zmq::message_t request (6);

  19. memcpy ((void *) request.data (), "Hello", 5);

  20. std::cout << "Sending Hello " << request_nbr << "..." << std::endl;

  21. socket.send (request);

  22. // Get the reply.

  23. zmq::message_t reply;

  24. socket.recv (&reply);

  25. std::cout << "Received World " << request_nbr << std::endl;

  26. }

  27. return 0;

  28. }

 

 

 

很简单吧。对比其他MQ要么需要部署Server(ActiveMQ,RabbitMQ),要么复杂Boost::ASIO。

作为反面教材,Boost::ASIO中简单的echo-server例子如下:

 

Server

 

 
  1. //

  2. // blocking_tcp_echo_server.cpp

  3. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  4. //

  5. // Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)

  6. //

  7. // Distributed under the Boost Software License, Version 1.0. (See accompanying

  8. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

  9. //

  10.  
  11. #include

  12. #include

  13. #include

  14. #include

  15. #include

  16. #include

  17.  
  18. using boost::asio::ip::tcp;

  19.  
  20. const int max_length = 1024;

  21.  
  22. typedef boost::shared_ptr socket_ptr;

  23.  
  24. void session(socket_ptr sock)

  25. {

  26. try

  27. {

  28. for (;;)

  29. {

  30. char data[max_length];

  31.  
  32. boost::system::error_code error;

  33. size_t length = sock->read_some(boost::asio::buffer(data), error);

  34. if (error == boost::asio::error::eof)

  35. break; // Connection closed cleanly by peer.

  36. else if (error)

  37. throw boost::system::system_error(error); // Some other error.

  38.  
  39. boost::asio::write(*sock, boost::asio::buffer(data, length));

  40. }

  41. }

  42. catch (std::exception& e)

  43. {

  44. std::cerr << "Exception in thread: " << e.what() << "\n";

  45. }

  46. }

  47.  
  48. void server(boost::asio::io_service& io_service, unsigned short port)

  49. {

  50. tcp::acceptor a(io_service, tcp::endpoint(tcp::v4(), port));

  51. for (;;)

  52. {

  53. socket_ptr sock(new tcp::socket(io_service));

  54. a.accept(*sock);

  55. boost::thread t(boost::bind(session, sock));

  56. }

  57. }

  58.  
  59. int main(int argc, char* argv[])

  60. {

  61. try

  62. {

  63. if (argc != 2)

  64. {

  65. std::cerr << "Usage: blocking_tcp_echo_server \n";

  66. return 1;

  67. }

  68.  
  69. boost::asio::io_service io_service;

  70.  
  71. using namespace std; // For atoi.

  72. server(io_service, atoi(argv[1]));

  73. }

  74. catch (std::exception& e)

  75. {

  76. std::cerr << "Exception: " << e.what() << "\n";

  77. }

  78.  
  79. return 0;

  80. }

Client

 

 

 
  1. //

  2. // blocking_tcp_echo_client.cpp

  3. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  4. //

  5. // Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)

  6. //

  7. // Distributed under the Boost Software License, Version 1.0. (See accompanying

  8. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

  9. //

  10.  
  11. #include

  12. #include

  13. #include

  14. #include

  15.  
  16. using boost::asio::ip::tcp;

  17.  
  18. enum { max_length = 1024 };

  19.  
  20. int main(int argc, char* argv[])

  21. {

  22. try

  23. {

  24. if (argc != 3)

  25. {

  26. std::cerr << "Usage: blocking_tcp_echo_client \n";

  27. return 1;

  28. }

  29.  
  30. boost::asio::io_service io_service;

  31.  
  32. tcp::resolver resolver(io_service);

  33. tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);

  34. tcp::resolver::iterator iterator = resolver.resolve(query);

  35.  
  36. tcp::socket s(io_service);

  37. boost::asio::connect(s, iterator);

  38.  
  39. using namespace std; // For strlen.

  40. std::cout << "Enter message: ";

  41. char request[max_length];

  42. std::cin.getline(request, max_length);

  43. size_t request_length = strlen(request);

  44. boost::asio::write(s, boost::asio::buffer(request, request_length));

  45.  
  46. char reply[max_length];

  47. size_t reply_length = boost::asio::read(s,

  48. boost::asio::buffer(reply, request_length));

  49. std::cout << "Reply is: ";

  50. std::cout.write(reply, reply_length);

  51. std::cout << "\n";

  52. }

  53. catch (std::exception& e)

  54. {

  55. std::cerr << "Exception: " << e.what() << "\n";

  56. }

  57.  
  58. return 0;

  59. }

其实Boost算是简单的了,只是有对比才有进步嘛,ZeroMQ的代码仅有一半(不过这似乎说明不了什么问题)。

 

3)效率高(其实这个不是最重要的,最重要的是1、2)

为了让大伙儿有一个感性的认识,俺特地找来了消息队列软件的性能测评。这是某老外写的一篇帖子(在"这里"),不懂洋文的同学可以看"这里"。连帖子都懒得看的同学,可以直接看下图。

从图中可以明显看出,ZMQ 相比其它几款MQ,简直是鹤立鸡群啊!性能根本不在一个档次嘛。

 

https://blog.csdn.net/zhu2695/article/details/51296436

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值