boost asio 异步实现tcp通讯

一、前言

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

#pragma once
#include <iostream>
#include <list>
#include <hash_map>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <firebird/log/logger_log4.hpp>
#include <firebird/detail/config.hpp>

using boost::asio::ip::tcp;

namespace firebird{
	enum command{ heartbeat = 0, regist, normal};

	const std::string tag = "KDS";//目的用于检查收到的数据包是否是有效的
	struct session_message 
	{
		boost::array<char, 7> sHeader;//tag占3个字节+4字节的数据包长度
		std::string sBody;
	};

	class FIREBIRD_DECL socket_session;
	typedef boost::shared_ptr<socket_session> socket_session_ptr;

	class session_context
	{
	public:
		socket_session_ptr session;
		std::string msg;
	};
	typedef boost::function<void(session_context&)> handler_func;

	class FIREBIRD_DECL socket_session:
		public boost::enable_shared_from_this<socket_session>,
		private boost::noncopyable
	{
	public:
		socket_session(boost::asio::io_service& io_service);
		~socket_session(void);

		enum state{ OPEN, CLOSE, READ_DATA, WRITE_DATA};

		DWORD id() { return m_id; }
		WORD get_business_type(){ return m_business_type; }
		void set_business_type(WORD type) { m_business_type = type; }
		DWORD get_app_id(){ return m_app_id; }
		void set_app_id(DWORD app_id) { m_app_id = app_id; }
		std::string& get_remote_addr() { return m_name; }
		void set_remote_addr(std::string& name) { m_name = name; }
		tcp::socket& socket() { return m_socket; }

		void start();
		void close();
		void async_write(command c, const std::string& sMsg);

		bool is_timeout();

		//注册回调函数
		void install_handler(int stated, handler_func call_back);
		//调用回调函数
		void call_handler(int stated, std::string msg);

	private:
		static boost::detail::atomic_count m_last_id;

		DWORD m_id;
		WORD  m_business_type;
		DWORD m_app_id;
		std::string m_name;
		session_message m_msg;
		stdext::hash_map<int, handler_func> m_hmHandler;

		tcp::socket m_socket;
		boost::asio::io_service& m_io_service;
		boost::asio::strand m_strand;

		std::time_t m_last_op_time;

		void async_write(const std::string& sMsg);

		//读消息头
		void handle_read_header(const boost::system::error_code& error);
		//读消息体
		void handle_read_body(const boost::system::error_code& error);
		//发送消息
		void handle_write(std::string& sMsg);
		//关闭session
		void handle_close();
	};
}

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

//socket_session.cpp

#include "socket_session.h"
#include "command_msg_types.h"
#include <firebird/archive/thrift_archive.hpp>

namespace firebird{
	boost::detail::atomic_count socket_session::m_last_id(0);

	socket_session::socket_session(boost::asio::io_service& io_srv)
		:m_io_service(io_srv), m_strand(io_srv), m_socket(io_srv), 
		m_business_type(0), m_app_id(0)
	{
		m_id = ++socket_session::m_last_id;
	}

	socket_session::~socket_session(void)
	{
		m_socket.close();
	}

	void socket_session::start()
	{
		try{
			const boost::system::error_code error;
			handle_read_header(error);
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	void socket_session::handle_close()
	{
		try{
			m_socket.close();
			call_handler(CLOSE, std::string("close"));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	//关闭回调函数在另一个线程操作,以免同一个线程下lock多次,造成死锁。
	void socket_session::close()
	{
		m_io_service.post(boost::bind(&socket_session::handle_close, shared_from_this()));
	}

	static int connection_timeout = 60;

	bool socket_session::is_timeout()
	{
		std::time_t now;
		std::time(&now);	
		return now - m_last_op_time > connection_timeout;
	}
	
	//读消息头
	void socket_session::handle_read_header(const boost::system::error_code& error)
	{
		LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO  << "enter.");

		try{
			if(error)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO  << get_remote_addr() << "[" << error.message().c_str() << "]");
				close();
				return;
			}

			std::time(&m_last_op_time);

			if (m_msg.sBody.length() > 0 && m_msg.sBody != "")
			{//读到数据回调注册的READ_DATA函数
				m_io_service.post(boost::bind(&socket_session::call_handler, shared_from_this(), READ_DATA, m_msg.sBody));
			}

			boost::asio::async_read(m_socket, 
				boost::asio::buffer(m_msg.sHeader),
				boost::bind(&socket_session::handle_read_body, shared_from_this(),
				boost::asio::placeholders::error));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	//读消息体
	void socket_session::handle_read_body(const boost::system::error_code& error)
	{
		LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << "enter.");

		try{
			if(error)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "[" << error.message().c_str() << "]");
				close();
				return;
			}

			if (tag.compare(0, tag.length(), m_msg.sHeader.data(), 0, tag.length()))
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO <<  get_remote_addr() << ",this is a invalid connection!");
				close();
				return;
			}

