---恢复内容开始---

 
 

一、前言

boost asio可算是一个简单易用,功能又强大可跨平台的C++通讯库,效率也表现的不错,linux环境是epoll实现的,而windows环境是iocp实现的。而tcp通讯是项目当中经常用到通讯方式之一,实现的方法有各式各样,因此总结一套适用于自己项目的方法是很有必要,很可能下一个项目直接套上去就可以用了。

二、实现思路

1.通讯包数据结构

Tag:检查数据包是否合法,具体会在下面讲解;

Length:描述Body的长度;

Command:表示数据包的类型,0表示心跳包(长连接需要心跳来检测连接是否正常),1表示注册包(客户端连接上服务器之后要将相关信息注册给服务器),2表示业务消息包;

business_type:业务消息包类型,服务器会根据业务消息包类型将数据路由到对应的客户端(客户端是有业务类型分类的);

app_id:客户端唯一标识符;

Data:消息数据;

2.连接对象

客户端连接上服务器之后,双方都会产生一个socket连接对象,通过这个对象可以收发数据,因此我定义为socket_session。

//socket_session.h

  1. #pragma once
  2. #include <iostream>
  3. #include <list>
  4. #include <hash_map>
  5. #include <boost/bind.hpp>
  6. #include <boost/asio.hpp>
  7. #include <boost/shared_ptr.hpp>
  8. #include <boost/make_shared.hpp>
  9. #include <boost/thread.hpp>
  10. #include <boost/thread/mutex.hpp>
  11. #include <boost/enable_shared_from_this.hpp>
  12. #include <firebird/log/logger_log4.hpp>
  13. #include <firebird/detail/config.hpp>
  14. #include <firebird/socket_utils/message_archive.hpp>
  15. using boost::asio::ip::tcp;
  16. namespace firebird{
  17. enum command{ heartbeat = 0, regist, normal};
  18. const std::string tag = "KDS";
  19. class FIREBIRD_DECL socket_session;
  20. typedef boost::shared_ptr<socket_session> socket_session_ptr;
  21. class FIREBIRD_DECL socket_session:
  22. public boost::enable_shared_from_this<socket_session>,
  23. private boost::noncopyable
  24. {
  25. public:
  26. typedef boost::function<void(socket_session_ptr)> close_callback;
  27. typedef boost::function<void(
  28. const boost::system::error_code&,
  29. socket_session_ptr, message&)> read_data_callback;
  30. socket_session(boost::asio::io_service& io_service);
  31. ~socket_session(void);
  32. DWORD id() { return m_id; }
  33. WORD get_business_type(){ return m_business_type; }
  34. void set_business_type(WORD type) { m_business_type = type; }
  35. DWORD get_app_id(){ return m_app_id; }
  36. void set_app_id(DWORD app_id) { m_app_id = app_id; }
  37. std::string& get_remote_addr() { return m_name; }
  38. void set_remote_addr(std::string& name) { m_name = name; }
  39. tcp::socket& socket() { return m_socket; }
  40. void installCloseCallBack(close_callback cb){ close_cb = cb; }
  41. void installReadDataCallBack(read_data_callback cb) { read_data_cb = cb; }
  42. void start();
  43. void close();
  44. void async_write(const std::string& sMsg);
  45. void async_write(message& msg);
  46. bool is_timeout();
  47. void set_op_time(){std::time(&m_last_op_time);}
  48. private:
  49. static boost::detail::atomic_count m_last_id;
  50. DWORD m_id;
  51. WORD  m_business_type;
  52. DWORD m_app_id;
  53. std::string m_name;
  54. boost::array<char, 7> sHeader;
  55. std::string sBody;
  56. tcp::socket m_socket;
  57. boost::asio::io_service& m_io_service;
  58. std::time_t m_last_op_time;
  59. close_callback close_cb;
  60. read_data_callback read_data_cb;
  61. //发送消息
  62. void handle_write(const boost::system::error_code& e,
  63. std::size_t bytes_transferred, std::string* pmsg);
  64. //读消息头
  65. void handle_read_header(const boost::system::error_code& error);
  66. //读消息体
  67. void handle_read_body(const boost::system::error_code& error);
  68. void handle_close();
  69. };
  70. }

这里注意的是,定义了一个tag="KDS",目的是为了检查收到的数据包是否有效,每一个数据包前3个字节不为“KDS”,那么就认为是非法的请求包,你也可以定义tag等于其它字符串,只要按协议发包就正常,当然这是比较简单的数据包检查方法了。比较严谨的方法是双方使用哈希算法来检查的,怎么做,这里先不做详解。

