C/C++网络编程总结与ZeroMQ
转载自:http://blog.****.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()函数
- 服务器端
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<errno.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #define MAXLINE 4096
- int main(int argc, char** argv)
- {
- int listenfd, connfd;
- struct sockaddr_in servaddr;
- char buff[4096];
- int n;
- if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
- printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(6666);
- if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
- printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- if( listen(listenfd, 10) == -1){
- printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- printf("======waiting for client's request======\n");
- while(1){
- if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
- printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
- continue;
- }
- n = recv(connfd, buff, MAXLINE, 0);
- buff[n] = '\0';
- printf("recv msg from client: %s\n", buff);
- close(connfd);
- }
- close(listenfd);
- }
- 客户端
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #include<errno.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #define MAXLINE 4096
- int main(int argc, char** argv)
- {
- int sockfd, n;
- char recvline[4096], sendline[4096];
- struct sockaddr_in servaddr;
- if( argc != 2){
- printf("usage: ./client <ipaddress>\n");
- exit(0);
- }
- if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
- printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
- exit(0);
- }
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(6666);
- if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
- printf("inet_pton error for %s\n",argv[1]);
- exit(0);
- }
- if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
- printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- printf("send msg to server: \n");
- fgets(sendline, 4096, stdin);
- if( send(sockfd, sendline, strlen(sendline), 0) < 0)
- {
- printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
- exit(0);
- }
- close(sockfd);
- exit(0);
- }
具体可参考: 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,号称世上最快消息内核。
|
ActiveMQ | RabbitMQ | ZeroMQ |
遵循规范 | JMS1.1及J2EE1.4 | AMPQ | --- |
架构模型 | 消息代理架构Broker | 消息代理架构Broker | C/S架构 |
实现语言 | Java | Erlang | C/C++ |
支持消息协议 | Stomp | AMPQ、Stomp等 | --- |
主要推动力量 | Apache、Redhat | Lshift、Vmware、SpringSource | iMatix |
支持编程语言 | C,Java,Python | C,Java,Python | C,Java,Python |
编程复杂度 | 复杂 | 简单 | 中等 |
持久化 | 支持 | 支持,不支持第三方数据库 | 发送端缓存 |
性能 | Normal | Normal | High |
内存使用率 | High | High | Normal |
下面是一个网上对消息系统的总结,也可以参考以下。
常见开源消息系统
3.最快的消息中间件zeroMQ
ZeroMQ简介
参考1: ØMQ(ZeroMQ)简介
参考2:ZeroMQ研究与应用分析[推荐]
4.ZeroMQ的C版本nanomsg,更快
为啥又整出个ZeroMQ的C版本呢?
具本人所知,ZeroMQ作者在实现ZeroMQ后,有一天幡然醒悟“如果用C来实现ZeroMQ会不会更快呢?”所以他就用C语言重新实现了ZeroMQ,即nanomsg,目前是alpha2版本。官方网站:http://nanomsg.org/index.html
关于为什么要用C实现zeromq,其实上面是作者的杜撰。具体原因可以参照以下分析:)
从网上的资料来看,nanomsg确实比zeromq更快。
5.ZeorMQ优点,我为什么使用MQ?
1)使用简单,不需要部署服务器什么的,直接编译后作为一个动态库使用;
2) 编程开发简单
以下是zeromq的一个“helloword”示例:
Server
- //
- // Hello World server in C++
- // Binds REP socket to tcp://*:5555
- // Expects "Hello" from client, replies with "World"
- //
- #include <zmq.hpp>
- #include <string>
- #include <iostream>
- #ifndef _WIN32
- #include <unistd.h>
- #else
- #include <windows.h>
- #endif
- int main () {
- // Prepare our context and socket
- zmq::context_t context (1);
- zmq::socket_t socket (context, ZMQ_REP);
- socket.bind ("tcp://*:5555");
- while (true) {
- zmq::message_t request;
- // Wait for next request from client
- socket.recv (&request);
- std::cout << "Received Hello" << std::endl;
- // Do some 'work'
- #ifndef _WIN32
- sleep(1);
- #else
- Sleep (1);
- #endif
- // Send reply back to client
- zmq::message_t reply (5);
- memcpy ((void *) reply.data (), "World", 5);
- socket.send (reply);
- }
- return 0;
- }
Client
- //
- // Hello World client in C++
- // Connects REQ socket to tcp://localhost:5555
- // Sends "Hello" to server, expects "World" back
- //
- #include <zmq.hpp>
- #include <string>
- #include <iostream>
- int main ()
- {
- // Prepare our context and socket
- zmq::context_t context (1);
- zmq::socket_t socket (context, ZMQ_REQ);
- std::cout << "Connecting to hello world server..." << std::endl;
- socket.connect ("tcp://localhost:5555");
- // Do 10 requests, waiting each time for a response
- for (int request_nbr = 0; request_nbr != 10; request_nbr++) {
- zmq::message_t request (6);
- memcpy ((void *) request.data (), "Hello", 5);
- std::cout << "Sending Hello " << request_nbr << "..." << std::endl;
- socket.send (request);
- // Get the reply.
- zmq::message_t reply;
- socket.recv (&reply);
- std::cout << "Received World " << request_nbr << std::endl;
- }
- return 0;
- }
很简单吧。对比其他MQ要么需要部署Server(ActiveMQ,RabbitMQ),要么复杂Boost::ASIO。
作为反面教材,Boost::ASIO中简单的echo-server例子如下:
Server
- //
- // blocking_tcp_echo_server.cpp
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //
- // Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
- //
- // Distributed under the Boost Software License, Version 1.0. (See accompanying
- // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- //
- #include <cstdlib>
- #include <iostream>
- #include <boost/bind.hpp>
- #include <boost/smart_ptr.hpp>
- #include <boost/asio.hpp>
- #include <boost/thread/thread.hpp>
- using boost::asio::ip::tcp;
- const int max_length = 1024;
- typedef boost::shared_ptr<tcp::socket> socket_ptr;
- void session(socket_ptr sock)
- {
- try
- {
- for (;;)
- {
- char data[max_length];
- boost::system::error_code error;
- size_t length = sock->read_some(boost::asio::buffer(data), error);
- if (error == boost::asio::error::eof)
- break; // Connection closed cleanly by peer.
- else if (error)
- throw boost::system::system_error(error); // Some other error.
- boost::asio::write(*sock, boost::asio::buffer(data, length));
- }
- }
- catch (std::exception& e)
- {
- std::cerr << "Exception in thread: " << e.what() << "\n";
- }
- }
- void server(boost::asio::io_service& io_service, unsigned short port)
- {
- tcp::acceptor a(io_service, tcp::endpoint(tcp::v4(), port));
- for (;;)
- {
- socket_ptr sock(new tcp::socket(io_service));
- a.accept(*sock);
- boost::thread t(boost::bind(session, sock));
- }
- }
- int main(int argc, char* argv[])
- {
- try
- {
- if (argc != 2)
- {
- std::cerr << "Usage: blocking_tcp_echo_server <port>\n";
- return 1;
- }
- boost::asio::io_service io_service;
- using namespace std; // For atoi.
- server(io_service, atoi(argv[1]));
- }
- catch (std::exception& e)
- {
- std::cerr << "Exception: " << e.what() << "\n";
- }
- return 0;
- }
- //
- // blocking_tcp_echo_client.cpp
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //
- // Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
- //
- // Distributed under the Boost Software License, Version 1.0. (See accompanying
- // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- //
- #include <cstdlib>
- #include <cstring>
- #include <iostream>
- #include <boost/asio.hpp>
- using boost::asio::ip::tcp;
- enum { max_length = 1024 };
- int main(int argc, char* argv[])
- {
- try
- {
- if (argc != 3)
- {
- std::cerr << "Usage: blocking_tcp_echo_client <host> <port>\n";
- return 1;
- }
- boost::asio::io_service io_service;
- tcp::resolver resolver(io_service);
- tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);
- tcp::resolver::iterator iterator = resolver.resolve(query);
- tcp::socket s(io_service);
- boost::asio::connect(s, iterator);
- using namespace std; // For strlen.
- std::cout << "Enter message: ";
- char request[max_length];
- std::cin.getline(request, max_length);
- size_t request_length = strlen(request);
- boost::asio::write(s, boost::asio::buffer(request, request_length));
- char reply[max_length];
- size_t reply_length = boost::asio::read(s,
- boost::asio::buffer(reply, request_length));
- std::cout << "Reply is: ";
- std::cout.write(reply, reply_length);
- std::cout << "\n";
- }
- catch (std::exception& e)
- {
- std::cerr << "Exception: " << e.what() << "\n";
- }
- return 0;
- }
3)效率高(其实这个不是最重要的,最重要的是1、2)
为了让大伙儿有一个感性的认识,俺特地找来了消息队列软件的性能测评。这是某老外写的一篇帖子(在"这里"),不懂洋文的同学可以看"这里"。连帖子都懒得看的同学,可以直接看下图。
从图中可以明显看出,ZMQ 相比其它几款MQ,简直是鹤立鸡群啊!性能根本不在一个档次嘛。