关于boost库的Asio中设置connect超时和read超时的一种解决方案

编写Asio中的同步或异步客户端时,库本身并没有提供超时机制,网上看了很多解决方案,看起来不是很简洁,想了一种思路,欢迎讨论

 

在使用同步client的时候,有时候会出现一种情况,可以成功打开客户端链接,但是数据发过去之后,很长时间收不到返回值,这个时候就会将进程或者线程阻住,影响其它业务的执行,所以想到用一个异步定时器,来控制超时,定时器代码实现如下:

#pragma once


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

test.cpp
#include
#include
#include
using namespace std;

/*
* 定时器类
* */
class std_timer
{
public:
	std_timer() :expired_(true), try_to_expire_(false)
	{
	}

	std_timer(const std_timer& t)
	{
		expired_ = t.expired_.load();
		try_to_expire_ = t.try_to_expire_.load();
	}
	~std_timer()
	{
		Expire();
		//std::cout << "timer destructed!" << std::endl;
	}

	void StartTimer(int interval, std::function task)
	{
		if (expired_ == false)
		{
			//std::cout << "timer is currently running, please expire it first..." << std::endl;
			return;
		}
		expired_ = false;
		std::thread([this, interval, task]()
		{
			while (!try_to_expire_)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(interval));
				task();
			}
			//std::cout << "stop task..." << std::endl;
			{
				std::lock_guard locker(mutex_);
				expired_ = true;
				expired_cond_.notify_one();
			}
		}).detach();
	}

	void Expire()
	{
		if (expired_)  return;

		if (try_to_expire_)
		{
			//std::cout << "timer is trying to expire, please wait..." << std::endl;
			return;
		}
		try_to_expire_ = true;
		{
			std::unique_lock locker(mutex_);
			expired_cond_.wait(locker, [this] {return expired_ == true; });
			if (expired_ == true) {
				// std::cout << "timer expired!" << std::endl;
				try_to_expire_ = false;
			}
		}
	}

	template
	void SyncWait(int after, callable&& f, arguments&&... args)
	{

		std::function::type()> task
		(std::bind(std::forward(f), std::forward(args)...));
		std::this_thread::sleep_for(std::chrono::milliseconds(after));
		task();
	}
	template
	void AsyncWait(int after, callable&& f, arguments&&... args)
	{
		std::function::type()> task
		(std::bind(std::forward(f), std::forward(args)...));

		std::thread([after, task]() {
			std::this_thread::sleep_for(std::chrono::milliseconds(after));
			task();
		}).detach();
	}

private:
	std::atomic expired_;
	std::atomic try_to_expire_;
	std::mutex mutex_;
	std::condition_variable expired_cond_;
};


//void EchoFunc(std::string&& s) {
//	std::cout << "test : " << s << endl;
//}
//
//int main_timer_std() {
//	Timer t;
//	//周期性执行定时任务	
//	t.StartTimer(1000, std::bind(EchoFunc, "hello world!"));
//	std::this_thread::sleep_for(std::chrono::seconds(4));
//	std::cout << "try to expire timer!" << std::endl;
//	t.Expire();
//
//	//周期性执行定时任务
//	t.StartTimer(1000, std::bind(EchoFunc, "hello c++11!"));
//	std::this_thread::sleep_for(std::chrono::seconds(4));
//	std::cout << "try to expire timer!" << std::endl;
//	t.Expire();
//
//	std::this_thread::sleep_for(std::chrono::seconds(2));
//
//	//只执行一次定时任务
//	//同步
//	t.SyncWait(1000, EchoFunc, "hello world!");
//	//异步
//	t.AsyncWait(1000, EchoFunc, "hello c++11!");
//
//	std::this_thread::sleep_for(std::chrono::seconds(2));
//
//	return 0;
//}

实现一个定时器很简单,不做赘述,然后再写一下asio同步客户端的实现:

#pragma once

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif
#include 
#include 
#include 
#include 

#include 
#include 
#include 


namespace BoostSocket
{
	namespace SyncClient
	{
		enum { BUF_SIZE = 1024 };
		using boost::asio::ip::tcp;

		class Client
		{
		public:
			Client(boost::asio::io_context& ioc,
				const std::string& host, const std::string& port)
				: socket_(ioc), resolver_(ioc){
				
				boost::asio::connect(socket_, 
					resolver_.resolve(boost::asio::ip::tcp::v4(), host, port));
			}

			virtual ~Client(void) { socket_.close(); }