//socket_session.cpp

  1. #include "socket_session.h"
  2. namespace firebird{
  3. boost::detail::atomic_count socket_session::m_last_id(0);
  4. socket_session::socket_session(boost::asio::io_service& io_srv)
  5. :m_io_service(io_srv), m_socket(io_srv),
  6. m_business_type(0), m_app_id(0)
  7. {
  8. m_id = ++socket_session::m_last_id;
  9. }
  10. socket_session::~socket_session(void)
  11. {
  12. m_socket.close();
  13. }
  14. void socket_session::start()
  15. {
  16. m_socket.set_option(boost::asio::ip::tcp::acceptor::linger(true, 0));
  17. m_socket.set_option(boost::asio::socket_base::keep_alive(true));
  18. std::time(&m_last_op_time);
  19. const boost::system::error_code error;
  20. handle_read_header(error);
  21. }
  22. void socket_session::handle_close()
  23. {
  24. try{
  25. m_socket.close();
  26. close_cb(shared_from_this());
  27. }
  28. catch(std::exception& e)
  29. {
  30. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << e.what() << "]");
  31. }
  32. catch(...)
  33. {
  34. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[未知异常]");
  35. }
  36. }
  37. void socket_session::close()
  38. {
  39. //由于回调中有加锁的情况,必须提交到另外一个线程去做,不然会出现死锁
  40. m_io_service.post(boost::bind(&socket_session::handle_close, shared_from_this()));
  41. }
  42. static int connection_timeout = 60;
  43. bool socket_session::is_timeout()
  44. {
  45. std::time_t now;
  46. std::time(&now);
  47. return now - m_last_op_time > connection_timeout;
  48. }
  49. //读消息头
  50. void socket_session::handle_read_header(const boost::system::error_code& error)
  51. {
  52. LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO  << "enter.");
  53. try{
  54. if(error)
  55. {
  56. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO  << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << error.message().c_str() << "]");
  57. close();
  58. return;
  59. }
  60. std::string data;
  61. data.swap(sBody);
  62. boost::asio::async_read(m_socket,
  63. boost::asio::buffer(sHeader),
  64. boost::bind(&socket_session::handle_read_body, shared_from_this(),
  65. boost::asio::placeholders::error));
  66. if (data.length() > 0 && data != "")
  67. {//读到数据回调注册的READ_DATA函数
  68. message msg;
  69. message_iarchive(msg, data);
  70. read_data_cb(error, shared_from_this(), msg);
  71. }
  72. }
  73. catch(std::exception& e)
  74. {
  75. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << e.what() << "]");
  76. close();
  77. }
  78. catch(...)
  79. {
  80. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[未知异常]");
  81. close();
  82. }
  83. }
  84. //读消息体
  85. void socket_session::handle_read_body(const boost::system::error_code& error)
  86. {
  87. LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << "enter.");
  88. try{
  89. if(error)
  90. {
  91. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << error.message().c_str() << "]");
  92. close();
  93. return;
  94. }
  95. if (tag.compare(0, tag.length(), sHeader.data(), 0, tag.length()))
  96. {
  97. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO <<  "连接远程地址:[" << get_remote_addr() << "],socket异常:[这是个非法连接!]");
  98. close();
  99. return;
  100. }
  101. DWORD dwLength = 0;
  102. char* len = (char*)&dwLength;
  103. memcpy(len, &sHeader[tag.length()], sizeof(dwLength));
  104. sBody.resize(dwLength);
  105. char* pBody = &sBody[0];
  106. boost::asio::async_read(m_socket,
  107. boost::asio::buffer(pBody, dwLength),
  108. boost::bind(&socket_session::handle_read_header, shared_from_this(),
  109. boost::asio::placeholders::error));
  110. }
  111. catch(std::exception& e)
  112. {
  113. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << e.what() << "]");
  114. close();
  115. }
  116. catch(...)
  117. {
  118. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[未知异常]");
  119. close();
  120. }
  121. }
  122. void socket_session::handle_write(const boost::system::error_code& error,
  123. std::size_t bytes_transferred, std::string* pmsg)
  124. {
  125. //数据发送成功就销毁
  126. if (pmsg != NULL)
  127. {
  128. delete pmsg;
  129. }
  130. if(error)
  131. {
  132. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << error.message().c_str() << "]");
  133. close();
  134. return;
  135. }
  136. }
  137. void socket_session::async_write(const std::string& sMsg)
  138. {
  139. LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO  << "enter.")
  140. try
  141. {
  142. DWORD dwLength = sMsg.size();
  143. char* pLen = (char*)&dwLength;
  144. //由于是异步发送,要保证数据发送完整时,才把数据销毁
  145. std::string* msg = new std::string();
  146. msg->append(tag);
  147. msg->append(pLen, sizeof(dwLength));
  148. msg->append(sMsg);
  149. boost::asio::async_write(m_socket,boost::asio::buffer(*msg, msg->size()),
  150. boost::bind(&socket_session::handle_write, shared_from_this(),
  151. boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred,
  152. msg));
  153. }
  154. catch(std::exception& e)
  155. {
  156. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[" << e.what() << "]");
  157. close();
  158. }
  159. catch(...)
  160. {
  161. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << get_remote_addr() << "],socket异常:[未知异常]");
  162. close();
  163. }
  164. }
  165. void socket_session::async_write(message& msg)
  166. {
  167. std::string data;
  168. message_oarchive(data, msg);
  169. async_write(data);
  170. }
  171. }

接受数据时,socket_session会先读取7个字节的head,比较前3个字节“KDS”,然后取得4个字节的Length,再读出Length长度的数据,最后将该数据传给read_data_cb回调函数处理,read_data_cb回调函数是在外部注册的。

3.连接管理器

对于服务器来说,它同时服务多个客户端,为了有效的管理,因此需要一个连接管理器,我定义为session_manager。session_manager主要是对socket_session的增删改查,和有效性检查。

