当前位置:C++技术网 > 资讯 > 关于libuv处理网络事件的问题

关于libuv处理网络事件的问题

更新时间:2017-09-02 09:40:43浏览次数:1+次

关于libuv写的一个压力测试程序, libuv 版本使用的 1.13.2-dev

初始化libuv 创建了2个loop, 一个mainloop,用于处理异步回调的用户事件;一个workloop用户socket的连接和消息发送


void CLibuvController::Init(I32 cpu_odds)
{
	main_loop.loop_ = uv_loop_new();
	// 设置主线程异步回调
	uv_async_init(main_loop.loop_, &main_loop.async_, OnCallBackWork);
	// 接收消息
	work_loop.loop_ = uv_loop_new();
	work_loop.loop_->data = &main_loop;
	I32 err = uv_thread_create(&work_loop.thread_, WorkThread,(void*)&work_loop.loop_);
}
//工作线程,启动了2个定时器, 用于监听连接和消息发送


void CLibuvController::WorkThread(void* pvoid)
{
	uv_timer_init(work_loop.loop_, &time1);
	uv_timer_start(&time1, OnConnectTimer, 100, 1000);

	uv_timer_init(work_loop.loop_, &time2);
	uv_timer_start(&time2, OnSendMsgTimer, 100, 1);

	m_IsRun = true;
	uv_run(work_loop.loop_, UV_RUN_DEFAULT);
	ShowLog("退出工作线程");
}
//监听socket是否可以连接
void CLibuvController::OnConnectTimer(uv_timer_t* t)
{
	if (!m_IsRun) return;
	//ShowNotice("OnConnectTimer ThreadId: %lu", std::this_thread::get_id());
	for (auto pSession : m_sessionList)
	{
		if (!pSession->IsConnect())
			pSession->Connect();
	}
}
//监听socket数据发送
void CLibuvController::OnSendMsgTimer(uv_timer_t* t)
{
	if (!m_IsRun) return;
	for (auto pSession : m_sessionList)
	{
		if (pSession->IsConnect())
			pSession->Send();
	}
}
//启动网络层
void CLibuvController::Start()
{
        //提供给用户层的定时器
	if (m_pWorkTimer)
	{
		m_pWorkTimer->SetWorkLoop(&main_loop);
		m_pWorkTimer->Init();
	}
	uv_run(main_loop.loop_, UV_RUN_DEFAULT);
}
//Socket的封装, 初始化函数
void CLibuvScoket::InitSocket(my_loop* pMainLoop, my_loop* pWorkLoop, I32 Index)
{
        m_pWorkLoop = pWorkLoop;//绑定的uv_loop_t
	m_pMainLoop = pMainLoop;//主线程的uv_loop_t
	m_Index = Index;
	
	m_Client_.data = this;
	m_ConnectState = CONNECT_DIS;
	m_IsConnected = false;
	m_IsRunning = false;
	readbuffer_ = uv_buf_init((I8*)malloc(BUFFER_SIZE), BUFFER_SIZE);
	writebuffer_ = uv_buf_init((I8*)malloc(BUFFER_SIZE), BUFFER_SIZE);
	m_SendBuffer.SetEmpty();
	m_RecvBuffer.SetEmpty();
}
// 连接服务器
void CLibuvScoket::Connect()
{
        //初始uv_tcp
	I32 isret = uv_tcp_init(m_pWorkLoop->loop_, &m_Client_);
	if (isret) return;
	struct sockaddr_in bind_addr;
	int iret = uv_ip4_addr(m_ConnectIp.c_str(), m_ConnectPort, &bind_addr);
	if (iret) return;
        //开始连接
	iret = uv_tcp_connect(&connect_, &m_Client_, (const sockaddr*)&bind_addr, OnBack_Connect);
	if (iret)
	{
		ShowError("%d: 连接服务器失败: %s", m_Client_.socket, GetUVError(iret).c_str());
		return;
	}
	m_ConnectState = CONNECT_DOING;
}

