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

来源:互联网 发布:淘宝时尚韩国女装店铺 编辑:程序博客网 时间:2024/06/09 18:59

转载自:http://blog.csdn.net/mydipan396/article/details/44411171


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


1.基本的Scoket编程

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

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

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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 服务器端  
  2.   
  3. #include<stdio.h>  
  4. #include<stdlib.h>  
  5. #include<string.h>  
  6. #include<errno.h>  
  7. #include<sys/types.h>  
  8. #include<sys/socket.h>  
  9. #include<netinet/in.h>  
  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. }  
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 客户端  
  2.   
  3. #include<stdio.h>  
  4. #include<stdlib.h>  
  5. #include<string.h>  
  6. #include<errno.h>  
  7. #include<sys/types.h>  
  8. #include<sys/socket.h>  
  9. #include<netinet/in.h>  
  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 <ipaddress>\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 写成,因此也是继承了这些优点。
lZeroMQ
p是由iMatix公司使用C语言开发的高性能消息中间件,是对socket的封装,在发送端缓存消息。

 

ActiveMQRabbitMQZeroMQ遵循规范JMS1.1J2EE1.4AMPQ---架构模型消息代理架构Broker消息代理架构BrokerC/S架构实现语言JavaErlangC/C++支持消息协议StompAMPQStomp---主要推动力量ApacheRedhatLshiftVmwareSpringSourceiMatix支持编程语言CJavaPythonCJavaPythonCJavaPython编程复杂度复杂简单中等持久化支持支持,不支持第三方数据库发送端缓存性能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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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 <zmq.hpp>  
  7. #include <string>  
  8. #include <iostream>  
  9. #ifndef _WIN32  
  10. #include <unistd.h>  
  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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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 <zmq.hpp>  
  7. #include <string>  
  8. #include <iostream>  
  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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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 <cstdlib>  
  12. #include <iostream>  
  13. #include <boost/bind.hpp>  
  14. #include <boost/smart_ptr.hpp>  
  15. #include <boost/asio.hpp>  
  16. #include <boost/thread/thread.hpp>  
  17.   
  18. using boost::asio::ip::tcp;  
  19.   
  20. const int max_length = 1024;  
  21.   
  22. typedef boost::shared_ptr<tcp::socket> 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 <port>\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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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 <cstdlib>  
  12. #include <cstring>  
  13. #include <iostream>  
  14. #include <boost/asio.hpp>  
  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 <host> <port>\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,简直是鹤立鸡群啊!性能根本不在一个档次嘛。

原创粉丝点击