//session_manager.h

  1. #pragma once
  2. #include "socket_session.h"
  3. #include "filter_container.h"
  4. #include <boost/date_time/posix_time/posix_time.hpp>
  5. #include <boost/multi_index_container.hpp>
  6. #include <boost/multi_index/member.hpp>
  7. #include <boost/multi_index/ordered_index.hpp>
  8. #include <boost/typeof/typeof.hpp>
  9. #include <boost/random.hpp>
  10. #include <boost/pool/detail/singleton.hpp>
  11. namespace firebird{
  12. template<typename T>
  13. class var_gen_wraper
  14. {
  15. public:
  16. var_gen_wraper(): gen(boost::mt19937((boost::int32_t)std::time(0)),
  17. boost::uniform_smallint<>(1, 100)) {}
  18. typename T::result_type operator() () { return gen(); }
  19. private:
  20. T gen;
  21. };
  22. struct  session_stu
  23. {
  24. DWORD   id;
  25. WORD    business_type;
  26. std::string address;
  27. DWORD   app_id;
  28. socket_session_ptr session;
  29. };
  30. struct sid{};
  31. struct sbusiness_type{};
  32. struct saddress{};
  33. struct sapp_id{};
  34. enum session_idx_member{ session_id = 0, session_business_type, session_address, app_id};
  35. #define CLIENT 0
  36. #define SERVER 1
  37. typedef boost::multi_index::multi_index_container<
  38. session_stu,
  39. boost::multi_index::indexed_by<
  40. boost::multi_index::ordered_unique<
  41. boost::multi_index::tag<sid>, BOOST_MULTI_INDEX_MEMBER(session_stu, DWORD, id)>,
  42. boost::multi_index::ordered_non_unique<
  43. boost::multi_index::tag<sbusiness_type>, BOOST_MULTI_INDEX_MEMBER(session_stu, WORD, business_type)>,
  44. boost::multi_index::ordered_non_unique<
  45. boost::multi_index::tag<saddress>, BOOST_MULTI_INDEX_MEMBER(session_stu, std::string, address)>,
  46. boost::multi_index::ordered_non_unique<
  47. boost::multi_index::tag<sapp_id>, BOOST_MULTI_INDEX_MEMBER(session_stu, DWORD, app_id)>
  48. >
  49. > session_set;
  50. #define MULTI_MEMBER_CON(Tag) boost::multi_index::index<session_set,Tag>::type&
  51. #define MULTI_MEMBER_ITR(Tag) boost::multi_index::index<session_set,Tag>::type::iterator
  52. struct is_business_type {
  53. is_business_type(WORD type)
  54. :m_type(type)
  55. {
  56. }
  57. bool operator()(const session_stu& s)
  58. {
  59. return (s.business_type == m_type);
  60. }
  61. WORD m_type;
  62. };
  63. class session_manager
  64. {
  65. public:
  66. typedef boost::shared_lock<boost::shared_mutex> readLock;
  67. typedef boost:: unique_lock<boost::shared_mutex> writeLock;
  68. session_manager(boost::asio::io_service& io_srv, int type, int expires_time);
  69. ~session_manager();
  70. void add_session(socket_session_ptr p);
  71. void update_session(socket_session_ptr p);
  72. template<typename Tag, typename Member>
  73. void del_session(Member m)
  74. {
  75. writeLock lock(m_mutex);
  76. if (m_sessions.empty())
  77. {
  78. return ;
  79. }
  80. MULTI_MEMBER_CON(Tag) idx = boost::multi_index::get<Tag>(m_sessions);
  81. //BOOST_AUTO(idx, boost::multi_index::get<Tag>(m_sessions));
  82. BOOST_AUTO(iter, idx.find(m));
  83. if (iter != idx.end())
  84. {
  85. idx.erase(iter);
  86. }
  87. }
  88. //获取容器中的第一个session
  89. template<typename Tag, typename Member>
  90. socket_session_ptr get_session(Member m)
  91. {
  92. readLock lock(m_mutex);
  93. if (m_sessions.empty())
  94. {
  95. return socket_session_ptr();
  96. }
  97. MULTI_MEMBER_CON(Tag) idx = boost::multi_index::get<Tag>(m_sessions);
  98. BOOST_AUTO(iter, idx.find(m));
  99. return iter != boost::end(idx) ? iter->session : socket_session_ptr();
  100. }
  101. //随机获取容器中的session
  102. template<typename Tag>
  103. socket_session_ptr get_session_by_business_type(WORD m)
  104. {
  105. typedef filter_container<is_business_type, MULTI_MEMBER_ITR(Tag)> FilterContainer;
  106. readLock lock(m_mutex);
  107. if (m_sessions.empty())
  108. {
  109. return socket_session_ptr();
  110. }
  111. MULTI_MEMBER_CON(Tag) idx = boost::multi_index::get<Tag>(m_sessions);
  112. //对容器的元素条件过滤
  113. is_business_type predicate(m);
  114. FilterContainer fc(predicate, idx.begin(), idx.end());
  115. FilterContainer::FilterIter iter = fc.begin();
  116. if (fc.begin() == fc.end())
  117. {
  118. return socket_session_ptr();
  119. }
  120. //typedef boost::variate_generator<boost::mt19937, boost::uniform_smallint<>> var_gen;
  121. //typedef boost::details::pool::singleton_default<var_gen_wraper<var_gen>> s_var_gen;
  122. ////根据随机数产生session
  123. //s_var_gen::object_type &gen = s_var_gen::instance();
  124. //int step = gen() % fc.szie();
  125. int step = m_next_session % fc.szie();
  126. ++m_next_session;
  127. for (int i = 0; i < step; ++i)
  128. {
  129. iter++;
  130. }
  131. return iter != fc.end() ? iter->session : socket_session_ptr();
  132. }
  133. //根据类型和地址取session
  134. template<typename Tag>
  135. socket_session_ptr get_session_by_type_ip(WORD m, std::string& ip)
  136. {
  137. typedef filter_container<is_business_type, MULTI_MEMBER_ITR(Tag)> FilterContainer;
  138. readLock lock(m_mutex);
  139. if (m_sessions.empty())
  140. {
  141. return socket_session_ptr();
  142. }
  143. MULTI_MEMBER_CON(Tag) idx = boost::multi_index::get<Tag>(m_sessions);
  144. //对容器的元素条件过滤
  145. is_business_type predicate(m);
  146. FilterContainer fc(predicate, idx.begin(), idx.end());
  147. FilterContainer::FilterIter iter = fc.begin();
  148. if (fc.begin() == fc.end())
  149. {
  150. return socket_session_ptr();
  151. }
  152. while (iter != fc.end())
  153. {
  154. if (iter->session->get_remote_addr().find(ip) != std::string::npos)
  155. {
  156. break;
  157. }
  158. iter++;
  159. }
  160. return iter != fc.end() ? iter->session : socket_session_ptr();
  161. }
  162. //根据类型和app_id取session
  163. template<typename Tag>
  164. socket_session_ptr get_session_by_type_appid(WORD m, DWORD app_id)
  165. {
  166. typedef filter_container<is_business_type, MULTI_MEMBER_ITR(Tag)> FilterContainer;
  167. readLock lock(m_mutex);
  168. if (m_sessions.empty())
  169. {
  170. return socket_session_ptr();
  171. }
  172. MULTI_MEMBER_CON(Tag) idx = boost::multi_index::get<Tag>(m_sessions);
  173. //对容器的元素条件过滤
  174. is_business_type predicate(m);
  175. FilterContainer fc(predicate, idx.begin(), idx.end());
  176. FilterContainer::FilterIter iter = fc.begin();
  177. if (fc.begin() == fc.end())
  178. {
  179. return socket_session_ptr();
  180. }
  181. while (iter != fc.end())
  182. {
  183. if (iter->session->get_app_id() == app_id)
  184. {
  185. break;
  186. }
  187. iter++;
  188. }
  189. return iter != fc.end() ? iter->session : socket_session_ptr();
  190. }
  191. private:
  192. int m_type;
  193. int m_expires_time;
  194. boost::asio::io_service& m_io_srv;
  195. boost::asio::deadline_timer m_check_tick;
  196. boost::shared_mutex m_mutex;
  197. unsigned short m_next_session;
  198. session_set m_sessions;
  199. void check_connection();
  200. };
  201. }