			DWORD dwLength = 0;

			char* len = (char*)&dwLength;
			memcpy(len, &m_msg.sHeader[tag.length()], sizeof(dwLength));

			m_msg.sBody.resize(dwLength);
			char* pBody = &m_msg.sBody[0];

			std::time(&m_last_op_time);

			boost::asio::async_read(m_socket, 
				boost::asio::buffer(pBody, dwLength),
				boost::bind(&socket_session::handle_read_header, shared_from_this(),
				boost::asio::placeholders::error));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	void socket_session::handle_write(std::string& sMsg)
	{
		try{
			//为了避免多线程同时write,导致数据包错乱,因此使用同步机制发送数据包
			boost::asio::write(m_socket, boost::asio::buffer(sMsg, sMsg.length()));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	void socket_session::async_write(const std::string& sMsg)
	{
		LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO  << "enter.")

		try
		{
			std::time(&m_last_op_time);

			DWORD dwLength = sMsg.length();
			char* pLen = (char*)&dwLength;

			std::string msg;
			msg.append(tag);
			msg.append(pLen, sizeof(dwLength));
			msg.append(sMsg);

			//由于多线程同时write,会导致数据包错乱,使用strand解决线程同步问题
			m_strand.post(boost::bind(&socket_session::handle_write, shared_from_this(), msg));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
			close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
			close();
		}
	}

	void socket_session::async_write(command c, const std::string& sMsg)
	{
		command_msg cmsg;

		switch (c)
		{
		case heartbeat:
			break;
		case regist:
			break;
		case normal:
			break;
		}

		cmsg.command = c;
		cmsg.business_type = m_business_type;
		cmsg.app_id = m_app_id;
		cmsg.data = sMsg;

		std::string temp_msg;
		firebird::thrift_oserialize(cmsg, temp_msg);

		async_write(temp_msg);
	}

	//注册回调函数
	void socket_session::install_handler(int stated, handler_func call_back)
	{
		try{
			m_hmHandler[stated] = call_back;
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
		}

	}

	//调用回调函数
	void socket_session::call_handler(int stated, std::string msg)
	{
		try{
			stdext::hash_map<int, handler_func>::iterator it = m_hmHandler.find(stated);
			if (it != boost::end(m_hmHandler))
			{
				session_context sc;
				sc.session = shared_from_this();
				sc.msg.swap(msg);
				m_hmHandler[stated](sc);
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << get_remote_addr() << "unknown exception.");
		}

	}
}

接受数据时,socket_session会先读取7个字节的head,比较前3个字节“KDS”,然后取得4个字节的Length,然后再读出Length长度的数据,将该数据传给外部注册为READ_DATA的回调函数,外部的回调函数都是通过socket_session的install_handler(int stated, handler_func call_back);注册的。


还有一点是,socket_session收数据是通过asio的异步接口async_read调用的,而发数据是通过同步接口write调用的,这是为了保证发的数据包是有序,为了能够达到非阻塞效果,提高性能,这里使用到了asio的strand,可保证高并发下write有序的调用,也不会阻塞调write的线程。


3.连接管理器

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

//session_manager.h

#pragma once
#include "socket_session.h"
#include "filter_container.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/random.hpp>
#include <boost/pool/detail/singleton.hpp>

namespace firebird{
	template<typename T>
	class var_gen_wraper
	{
	public:
		var_gen_wraper(): gen(boost::mt19937((boost::int32_t)std::time(0)), 
			boost::uniform_smallint<>(1, 100)) {}
		typename T::result_type operator() () { return gen(); }
	private:
		T gen;
	};

	struct  session_stu
	{
		DWORD	id;
		WORD	business_type;
		std::string address;
		DWORD	app_id;
		socket_session_ptr session;
	};

	struct sid{};
	struct sbusiness_type{};
	struct saddress{};
	struct sapp_id{};