			int  SendData(const char* _buffer, std::size_t data_len){
				std::size_t reply_length = boost::asio::write(socket_,
					boost::asio::buffer(_buffer, data_len));
				return reply_length;
			}

			int	 RecvData(char* data, std::size_t read_len) {
				std::size_t reply_length = boost::asio::read(socket_,
					boost::asio::buffer(data, read_len));
				return reply_length;
			}

		public:
			tcp::socket socket_;
			boost::asio::ip::tcp::resolver resolver_;

		private:
			std::string err_str_;
		};

		class ClientManager
		{
		public:
			ClientManager() {}
			~ClientManager() {}

		public:
			Client* get_client(const std::string& host, const std::string& port){
				return new Client(ioc_, host, port);
			}


		public:
			boost::asio::io_context ioc_;
		};

	}//namespace SyncClient
}//namespace BoostSocket

由于boost的asio库,在编译的时候有时会跟其它第三方库有冲突,导致出现很多编译问题,所以再在上层封装一层使用类:

#pragma once

#include 
#include 
#include 

namespace BoostSocket
{

	//同步客户端使用接口
	namespace SyncClient
	{

		class ClientManager;
		class Client;

		class ClientOper
		{
		public:
			virtual ~ClientOper() {}

			int  OpenClient(const std::string& host_name, const std::string & port);
			void CloseClient();

			int  SendData(const char* _buffer, std::size_t read_len);
			int  RecvData(char* data, std::size_t data_len);

		private:
			ClientManager*  oper_obj_;
			Client*  client_obj_;

		};
	}
}//namespace BoostSocket

#include "Boost_Socket_Oper.h"
#include "Client.h"

///
*同步客户端*
//
int BoostSocket::SyncClient::ClientOper::OpenClient(const std::string & host_name, const std::string & port)
{
	try {

		oper_obj_ = new BoostSocket::SyncClient::ClientManager;
		// 构建Server实例
		client_obj_ = oper_obj_->get_client(host_name, port);

	}
	catch (std::exception& _e) {
		std::cout << _e.what() << std::endl;
		return -1;
	}

	return 0;
}

void BoostSocket::SyncClient::ClientOper::CloseClient()
{
	if (client_obj_ != nullptr)
	{
		delete client_obj_;
		client_obj_ = nullptr;
	}

}

int BoostSocket::SyncClient::ClientOper::SendData(const char* _buffer, std::size_t read_len)
{
	int nret = 0;
	try
	{
		nret = client_obj_->SendData(_buffer, read_len);
	}
	catch (std::exception& _e) {
		std::cout << _e.what() << std::endl;
		nret = -1;
	}
	return nret;
}

int BoostSocket::SyncClient::ClientOper::RecvData(char* data, std::size_t data_len)
{
	int nret = 0;
	try
	{
		nret = client_obj_->RecvData(data, data_len);
	}
	catch (std::exception& _e) {
		std::cout << _e.what() << std::endl;
		nret = -1;
	}
	return nret;
}

封装好之后,我们就可以利用定时器来实现超时检测了,  测试代码如下:

using BoostSocket::SyncClient::ClientOper;

void time_out(ClientOper* client) {
	client->CloseClient();
}

int main(void)
{
	char request[1024] = { 0 };
	char reply[1024] = { 0 };
	std::vector pBuf;

	std::size_t request_length = 0;
	do {
		std::cout << "Enter message: ";
		std::cin.getline(request, 1024);
		request_length = std::strlen(request);
	} while (request_length == 0);


	ClientOper client;
	int n = client.OpenClient("172.20.26.152", "13588");
	if (n < 0)
	{
		std::cout << "打开客户端失败!" << std::endl;
		client.CloseClient();

		system("pause");
		return -1;
	}

	int nret = client.SendData(request, request_length);
	if (nret < 0)
	{
		std::cout << "发送数据失败!" << std::endl;
		client.CloseClient();

		system("pause");
		return -1;
	}

	std_timer timer;
	//同步
	timer.AsyncWait(3000, time_out, &client);

	int nlen = client.RecvData(reply, request_length);
	if (nlen < 0)
	{
		std::cout << "接收数据失败!" << std::endl;
		client.CloseClient();

		system("pause");
		return -1;
	}

	std::cout << reply << std::endl;
	std::cout << nret << " " << nlen << std::endl;

	client.CloseClient();

	system("pause");
	return 0;
}

在超时回调里可以将链接关闭重新打开,或者一些其它操作,来中断接收数据,这样就可以实现超时操作了。在项目中测试通过。

你可能感兴趣的:(boost::asio)