这里主要用到了boost的multi_index容器,这是一个非常有用方便的容器,可实现容器的多列索引,具体的使用方法,在这里不多做详解。

//session_manager.cpp

  1. #include "session_manager.h"
  2. namespace firebird{
  3. session_manager::session_manager(boost::asio::io_service& io_srv, int type, int expires_time)
  4. :m_io_srv(io_srv), m_check_tick(io_srv), m_type(type), m_expires_time(expires_time),m_next_session(0)
  5. {
  6. check_connection();
  7. }
  8. session_manager::~session_manager()
  9. {
  10. }
  11. //检查服务器所有session的连接状态
  12. void session_manager::check_connection()
  13. {
  14. try{
  15. writeLock lock(m_mutex);
  16. session_set::iterator iter = m_sessions.begin();
  17. while (iter != m_sessions.end())
  18. {
  19. LOG4CXX_DEBUG(firebird_log, "循环");
  20. if (CLIENT == m_type)//客户端的方式
  21. {
  22. if (!iter->session->socket().is_open())//已断开,删除已断开的连接
  23. {
  24. LOG4CXX_INFO(firebird_log, "重新连接[" << iter->address << "]");
  25. iter->session->close(); //通过关闭触发客户端重连
  26. }
  27. else{//连接中,发送心跳
  28. message msg;
  29. msg.command = heartbeat;
  30. msg.business_type = iter->session->get_business_type();
  31. msg.app_id = iter->session->get_app_id();
  32. msg.data() = "H";
  33. iter->session->async_write(msg);
  34. iter->session->set_op_time();
  35. }
  36. }
  37. else if (SERVER == m_type)//服务器的方式
  38. {
  39. if (!iter->session->socket().is_open())//已断开,删除已断开的连接
  40. {
  41. LOG4CXX_INFO(firebird_log, KDS_CODE_INFO << "删除已关闭的session:[" << iter->session->get_remote_addr() << "]");
  42. iter = m_sessions.erase(iter);
  43. continue;
  44. }
  45. else{//连接中,设定每30秒检查一次
  46. if (iter->session->is_timeout()) //如果session已长时间没操作,则关闭
  47. {
  48. LOG4CXX_INFO(firebird_log, KDS_CODE_INFO << "删除已超时的session:[" << iter->session->get_remote_addr() << "]");
  49. iter->session->close();//通过关闭触发删除session
  50. }
  51. }
  52. iter->session->set_op_time();
  53. }
  54. else{
  55. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown manager_type");
  56. }
  57. ++iter;
  58. }
  59. LOG4CXX_DEBUG(firebird_log, "定时检查");
  60. m_check_tick.expires_from_now(boost::posix_time::seconds(m_expires_time));
  61. m_check_tick.async_wait(boost::bind(&session_manager::check_connection, this));
  62. }
  63. catch(std::exception& e)
  64. {
  65. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "[" << e.what() << "]");
  66. }
  67. catch(...)
  68. {
  69. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
  70. }
  71. }
  72. void session_manager::add_session(socket_session_ptr p)
  73. {
  74. writeLock lock(m_mutex);
  75. session_stu stuSession;
  76. stuSession.id = p->id();
  77. stuSession.business_type = 0;
  78. stuSession.address = p->get_remote_addr();
  79. stuSession.app_id = p->get_app_id();
  80. stuSession.session = p;
  81. m_sessions.insert(stuSession);
  82. }
  83. void session_manager::update_session(socket_session_ptr p)
  84. {
  85. writeLock lock(m_mutex);
  86. if (m_sessions.empty())
  87. {
  88. return ;
  89. }
  90. MULTI_MEMBER_CON(sid) idx = boost::multi_index::get<sid>(m_sessions);
  91. BOOST_AUTO(iter, idx.find(p->id()));
  92. if (iter != idx.end())
  93. {
  94. const_cast<session_stu&>(*iter).business_type = p->get_business_type();
  95. const_cast<session_stu&>(*iter).app_id = p->get_app_id();
  96. }
  97. }
  98. }

这个时候,我就可以使用id、business_type、address、app_id当做key来索引socket_session了,单使用map容器是做不到的。

还有索引时,需要的一个条件过滤器