	enum session_idx_member{ session_id = 0, session_business_type, session_address, app_id};
#define CLIENT 0
#define SERVER 1

	typedef boost::multi_index::multi_index_container<
		session_stu, 
		boost::multi_index::indexed_by<
		boost::multi_index::ordered_unique<
		boost::multi_index::tag<sid>, BOOST_MULTI_INDEX_MEMBER(session_stu, DWORD, id)>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<sbusiness_type>, BOOST_MULTI_INDEX_MEMBER(session_stu, WORD, business_type)>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<saddress>, BOOST_MULTI_INDEX_MEMBER(session_stu, std::string, address)>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<sapp_id>, BOOST_MULTI_INDEX_MEMBER(session_stu, DWORD, app_id)>
		>
	> session_set;

#define MULTI_MEMBER_CON(MultiIndexContainer, Tag) boost::multi_index::index<MultiIndexContainer,Tag>::type&
#define MULTI_MEMBER_ITR(MultiIndexContainer, Tag) boost::multi_index::index<MultiIndexContainer,Tag>::type::iterator

	struct is_business_type {
		is_business_type(WORD type)
			:m_type(type)
		{

		}
		bool operator()(const session_stu& s) 
		{
			return (s.business_type == m_type);
		}

		WORD m_type;
	};

	class session_manager
	{
	public:
		session_manager(boost::asio::io_service& io_srv, int type, int expires_time);
		~session_manager();

		void add_session(socket_session_ptr p);
		void update_session(socket_session_ptr p);

		template<typename MultiIndexContainer, typename Tag, typename Member>
		void del_session(Member m)
		{
			try{
				boost::mutex::scoped_lock lock(m_mutex);
				if (m_sessions.empty())
				{
					return ;
				}

				MULTI_MEMBER_CON(MultiIndexContainer, Tag) idx = boost::multi_index::get<Tag>(m_sessions);
				//BOOST_AUTO(idx, boost::multi_index::get<Tag>(m_sessions));
				BOOST_AUTO(iter, idx.find(m));

				if (iter != idx.end())
				{
					idx.erase(iter);
				}
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}

		}

		//获取容器中的第一个session
		template<typename MultiIndexContainer, typename Tag, typename Member>
		socket_session_ptr get_session(Member m)
		{
			try{
				boost::mutex::scoped_lock lock(m_mutex);

				if (m_sessions.empty())
				{
					return socket_session_ptr();
				}

				MULTI_MEMBER_CON(MultiIndexContainer, Tag) idx = boost::multi_index::get<Tag>(m_sessions);
				BOOST_AUTO(iter, idx.find(m));
				return iter != boost::end(idx) ? iter->session : socket_session_ptr();
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}
			return socket_session_ptr();
		}

		//随机获取容器中的session
		template<typename MultiIndexContainer, typename Tag>
		socket_session_ptr get_session_by_business_type(WORD m)
		{
			typedef filter_container<is_business_type, MULTI_MEMBER_ITR(MultiIndexContainer, Tag)> FilterContainer;
			try{
				boost::mutex::scoped_lock lock(m_mutex);

				if (m_sessions.empty())
				{
					return socket_session_ptr();
				}

				MULTI_MEMBER_CON(MultiIndexContainer, Tag) idx = boost::multi_index::get<Tag>(m_sessions);

				//对容器的元素条件过滤
				is_business_type predicate(m);
				FilterContainer fc(predicate, idx.begin(), idx.end());
				FilterContainer::FilterIter iter = fc.begin();

				if (fc.begin() == fc.end())
				{
					return socket_session_ptr();
				}

				typedef boost::variate_generator<boost::mt19937, boost::uniform_smallint<>> var_gen;
				typedef boost::details::pool::singleton_default<var_gen_wraper<var_gen>> s_var_gen;

				//根据随机数产生session
				s_var_gen::object_type &gen = s_var_gen::instance();

				int step = gen() % fc.szie();

				for (int i = 0; i < step; ++i)
				{
					iter++;
				}

				return iter != fc.end() ? iter->session : socket_session_ptr();
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}
			return socket_session_ptr();
		}