//连接回调
void CLibuvScoket::OnBack_Connect(uv_connect_t* handle, I32 status)
{
	CLibuvScoket *pClient = (CLibuvScoket*)handle->handle->data;
	if (status)
	{
		pClient->m_ConnectState = CONNECT_ERROR;
		ShowError("%d 连接错误: %s", pClient->m_ClientId, GetUVError(status).c_str());
		pClient->Close();
		return;
	}
	//客户端开始接收服务器的数据 
	int iret = uv_read_start(handle->handle, OnAllocBuffer, OnBack_Recv);
	if (iret)
	{
		ShowError("%d 开始读取数据 错误: %s", pClient->m_ClientId, GetUVError(iret).c_str());
		pClient->Close();
		pClient->m_ConnectState = CONNECT_ERROR;
	}
	else
	{
		// 连接成功, 回调主线程
		MsgEvent data;
		data.type = MSG_TYPE_CONNECT;
		data.index = pClient->m_Index;
		CLibuvController::work_msg_queue.put(data);
		uv_async_send(&pClient->m_pMainLoop->async_);
		pClient->m_IsRunning = true;
		pClient->m_ConnectState = CONNECT_FINISH;
		pClient->m_IsConnected = true;
	}
}

这样的处理方式合理吗?是否妥当的使用了libuv呢?

因为测试需求, 我并不是一个socket绑定的一个loop句柄, 而是多个socket绑定了一个loop句柄.

在测试过程中, 程序会频繁的连接服务器A 主动断开 再次连接服务器B 在断开, 又连接服务器A, 这样的重复动作.当socket连接个数一多.跑不到几分钟就挂了. 


C++技术网会员解答:

    您好,感谢您对C++技术网的支持与信任。

    libuv总体上文档资料还是偏少,而且属于一个网络库,使用的人并不是非常多。要想真正了解深入,建议还是看源码学习。

    对于“初始化libuv 创建了2个loop, 一个mainloop,用于处理异步回调的用户事件;一个workloop用户socket的连接和消息发送”,这个实现应该是没有问题的。loop事件循环会不断的监听事件,然后再触发对应的回调函数处理。只要回调函数能够正常被回调,功能就是正常的。

    用作测试,你这里只实现了TCP客户端。而客户端会与多个服务器进行通信。而客户端一个通信loop里面,绑定了多个socket。而从表现现象来看,“ 程序会频繁的连接服务器A 主动断开 再次连接服务器B 在断开, 又连接服务器A, 这样的重复动作.”。这个说明,在处理socket事件时,libuv是将绑定的之前的socket关闭,然后用新的socket来处理。虽然是绑定了多个socket,但是并没有保证多个socket能够并发通信,这里是轮流通信。因为一个loop绑定多一个socket,也就是说,多个socket的事件都会经过这个loop句柄,然后再通信的时候,每一个socket的上下文都不一样。在处理socket事件时,自然要切换上下文环境。在切换上下文环境的时候,也就将上一个socket断开了,然后重连了新的socket。但是socket本身并没有删除,只是断开了连接而已。这大概就是出现你描述的问题的原因吧。

    那么要避免这个问题,就要主动设置不关闭连接什么的。这个要具体查询libuv的功能,看看如何实现。

    至于“当socket连接个数一多.跑不到几分钟就挂了. ”这个问题,要看具体的问题,然后查看崩溃的地方的代码,去分析原因了,这个问题没法推测。

    建议排查问题的步骤是:

1.只使用1个socket进行快速的通信测试,确保程序本身没有问题。在快速通信(如间隔10ms)时就可能暴露一些问题。

2.在1的可靠基础上,再绑定多个socket进行测试。

3.断开连接后,查看断开的原因,是正常断开还是强制断开还是异常断开。这样才好去寻找解决办法,使用libuv的机制去设置不断开之类的。

4.当连接数变多的时候会崩溃,请仔细分析崩溃时提示的问题描述,搜索查询崩溃问题相关的资料,以定性问题。然后再分析代码以解决问题。