//filter_container.h

  1. #pragma once
  2. #include <boost/iterator/filter_iterator.hpp>
  3. namespace firebird{
  4. template <class Predicate, class Iterator>
  5. class filter_container
  6. {
  7. public:
  8. typedef boost::filter_iterator<Predicate, Iterator> FilterIter;
  9. filter_container(Predicate p, Iterator begin, Iterator end)
  10. :m_begin(p, begin, end),
  11. m_end(p, end, end)
  12. {
  13. }
  14. ~filter_container() {}
  15. FilterIter begin() { return m_begin; }
  16. FilterIter end()   { return m_end; }
  17. int szie() {
  18. int i = 0;
  19. FilterIter fi = m_begin;
  20. while(fi != m_end)
  21. {
  22. ++i;
  23. ++fi;
  24. }
  25. return i;
  26. }
  27. private:
  28. FilterIter m_begin;
  29. FilterIter m_end;
  30. };
  31. }

4.服务器端的实现

服务器我定义为server_socket_utils,拥有一个session_manager,每当accept成功得到一个socket_session时,都会将其增加到session_manager去管理,注册相关回调函数。

read_data_callback   接收到数据的回调函数

收到数据之后,也就是数据包的body部分,反序列化出command、business_type、app_id和data(我使用到了thrift),如果command==normal正常的业务包,会调用handle_read_data传入data。

close_callback 关闭socket_session触发的回调函数

根据id将该连接从session_manager中删除掉

//server_socket_utils.h

  1. #pragma once
  2. #include "socket_session.h"
  3. #include "session_manager.h"
  4. #include <boost/format.hpp>
  5. #include <firebird/message/message.hpp>
  6. namespace firebird{
  7. using boost::asio::ip::tcp;
  8. class FIREBIRD_DECL server_socket_utils
  9. {
  10. private:
  11. boost::asio::io_service m_io_srv;
  12. boost::asio::io_service::work m_work;
  13. tcp::acceptor m_acceptor;
  14. void handle_accept(socket_session_ptr session, const boost::system::error_code& error);
  15. void close_callback(socket_session_ptr session);
  16. void read_data_callback(const boost::system::error_code& e,
  17. socket_session_ptr session, message& msg);
  18. protected:
  19. virtual void handle_read_data(message& msg, socket_session_ptr pSession) = 0;
  20. public:
  21. server_socket_utils(int port);
  22. ~server_socket_utils(void);
  23. void start();
  24. boost::asio::io_service& get_io_service() { return m_io_srv; }
  25. session_manager m_manager;
  26. };
  27. }

//server_socket_utils.cpp

  1. #include "server_socket_utils.h"
  2. namespace firebird{
  3. server_socket_utils::server_socket_utils(int port)
  4. :m_work(m_io_srv),
  5. m_acceptor(m_io_srv, tcp::endpoint(tcp::v4(), port)),
  6. m_manager(m_io_srv, SERVER, 3)
  7. {
  8. //m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
  9. //// 关闭连接前留0秒给客户接收数据
  10. //m_acceptor.set_option(boost::asio::ip::tcp::acceptor::linger(true, 0));
  11. //m_acceptor.set_option(boost::asio::ip::tcp::no_delay(true));
  12. //m_acceptor.set_option(boost::asio::socket_base::keep_alive(true));
  13. //m_acceptor.set_option(boost::asio::socket_base::receive_buffer_size(16384));
  14. }
  15. server_socket_utils::~server_socket_utils(void)
  16. {
  17. }
  18. void server_socket_utils::start()
  19. {
  20. try{
  21. socket_session_ptr new_session(new socket_session(m_io_srv));
  22. m_acceptor.async_accept(new_session->socket(),
  23. boost::bind(&server_socket_utils::handle_accept, this, new_session,
  24. boost::asio::placeholders::error));
  25. }
  26. catch(std::exception& e)
  27. {
  28. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[" << e.what() << "]");
  29. }
  30. catch(...)
  31. {
  32. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[未知异常]");
  33. }
  34. }
  35. void server_socket_utils::handle_accept(socket_session_ptr session, const boost::system::error_code& error)
  36. {
  37. if (!error)
  38. {
  39. try{
  40. socket_session_ptr new_session(new socket_session(m_io_srv));
  41. m_acceptor.async_accept(new_session->socket(),
  42. boost::bind(&server_socket_utils::handle_accept, this, new_session,
  43. boost::asio::placeholders::error));
  44. if (session != NULL)
  45. {
  46. //注册关闭回调函数
  47. session->installCloseCallBack(boost::bind(&server_socket_utils::close_callback, this, _1));
  48. //注册读到数据回调函数
  49. session->installReadDataCallBack(boost::bind(&server_socket_utils::read_data_callback, this, _1, _2, _3));
  50. boost::format fmt("%1%:%2%");
  51. fmt % session->socket().remote_endpoint().address().to_string();
  52. fmt % session->socket().remote_endpoint().port();
  53. session->set_remote_addr(fmt.str());
  54. session->start();
  55. m_manager.add_session(session);
  56. }
  57. }
  58. catch(std::exception& e)
  59. {
  60. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[" << e.what() << "]");
  61. }
  62. catch(...)
  63. {
  64. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[未知异常]");
  65. }
  66. }
  67. }
  68. void server_socket_utils::close_callback(socket_session_ptr session)
  69. {
  70. LOG4CXX_DEBUG(firebird_log, "close_callback");
  71. try{
  72. m_manager.del_session<sid>(session->id());
  73. }
  74. catch(std::exception& e)
  75. {
  76. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[" << e.what() << "]");
  77. }
  78. catch(...)
  79. {
  80. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[未知异常]");
  81. }
  82. }
  83. void server_socket_utils::read_data_callback(const boost::system::error_code& e,
  84. socket_session_ptr session, message& msg)
  85. {
  86. try{
  87. LOG4CXX_DEBUG(firebird_log, "command =[" << msg.command << "],["
  88. << msg.business_type << "],[" << msg.data() << "]");
  89. if (msg.command == heartbeat)
  90. {//心跳
  91. session->async_write(msg);
  92. }
  93. else if (msg.command == regist)
  94. {//注册
  95. session->set_business_type(msg.business_type);
  96. session->set_app_id(msg.app_id);
  97. m_manager.update_session(session);
  98. session->async_write(msg);
  99. LOG4CXX_FATAL(firebird_log, "远程地址:[" << session->get_remote_addr() << "],服务器类型:[" <<
  100. session->get_business_type() << "],服务器ID:[" << session->get_app_id() << "]注册成功!");
  101. }
  102. else if (msg.command == normal)
  103. {//业务数据
  104. handle_read_data(msg, session);
  105. }
  106. else
  107. {
  108. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "收到非法消息包!");
  109. }
  110. }
  111. catch(std::exception& e)
  112. {
  113. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[" << e.what() << "]");
  114. }
  115. catch(...)
  116. {
  117. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "socket异常:[未知异常]");
  118. }
  119. }
  120. }