		//根据类型和地址取session
		template<typename MultiIndexContainer, typename Tag>
		socket_session_ptr get_session_by_type_ip(WORD m, std::string& ip)
		{
			typedef filter_container<is_business_type, MULTI_MEMBER_ITR(MultiIndexContainer, Tag)> FilterContainer;
			try{
				boost::mutex::scoped_lock lock(m_mutex);

				if (m_sessions.empty())
				{
					return socket_session_ptr();
				}

				MULTI_MEMBER_CON(MultiIndexContainer, Tag) idx = boost::multi_index::get<Tag>(m_sessions);

				//对容器的元素条件过滤
				is_business_type predicate(m);
				FilterContainer fc(predicate, idx.begin(), idx.end());
				FilterContainer::FilterIter iter = fc.begin();

				if (fc.begin() == fc.end())
				{
					return socket_session_ptr();
				}

				while (iter != fc.end())
				{
					if (iter->session->get_remote_addr().find(ip) != std::string::npos)
					{
						break;
					}
					
					iter++;
				}

				return iter != fc.end() ? iter->session : socket_session_ptr();
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}
			return socket_session_ptr();
		}

		//根据类型和app_id取session
		template<typename MultiIndexContainer, typename Tag>
		socket_session_ptr get_session_by_type_appid(WORD m, DWORD app_id)
		{
			typedef filter_container<is_business_type, MULTI_MEMBER_ITR(MultiIndexContainer, Tag)> FilterContainer;
			try{
				boost::mutex::scoped_lock lock(m_mutex);

				if (m_sessions.empty())
				{
					return socket_session_ptr();
				}

				MULTI_MEMBER_CON(MultiIndexContainer, Tag) idx = boost::multi_index::get<Tag>(m_sessions);

				//对容器的元素条件过滤
				is_business_type predicate(m);
				FilterContainer fc(predicate, idx.begin(), idx.end());
				FilterContainer::FilterIter iter = fc.begin();

				if (fc.begin() == fc.end())
				{
					return socket_session_ptr();
				}

				while (iter != fc.end())
				{
					if (iter->session->get_app_id() == app_id)
					{
						break;
					}

					iter++;
				}

				return iter != fc.end() ? iter->session : socket_session_ptr();
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}
			return socket_session_ptr();
		}

	private:
		int m_type;
		int m_expires_time;
		boost::asio::io_service& m_io_srv;
		boost::asio::deadline_timer m_check_tick;
		boost::mutex m_mutex;

		session_set m_sessions;

		void check_connection();
	};
}

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

//session_manager.cpp

#include "session_manager.h"

namespace firebird{
	session_manager::session_manager(boost::asio::io_service& io_srv, int type, int expires_time)
		:m_io_srv(io_srv), m_check_tick(io_srv), m_type(type), m_expires_time(expires_time)
	{
		check_connection();
	}

	session_manager::~session_manager()
	{

	}

	//检查服务器所有session的连接状态
	void session_manager::check_connection()
	{
		try{
			boost::mutex::scoped_lock lock(m_mutex);

			session_set::iterator iter = m_sessions.begin();
			while (iter != m_sessions.end())
			{
				LOG4CXX_DEBUG(firebird_log, "循环");
				if (CLIENT == m_type)//客户端的方式
				{
					if (!iter->session->socket().is_open())//已断开,删除已断开的连接
					{
						LOG4CXX_INFO(firebird_log, "重新连接[" << iter->address << "]");
						iter->session->close(); //通过关闭触发客户端重连
					}
					else{//连接中,发送心跳
						iter->session->async_write(command::heartbeat, "H");
					}
				}
				else if (SERVER == m_type)//服务器的方式
				{
					if (!iter->session->socket().is_open())//已断开,删除已断开的连接
					{
						LOG4CXX_INFO(firebird_log, KDS_CODE_INFO << "删除已关闭的session:[" << iter->session->get_remote_addr() << "]");
						iter = m_sessions.erase(iter);
						continue;
					}
					else{//连接中,设定每30秒检查一次
						if (iter->session->is_timeout()) //如果session已长时间没操作,则关闭
						{
							LOG4CXX_INFO(firebird_log, KDS_CODE_INFO << "删除已超时的session:[" << iter->session->get_remote_addr() << "]");
							iter->session->close();//通过关闭触发删除session
						}
					}
				}
				else{
					LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown manager_type");
				}
				++iter;
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}

		try{
			LOG4CXX_DEBUG(firebird_log, "定时检查");
			m_check_tick.expires_from_now(boost::posix_time::seconds(m_expires_time));
			m_check_tick.async_wait(boost::bind(&session_manager::check_connection, this));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}
	}

	void session_manager::add_session(socket_session_ptr p)
	{
		try{
			boost::mutex::scoped_lock lock(m_mutex);
			session_stu stuSession;
			stuSession.id = p->id();
			stuSession.business_type = 0;
			stuSession.address = p->get_remote_addr();
			stuSession.app_id = p->get_app_id();
			stuSession.session = p;
			m_sessions.insert(stuSession);
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}
	}

	void session_manager::update_session(socket_session_ptr p)
	{
		try{
			boost::mutex::scoped_lock lock(m_mutex);
			if (m_sessions.empty())
			{
				return ;
			}

			MULTI_MEMBER_CON(session_set, sid) idx = boost::multi_index::get<sid>(m_sessions);
			BOOST_AUTO(iter, idx.find(p->id()));

			if (iter != idx.end())
			{
				const_cast<session_stu&>(*iter).business_type = p->get_business_type();
				const_cast<session_stu&>(*iter).app_id = p->get_app_id();
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}
	}
}

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

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

//filter_container.h

#pragma once
#include <boost/iterator/filter_iterator.hpp>

namespace firebird{
	template <class Predicate, class Iterator>
	class filter_container
	{
	public:
		typedef boost::filter_iterator<Predicate, Iterator> FilterIter;

		filter_container(Predicate p, Iterator begin, Iterator end)
			:m_begin(p, begin, end),
			m_end(p, end, end)
		{

		}
		~filter_container() {}

		FilterIter begin() { return m_begin; }
		FilterIter end()   { return m_end; }
		int szie() {
			int i = 0;
			FilterIter fi = m_begin;
			while(fi != m_end)
			{
				++i;
				++fi;
			}

			return i;
		}

	private:
		FilterIter m_begin;
		FilterIter m_end;
	};
}

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

#pragma once
#include "socket_session.h"
#include "session_manager.h"
#include <boost/format.hpp>

namespace firebird{
	using boost::asio::ip::tcp;

	class FIREBIRD_DECL server_socket_utils
	{
	private:
		boost::asio::io_service m_io_srv;
		boost::asio::io_service::work m_work;
		tcp::acceptor m_acceptor;

		void handle_accept(socket_session_ptr session, const boost::system::error_code& error);

		void close_callback(session_context& context);
		void read_data_callback(session_context& context);

	protected:
		virtual void handle_read_data(std::string& msg, socket_session_ptr pSession) = 0;

	public:
		server_socket_utils(int port);
		~server_socket_utils(void);

		void start();
		boost::asio::io_service& get_io_service() { return m_io_srv; }

		session_manager m_manager;
	};
}

//server_socket_utils.cpp

#include "server_socket_utils.h"
//#include "command_msg_convert.h"
#include "command_msg_types.h"
#include <firebird/archive/thrift_archive.hpp>

namespace firebird{
	server_socket_utils::server_socket_utils(int port)
		:m_work(m_io_srv),
		m_acceptor(m_io_srv, tcp::endpoint(tcp::v4(), port)),
		m_manager(m_io_srv, SERVER, 3)
	{
		m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
		// 关闭连接前留0秒给客户接收数据
		m_acceptor.set_option(boost::asio::ip::tcp::acceptor::linger(true, 0));
		m_acceptor.set_option(boost::asio::ip::tcp::no_delay(true));
		m_acceptor.set_option(boost::asio::socket_base::keep_alive(true));
	}

	server_socket_utils::~server_socket_utils(void)
	{
	}

	void server_socket_utils::start()
	{
		try{
			socket_session_ptr new_session(new socket_session(m_io_srv));
			m_acceptor.async_accept(new_session->socket(),
				boost::bind(&server_socket_utils::handle_accept, this, new_session,
				boost::asio::placeholders::error));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}


	}

	void server_socket_utils::handle_accept(socket_session_ptr session, const boost::system::error_code& error)
	{
		if (!error)
		{
			try{
				socket_session_ptr new_session(new socket_session(m_io_srv));
				m_acceptor.async_accept(new_session->socket(),
					boost::bind(&server_socket_utils::handle_accept, this, new_session,
					boost::asio::placeholders::error));

				if (session != NULL)
				{
					//注册关闭回调函数
					session->install_handler(socket_session::CLOSE, boost::bind(&server_socket_utils::close_callback, this, _1));
					//注册读到数据回调函数
					session->install_handler(socket_session::READ_DATA, boost::bind(&server_socket_utils::read_data_callback, this, _1));

					boost::format fmt("%1%:%2%");
					fmt % session->socket().remote_endpoint().address().to_string();
					fmt % session->socket().remote_endpoint().port();
					session->set_remote_addr(fmt.str());

					session->start();
					m_manager.add_session(session);
					//LOG4CXX_INFO(console_log, KDS_CODE_INFO << "[" << session->get_remote_addr() << "]接入成功!");
				}
			}
			catch(std::exception& e)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
			}
			catch(...)
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			}

		}
	}