5.客户端

客户端与服务器的逻辑也差不多,区别就是在于客户端通过connect得到socket_session,而服务器是通过accept得到socket_session。

//client_socket_utils.h

  1. #pragma once
  2. #include "socket_session.h"
  3. #include "session_manager.h"
  4. #include <boost/algorithm/string.hpp>
  5. #include <firebird/message/message.hpp>
  6. namespace firebird{
  7. class FIREBIRD_DECL client_socket_utils
  8. {
  9. public:
  10. client_socket_utils();
  11. ~client_socket_utils();
  12. void session_connect(std::vector<socket_session_ptr>& vSession);
  13. void session_connect(socket_session_ptr pSession);
  14. //socket_session_ptr get_session(std::string& addr);
  15. boost::asio::io_service& get_io_service() { return m_io_srv; }
  16. protected:
  17. virtual void handle_read_data(message& msg, socket_session_ptr pSession) = 0;
  18. private:
  19. boost::asio::io_service m_io_srv;
  20. boost::asio::io_service::work m_work;
  21. session_manager m_manager;
  22. void handle_connect(const boost::system::error_code& error,
  23. tcp::resolver::iterator endpoint_iterator, socket_session_ptr pSession);
  24. void close_callback(socket_session_ptr session);
  25. void read_data_callback(const boost::system::error_code& e,
  26. socket_session_ptr session, message& msg);
  27. };
  28. }

//client_socket_utils.cpp

  1. #include "client_socket_utils.h"
  2. namespace firebird{
  3. client_socket_utils::client_socket_utils()
  4. :m_work(m_io_srv), m_manager(m_io_srv, CLIENT, 3)
  5. {
  6. }
  7. client_socket_utils::~client_socket_utils()
  8. {
  9. }
  10. void client_socket_utils::session_connect(std::vector<socket_session_ptr>& vSession)
  11. {
  12. for (int i = 0; i < vSession.size(); ++i)
  13. {
  14. session_connect(vSession[i]);
  15. }
  16. }
  17. void client_socket_utils::session_connect(socket_session_ptr pSession)
  18. {
  19. std::string& addr = pSession->get_remote_addr();
  20. try{
  21. //注册关闭回调函数
  22. pSession->installCloseCallBack(boost::bind(&client_socket_utils::close_callback, this, _1));
  23. //注册读到数据回调函数
  24. pSession->installReadDataCallBack(boost::bind(&client_socket_utils::read_data_callback, this, _1, _2, _3));
  25. std::vector<std::string> ip_port;
  26. boost::split(ip_port, addr, boost::is_any_of(":"));
  27. if (ip_port.size() < 2)
  28. {
  29. //throw std::runtime_error("ip 格式不正确!");
  30. LOG4CXX_ERROR(firebird_log, "[" << addr << "] ip 格式不正确!");
  31. return;
  32. }
  33. tcp::resolver resolver(pSession->socket().get_io_service());
  34. tcp::resolver::query query(ip_port[0], ip_port[1]);
  35. tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
  36. //pSession->set_begin_endpoint(endpoint_iterator);//设置起始地址,以便重连
  37. //由于客户端是不断重连的,即使还未连接也要保存该session
  38. m_manager.add_session(pSession);
  39. tcp::endpoint endpoint = *endpoint_iterator;
  40. pSession->socket().async_connect(endpoint,
  41. boost::bind(&client_socket_utils::handle_connect, this,
  42. boost::asio::placeholders::error, ++endpoint_iterator, pSession));
  43. }
  44. catch(std::exception& e)
  45. {
  46. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << addr << "],socket异常:[" << e.what() << "]");
  47. }
  48. catch(...)
  49. {
  50. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << addr << "],socket异常:[未知异常]");
  51. }
  52. }
  53. void client_socket_utils::handle_connect(const boost::system::error_code& error,
  54. tcp::resolver::iterator endpoint_iterator, socket_session_ptr pSession)
  55. {
  56. LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << " enter.");
  57. std::string sLog;
  58. try{
  59. if (!error)
  60. {
  61. LOG4CXX_FATAL(firebird_log, "服务器:[" << pSession->get_business_type() <<"],连接远程地址:[" << pSession->get_remote_addr().c_str() << "]成功!");
  62. pSession->start();
  63. //向服务器注册服务类型
  64. message msg;
  65. msg.command = regist;
  66. msg.business_type = pSession->get_business_type();
  67. msg.app_id = pSession->get_app_id();
  68. msg.data() = "R";
  69. pSession->async_write(msg);
  70. }
  71. else if (endpoint_iterator != tcp::resolver::iterator())
  72. {
  73. LOG4CXX_ERROR(firebird_log, "连接远程地址:[" << pSession->get_remote_addr().c_str() << "]失败,试图重连下一个地址。");
  74. pSession->socket().close();//此处用socket的close,不应用session的close触发连接,不然会导致一直重连
  75. tcp::endpoint endpoint = *endpoint_iterator;
  76. pSession->socket().async_connect(endpoint,
  77. boost::bind(&client_socket_utils::handle_connect, this,
  78. boost::asio::placeholders::error, ++endpoint_iterator, pSession));
  79. }
  80. else
  81. {
  82. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << pSession->get_remote_addr().c_str() << "]失败!");
  83. pSession->socket().close();//此处用socket的close,不应用session的close触发连接,不然会导致一直重连
  84. }
  85. }
  86. catch(std::exception& e)
  87. {
  88. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << pSession->get_remote_addr().c_str() <<"],socket异常:[" << e.what() << "]");
  89. }
  90. catch(...)
  91. {
  92. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << pSession->get_remote_addr().c_str() <<"],socket异常:[未知异常]");
  93. }
  94. }
  95. void client_socket_utils::read_data_callback(const boost::system::error_code& e,
  96. socket_session_ptr session, message& msg)
  97. {
  98. LOG4CXX_DEBUG(firebird_log, "command =[" << msg.command << "],["
  99. << msg.business_type << "],[" << msg.data() << "]");
  100. if (msg.command == heartbeat)
  101. {//心跳
  102. }
  103. else if (msg.command == regist)
  104. {//注册
  105. LOG4CXX_FATAL(firebird_log, "服务器:[" << session->get_business_type() <<"]注册成功。");
  106. }
  107. else if (msg.command == normal)
  108. {//业务数据
  109. handle_read_data(msg, session);
  110. }
  111. else
  112. {
  113. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "收到非法消息包!");
  114. }
  115. }
  116. //关闭session就会重连
  117. void client_socket_utils::close_callback(socket_session_ptr session)
  118. {
  119. LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << "enter.");
  120. try{
  121. //tcp::resolver::iterator endpoint_iterator = context.session->get_begin_endpoint();
  122. std::string& addr = session->get_remote_addr();
  123. std::vector<std::string> ip_port;
  124. boost::split(ip_port, addr, boost::is_any_of(":"));
  125. if (ip_port.size() < 2)
  126. {
  127. LOG4CXX_ERROR(firebird_log, "[" << addr << "] ip 格式不正确!");
  128. return;
  129. }
  130. tcp::resolver resolver(session->socket().get_io_service());
  131. tcp::resolver::query query(ip_port[0], ip_port[1]);
  132. tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
  133. tcp::endpoint endpoint = *endpoint_iterator;
  134. session->socket().async_connect(endpoint,
  135. boost::bind(&client_socket_utils::handle_connect, this,
  136. boost::asio::placeholders::error, ++endpoint_iterator, session));
  137. }
  138. catch(std::exception& e)
  139. {
  140. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << session->get_remote_addr().c_str() <<"],socket异常:[" << e.what() << "]");
  141. }
  142. catch(...)
  143. {
  144. LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "连接远程地址:[" << session->get_remote_addr().c_str() <<"],socket异常:[未知异常]");
  145. }
  146. }
  147. }