	void server_socket_utils::close_callback(session_context& context)
	{
		LOG4CXX_DEBUG(firebird_log, "close_callback");
		try{
			m_manager.del_session<session_set, sid>(context.session->id());
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}

	}

	void server_socket_utils::read_data_callback(session_context& context)
	{
		try{
			command_msg cmsg;
			//from_string(cmsg, context.msg);
			firebird::thrift_iserialize(cmsg, context.msg);
			LOG4CXX_DEBUG(firebird_log, "command =[" << cmsg.command << "],[" 
				<< cmsg.business_type << "],[" << cmsg.data << "]");

			if (cmsg.command == heartbeat)
			{//心跳
				context.session->async_write(heartbeat,std::string("H"));
			}
			else if (cmsg.command == regist)
			{//注册
				context.session->set_business_type(cmsg.business_type);
				context.session->set_app_id(cmsg.app_id);
				m_manager.update_session(context.session);
				context.session->async_write(regist, std::string("R"));
				LOG4CXX_INFO(firebird_log, "[" << context.session->get_remote_addr() << "][" <<
					context.session->get_business_type() << "][" << context.session->get_app_id() << "]注册成功!");
			}
			else if (cmsg.command == normal)
			{//业务数据
				handle_read_data(cmsg.data, context.session);
			}
			else 
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown command.");
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}
	}
}

5.客户端

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

//client_socket_utils.h

#pragma once
#include "socket_session.h"
#include "session_manager.h"
#include <boost/algorithm/string.hpp>

namespace firebird{
	class FIREBIRD_DECL client_socket_utils
	{
	public:
		client_socket_utils();
		~client_socket_utils();

		void session_connect(std::vector<socket_session_ptr>& vSession);
		void session_connect(socket_session_ptr pSession);
		socket_session_ptr get_session(std::string& addr);
		boost::asio::io_service& get_io_service() { return m_io_srv; }

	protected:
		virtual void handle_read_data(std::string& msg, socket_session_ptr pSession) = 0;

	private:
		boost::asio::io_service m_io_srv;
		boost::asio::io_service::work m_work;
		session_manager m_manager;

		void handle_connect(const boost::system::error_code& error,
			tcp::resolver::iterator endpoint_iterator, socket_session_ptr pSession);

		void read_data_callback(session_context& context);
		void close_callback(session_context& context);
	};
}

//client_socket_utils.cpp

#include "client_socket_utils.h"
//#include "command_msg_convert.h"
#include "command_msg_types.h"
#include <firebird/archive/thrift_archive.hpp>

namespace firebird{
	client_socket_utils::client_socket_utils()
		:m_work(m_io_srv), m_manager(m_io_srv, CLIENT, 3)
	{
	}

	client_socket_utils::~client_socket_utils()
	{
	}

	void client_socket_utils::session_connect(std::vector<socket_session_ptr>& vSession)
	{
		for (int i = 0; i < vSession.size(); ++i)
		{
			session_connect(vSession[i]);
		}
	}

	void client_socket_utils::session_connect(socket_session_ptr pSession)
	{
		try{
			//注册读到数据回调函数
			pSession->install_handler(socket_session::READ_DATA, boost::bind(&client_socket_utils::read_data_callback, this, _1));
			//注册关闭回调函数
			pSession->install_handler(socket_session::CLOSE, boost::bind(&client_socket_utils::close_callback, this, _1));

			std::string& addr = pSession->get_remote_addr();

			std::vector<std::string> ip_port;
			boost::split(ip_port, addr, boost::is_any_of(":"));

			if (ip_port.size() < 2)
			{
				//throw std::runtime_error("ip 格式不正确!");
				LOG4CXX_ERROR(firebird_log, "[" << addr << "] ip 格式不正确!");
				return;
			}

			tcp::resolver resolver(pSession->socket().get_io_service());
			tcp::resolver::query query(ip_port[0], ip_port[1]);
			tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
			//pSession->set_begin_endpoint(endpoint_iterator);//设置起始地址,以便重连

			//由于客户端是不断重连的,即使还未连接也要保存该session
			m_manager.add_session(pSession);

			tcp::endpoint endpoint = *endpoint_iterator;
			pSession->socket().async_connect(endpoint,
				boost::bind(&client_socket_utils::handle_connect, this,
				boost::asio::placeholders::error, ++endpoint_iterator, pSession));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << " with exception:[" << e.what() << "]");
			//pSession->socket().close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			//pSession->socket().close();
		}
	}

	void client_socket_utils::handle_connect(const boost::system::error_code& error,
		tcp::resolver::iterator endpoint_iterator, socket_session_ptr pSession)
	{
		LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << " enter.");
		std::string sLog;
		try{
			if (!error)
			{
				LOG4CXX_INFO(firebird_log, "[" << pSession->get_business_type() <<"]连接[" << pSession->get_remote_addr().c_str() << "]成功!");
				pSession->start();

				//向服务器注册服务类型
				pSession->async_write(regist, std::string("R"));
			}
			else if (endpoint_iterator != tcp::resolver::iterator())
			{
				LOG4CXX_INFO(firebird_log, "连接失败,试图重连下一个地址。");
				pSession->socket().close();//此处用socket的close,不应用session的close触发连接,不然会导致一直重连
				tcp::endpoint endpoint = *endpoint_iterator;
				pSession->socket().async_connect(endpoint,
					boost::bind(&client_socket_utils::handle_connect, this,
					boost::asio::placeholders::error, ++endpoint_iterator, pSession));
			}
			else
			{
				LOG4CXX_INFO(firebird_log, KDS_CODE_INFO << "连接失败!");
				pSession->socket().close();//此处用socket的close,不应用session的close触发连接,不然会导致一直重连
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << " with exception:[" << e.what() << "]");
			//pSession->socket().close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			//pSession->socket().close();
		}
	}


	socket_session_ptr client_socket_utils::get_session(std::string& addr)
	{
		return m_manager.get_session<session_set, saddress>(addr);
	}

	void client_socket_utils::read_data_callback(session_context& context)
	{
		try{
			command_msg cmsg;
			//from_string(cmsg, context.msg);
			firebird::thrift_iserialize(cmsg, context.msg);
			LOG4CXX_DEBUG(firebird_log, "command =[" << cmsg.command << "],[" 
				<< cmsg.business_type << "],[" << cmsg.data << "]");

			if (cmsg.command == heartbeat)
			{//心跳
			}
			else if (cmsg.command == regist)
			{//注册
			}
			else if (cmsg.command == normal)
			{//业务数据
				handle_read_data(cmsg.data, context.session);
			}
			else 
			{
				LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown command.");
			}
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "with exception:[" << e.what() << "]");
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
		}
	}

	//关闭session就会重连
	void client_socket_utils::close_callback(session_context& context)
	{
		LOG4CXX_DEBUG(firebird_log, KDS_CODE_INFO << "enter.");

		try{
			//tcp::resolver::iterator endpoint_iterator = context.session->get_begin_endpoint();

			std::string& addr = context.session->get_remote_addr();

			std::vector<std::string> ip_port;
			boost::split(ip_port, addr, boost::is_any_of(":"));

			if (ip_port.size() < 2)
			{
				LOG4CXX_ERROR(firebird_log, "[" << addr << "] ip 格式不正确!");
				return;
			}

			tcp::resolver resolver(context.session->socket().get_io_service());
			tcp::resolver::query query(ip_port[0], ip_port[1]);
			tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);

			tcp::endpoint endpoint = *endpoint_iterator;
			context.session->socket().async_connect(endpoint,
				boost::bind(&client_socket_utils::handle_connect, this,
				boost::asio::placeholders::error, ++endpoint_iterator, context.session));
		}
		catch(std::exception& e)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "exception:[" << e.what() << "]");
			//context.session->socket().close();
		}
		catch(...)
		{
			LOG4CXX_ERROR(firebird_log, KDS_CODE_INFO << "unknown exception.");
			//context.session->socket().close();
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值