5.对象串行化

socket_session发送和接收数据包的时候使用到了对象串行化,我这里是通过thrift实现的,其实boost的serialization库也提供了这样的功能,使用起来更为方便,但我在测试过程中,thrift相比之下性能会高很多,因此就坚持使用thrift了,感兴趣的话可以看我之前写的使用thrift串行化对象轻量级序列化库boost serialization》

5.1字符串与thrift对象的相互转换

  1. #pragma once
  2. #include <boost/shared_ptr.hpp>
  3. #include <transport/TBufferTransports.h>
  4. #include <protocol/TProtocol.h>
  5. #include <protocol/TBinaryProtocol.h>
  6. namespace firebird{
  7. using namespace apache::thrift;
  8. using namespace apache::thrift::transport;
  9. using namespace apache::thrift::protocol;
  10. template<typename T>
  11. void thrift_iserialize(T& stu, std::string& s)
  12. {
  13. boost::shared_ptr<TMemoryBuffer> trans(new TMemoryBuffer((uint8_t*)&s[0], s.size()));
  14. boost::shared_ptr<TProtocol> proto(new TBinaryProtocol(trans));
  15. stu.read(proto.get());
  16. }
  17. template<typename T>
  18. void thrift_oserialize(T& stu, std::string& s)
  19. {
  20. boost::shared_ptr<TMemoryBuffer> trans(new TMemoryBuffer());
  21. boost::shared_ptr<TProtocol> proto(new TBinaryProtocol(trans));
  22. stu.write(proto.get());
  23. s = trans->getBufferAsString();
  24. }
  25. }

5.2通过thrift对象,普通的对象与字符串的相互转换

  1. #pragma once
  2. #include "message_archive.hpp"
  3. #include <firebird/archive/thrift_archive.hpp>
  4. #include <firebird/message/TMessage_types.h>
  5. namespace firebird
  6. {
  7. /*** message to ThriftMessage ***/
  8. void msg_to_tmsg(TMessage& tmsg, message& msg)
  9. {
  10. //设置
  11. tmsg.command = msg.command;
  12. tmsg.business_type = msg.business_type;
  13. tmsg.app_id = msg.app_id;
  14. //设置context
  15. tmsg.context.cmdVersion = msg.context().cmdVersion;
  16. tmsg.context.cpid.swap(msg.context().cpid);
  17. tmsg.context.remote_ip.swap(msg.context().remote_ip);
  18. tmsg.context.wSerialNumber = msg.context().wSerialNumber;
  19. tmsg.context.session_id = msg.context().session_id;
  20. //设置source
  21. for (int i = 0; i < msg.source().size(); ++i)
  22. {
  23. tmsg.source.push_back(msg.source()[i]);
  24. }
  25. //设置destination
  26. for (int i = 0; i < msg.destination().size(); ++i)
  27. {
  28. tmsg.destination.push_back(msg.destination()[i]);
  29. }
  30. //设置data
  31. tmsg.data = msg.data();
  32. }
  33. /*** ThriftMessage to message ***/
  34. void tmsg_to_msg(message& msg, TMessage& tmsg)
  35. {
  36. //设置
  37. msg.command = tmsg.command;
  38. msg.business_type = tmsg.business_type;
  39. msg.app_id = tmsg.app_id;
  40. //设置context
  41. msg.context().cmdVersion = tmsg.context.cmdVersion;
  42. msg.context().cpid = tmsg.context.cpid;
  43. msg.context().remote_ip = tmsg.context.remote_ip;
  44. msg.context().wSerialNumber = tmsg.context.wSerialNumber;
  45. msg.context().session_id = tmsg.context.session_id;
  46. //设置source
  47. for (int i = 0; i < tmsg.source.size(); ++i)
  48. {
  49. msg.source() << tmsg.source[i];
  50. }
  51. //设置destination
  52. for (int i = 0; i < tmsg.destination.size(); ++i)
  53. {
  54. msg.destination() << tmsg.destination[i];
  55. }
  56. //设置data
  57. msg.data() = tmsg.data;
  58. }
  59. void message_iarchive(message& msg, std::string& s)
  60. {
  61. TMessage tmsg;
  62. thrift_iserialize(tmsg, s);
  63. tmsg_to_msg(msg, tmsg);
  64. }
  65. void message_oarchive(std::string& s, message& msg)
  66. {
  67. TMessage tmsg;
  68. msg_to_tmsg(tmsg, msg);
  69. thrift_oserialize(tmsg, s);
  70. }
  71. }

---恢复内容结束---

boost asio 异步实现tcp通讯的更多相关文章

  1. boost asio异步读写网络聊天程序client 实例具体解释

    boost官方文档中聊天程序实例解说 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...

  2. boost asio异步读写网络聊天程序客户端 实例详解

    boost官方文档中聊天程序实例讲解 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...

  3. 使用Boost asio实现异步的TCP/IP通信

    可以先了解一下Boost asio基本概念,以下是Boost asio实现的异步TCP/IP通信: 服务器: #include "stdafx.h" #include <io ...

  4. 使用 boost.asio 简单实现 异步Socket 通信

     客户端: class IPCClient { public: IPCClient(); ~IPCClient(); bool run(); private: bool connect(); bool ...

  5. boost::asio::ip::tcp实现网络通信的小例子

    同步方式: Boost.Asio是一个跨平台的网络及底层IO的C++编程库,它使用现代C++手法实现了统一的异步调用模型. 头文件 #include <boost/asio.hpp> 名空 ...

  6. BOOST.Asio——Tutorial

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  啥说的,鄙视那些无视版权随 ...

  7. boost asio 学习(九) boost::asio 网络封装

    http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting- started-with-boostasio?pg=10 9. A ...

  8. libgo协程库:网络性能完爆ASIO异步模型(-O3测试)

    在purecpp社区的github组织中有一个协程库:https://github.com/yyzybb537/libgo 近日有用户找到我,想要了解一下libgo库在网络方面的性能,于是选取已入选标 ...

  9. Boost.Asio c++ 网络编程翻译(14)

    保持活动 假如,你须要做以下的操作: io_service service; ip::tcp::socket sock(service); char buff[512]; ... read(sock, ...

随机推荐

  1. Chapter 12 外观模式

    外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个模式使得这一子系统更加容易使用. 外观模式三个阶段: 首先,在设计初期阶段,应该要有意识的将不同的两个层分离. 其次,在 ...

  2. 开发板ip设置

    vi /etc/init.d/rcS 在其中加入 ifconfig eth0 192.168.1.10 netmask 255.255.255.0 up 就可以了

  3. Sitemesh3的使用及配置

    1 . Sitemesh 3 简介 Sitemesh 是一个网页布局和修饰的框架,基于 Servlet 中的 Filter,类似于 ASP.NET 中的‘母版页’技术.参考:百度百科,相关类似技术:A ...

  4. c++ 重载,覆盖,重定义 2

    前一篇 http://www.cnblogs.com/iois/p/4085173.html 写有些地方不够准确,重新整理了一遍 函数重载(Function Overloading) C++允许同一范 ...

  5. ARM标准汇编与GNU汇编

    ARM标准汇编与GNU汇编 http://www.cnblogs.com/hnrainll/archive/2011/05/17/2048315.html

  6. BZOJ 3036: 绿豆蛙的归宿( 期望dp )

    从终点往起点倒推 . 在一个图 考虑点 u , 出度为 s : s = 0 , d[ u ] = 0 ; s ≠ 0 , 则 d( u ) = ( ∑ d( v ) ) / s ( ( u , v ) ...

  7. WCF技术剖析之二十五: 元数据(Metadata)架构体系全景展现[元数据描述篇]

    原文:WCF技术剖析之二十五: 元数据(Metadata)架构体系全景展现[元数据描述篇] 在[WS标准篇]中我花了很大的篇幅介绍了WS-MEX以及与它相关的WS规范:WS-Policy.WS-Tra ...

  8. 动态网页爬取例子(WebCollector+selenium+phantomjs)

    目标:动态网页爬取 说明:这里的动态网页指几种可能:1)需要用户交互,如常见的登录操作:2)网页通过JS / AJAX动态生成,如一个html里有<div id="test" ...

  9. ARM相关知识

    ARM7采用冯·诺依曼(Von-Neumann)结构,数据存储器和程序存储器重合在一起.    同时,此结构也被大多数计算机所采用. ARM7为三级流水线结构(取指,译码,执行),平均功耗为0.6mW ...

  10. 关于caffe-windows中 compute_image_mean.exe出现的问题

    这两天有兴致装了下caffe.感受下这个框架. 可是在这个过程中遇到非常多问题.我把碰到的问题和解决方式写下,便于后人高速上手. compute_image_mean.exe 编译出来后.运行数据变换 ...