libuv套接字绑定失败导致的退出异常

libuv使用不当导致的内存泄漏 libuv库使用过程中,发现简单的定时器不关闭对应的句柄会导致内存泄漏;使用文章后面的测试代码缺少uv_close或者uv_loop_close均会导致内存泄漏,关闭的时机也有影响。 Detected memory leaks! Dumping objects -> {185} normal block at 0x00FB7860, 32 bytes long. Data: < R z > E8 52 E8 7A 00 00 00 00 00 00 00 00 00 阅读详情

1、问题

套按字绑定失败导致的loop退出异常

2、环境

libuv版本:1.42.0及以下

OS版本:win10 x64

3、现象

uv_async_send的回调方法中,使用uv_udp_bind(或者uv_tcp_bind,后文以udp举例),将一个本机不存在的IP,绑定到uv_udp_t(或者uv_tcp_t)代表的handle(new创建),绑定返回失败后,对handle进行释放(delete);在退出loop的过程中,应用发生异常。

备注:由于主机IP的人为修改等业务性操作流程的存在,意外导致应用配置文件未同步变化,此类问题在实际环境中确实存在。

4、分析

1、在绑定本机上的已有IP时,退出过程中,并未发生异常;
2、退出loop前,使用uv_walk迭代关闭libuv内部维护的handle,发现无论是正常还是异常情况的绑定,均存在一个handle(尽管可能还有别的handle,比如复现demo中的uv_async_t,但我们可以聚焦于问题相关的那个),怀疑此handle是对应的udp类型的handle。在绑定失败的情况下,进入uv_walk后,handle的type是个随机的野值;
3、查看libuv源码中的src/win/udp.c文件,在uv_udp_maybe_bind方法中,当bind失败后,直接返回了WSAGetLastError()方法返回的错误值;
4、第3步导致的问题是,在bind操作前,已经使用了uv_udp_init将handle与loop进行了关联,而bind失败后,并没有关闭(closesocket)创建的handle->socket,也没有撤销handle与loop的关联(QUEUE_REMOVE),导致loop中已经存在了一个udp handle,而bind失败后,又接着对handle占用的内存进行了释放;
5、退出loop时,uv_walk依然发现queue里有个handle,然而,我们在上一步中,已经释放了对应地址的handle(甚至此地址已经又包含着另外的对象),从而导致了连锁的异常;

5、复现

我们的复现demo,其主要流程是:应用进程的主线程内,首先创建一个工程线程,在后者内,初始化、运行libuv的loop(即uv_run位于工作线程中);然后,主线程内,通过uv_async_send的异步方式,传递一个请求,使用libuv的loop创建并绑定udp handle;最后,主线程等待用户输入字符q,以退出整个应用。

在demo中,当绑定使用的是ipNormal的值(即本机存在的IP时),无异常;当绑定使用的是ipAbnormal的值(即非本机存在的IP)时,退出发生异常;

6、解决

可能的解决方案有以下:
方案一
1、没有libuv源代码(无法完成源码修改)的情况下,我们只能从应用的实现、释放流程上,避免发生对bind失败的handle,立刻进行内存释放的操作,而应该在loop彻底退出后,再释放handle占用的内存(尽管这样,让人感觉些许的难受,此外,loop全程也维护了一个无法行使职能的handle);

按照libuv的坚持(https://github.com/libuv/libuv/issues/3272),也应该先uv_close(失败时的处理未文档化),然后在callback中再释放,以demo中的,可能比较别扭:

    if (iRet != 0) {
        if (uvUdpHandle) {
            uvUdpHandle->data = this;
            ::uv_close((uv_handle_t*)uvUdpHandle, [](uv_handle_t* handle){
                ((DMainTests*)handle->data)->uvUdpHandle = nullptr;
                delete handle;
                handle = nullptr;
            });

        }
    }

方案二
1、将libuv源代码src/uv-common.c中的第341行:

return uv__udp_bind(handle, addr, addrlen, flags);


修改为:

int ret = uv__udp_bind(handle, addr, addrlen, flags);
if (ret != 0) {
closesocket(handle->socket);    // need?
QUEUE_REMOVE(&handle->handle_queue);
}
return ret;

2、修改完后,重新编译生成libuv库;

7、附代码

工程结构

 

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(UvTests)

set(CMAKE_CXX_STANDARD 11)

include_directories("${PROJECT_SOURCE_DIR}/lib/libuv1.42.0/include")

add_executable(${PROJECT_NAME}
        main.cpp
        test/DMainTests.cpp
        test/DMainTests.h)

link_directories(${PROJECT_SOURCE_DIR}/lib/libuv1.42.0/win/Debug)

target_link_libraries(${PROJECT_NAME} ws2_32)
target_link_libraries(${PROJECT_NAME} "${PROJECT_SOURCE_DIR}/lib/libuv1.42.0/win/Debug/libuv.dll")

test/DMainTests.h

#ifndef UVTESTS_DMAINTESTS_H
#define UVTESTS_DMAINTESTS_H

#include "uv.h"

#include <atomic>

class DMainTests {

protected:
    uv_loop_t* objLoop;
    uv_async_t *uvAsyncHandle;
    uv_udp_t* uvUdpHandle;

    volatile std::atomic<bool> flagOfThreadExit;    // uv loop thread exit flag
    volatile std::atomic<bool> flagOfLoopOn;        // uv loop is online?
    volatile std::atomic<bool> flagOfExit;          // app exit flag

public:
    DMainTests();

    virtual ~DMainTests();

public:
    virtual void init();

protected:
    virtual void initBiz();

    virtual void closeBiz();

    static void threadPrc(void* pVoid);

    virtual void threadProcObj();

    static void cbAsync(uv_async_t *handle);

    virtual void cbAsyncObj();

    static void cbWalkClose(uv_handle_t* handle, void *arg);
};


#endif //UVTESTS_DMAINTESTS_H

test/DMainTests.cpp

#include "DMainTests.h"

#include <iostream>
#include <string>
#include <thread>

DMainTests::DMainTests() {

}

DMainTests::~DMainTests() {

}

void DMainTests::init() {
    initBiz();
}

void DMainTests::initBiz() {
    flagOfLoopOn = false;
    flagOfExit = false;
    std::thread obj(DMainTests::threadPrc, this);
    obj.detach();

    while (!flagOfLoopOn) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        std::this_thread::yield();
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    ::uv_async_send(uvAsyncHandle);

    // wait or exit when input 'q'
    std::string strInput;
    std::getline(std::cin, strInput);
    if (strInput.compare("q") == 0) {
        closeBiz();
    }
}

void DMainTests::closeBiz() {
    std::cout << "DMainWindowTests::closeBiz enter" << std::endl;
    flagOfExit = true;
    ::uv_async_send(uvAsyncHandle);

    while (!flagOfThreadExit) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        std::this_thread::yield();
    }
    std::cout << "DMainWindowTests::closeBiz exit" << std::endl;
}

void DMainTests::threadPrc(void *pVoid) {
    DMainTests* obj  =(DMainTests*)pVoid;
    obj->threadProcObj();
}

void DMainTests::threadProcObj() {
    flagOfThreadExit = false;

    objLoop = new uv_loop_t;
    uv_loop_init(objLoop);

    uvUdpHandle = new uv_udp_t;

    uvAsyncHandle = new uv_async_t;
    uvAsyncHandle->data = this;
    ::uv_async_init(objLoop, uvAsyncHandle, &DMainTests::cbAsync);

    flagOfLoopOn = true;
    ::uv_run(objLoop, UV_RUN_DEFAULT);

    delete uvAsyncHandle;
    uvAsyncHandle = nullptr;

    if (uvUdpHandle) {
        delete uvUdpHandle;
        uvUdpHandle = nullptr;
    }

    delete objLoop;
    objLoop = nullptr;

    flagOfThreadExit = true;
}

void DMainTests::cbAsync(uv_async_t *handle) {
    DMainTests* obj = (DMainTests*)handle->data;
    obj->cbAsyncObj();
}

void DMainTests::cbAsyncObj() {
    std::cout << "DMainWindowTests::cbAsyncObj enter" << std::endl;
    if (flagOfExit) {
        ::uv_walk(objLoop, &DMainTests::cbWalkClose, nullptr);
        std::cout << "DMainWindowTests::cbAsyncObj exit[app exit]" << std::endl;
        return;
    }

    const char* ipNormal = "192.168.159.1";
    const char* ipAbnormal = "123.6.2.18";
    sockaddr_in addr;
    int iRet = -1;
    do {
        iRet = ::uv_ip4_addr(ipAbnormal, 36218, &addr);
        iRet = ::uv_udp_init_ex(objLoop, uvUdpHandle, 2);
        if (iRet != 0) {
            break;
        }
        iRet = ::uv_udp_bind(uvUdpHandle, (const sockaddr*)&addr, UV_UDP_REUSEADDR);
        if (iRet != 0) {
            break;
        }
    } while(false);

    if (iRet != 0) {
        if (uvUdpHandle) {
            delete uvUdpHandle;
            uvUdpHandle = nullptr;
        }
    }

    std::cout << "DMainWindowTests::cbAsyncObj exit[udp bind]" << std::endl;
}

void DMainTests::cbWalkClose(uv_handle_t *handle, void *arg) {
    if (handle && uv_is_closing(handle) == 0 && handle->type != 0) {
        std::cout << "DMainWindowTests::cbWalkClose run, type : "
        << handle->type
        << std::endl;

        uv_close(handle, nullptr);
    }
}

main.cpp

#include "test/DMainTests.h"

int main(int argc, char *argv[])
{
    DMainTests windowTests;
    windowTests.init();

    return 0;
}

Linux C套接字编程实战:从TCP/UDP到epoll高并发 网络编程是构建分布式系统的基石,其核心在于理解操作系统提供的进程间通信机制。在Linux系统中,套接字(Socket)作为“一切皆文件”理念的延伸,为网络I/O提供了统一的文件描述符抽象,使得开发者可以使用read/write等标准接口操作网络连接。其底层原理基于TCP/IP协议栈,通过三次握手建立可靠连接,或通过UDP实现无连接通信,分别满足不同场景下对可靠性、实时性的需求。这项技术的核心价值在于能够高效、稳定地实现跨网络的数据交换,是开发Web服务器、实时通信、物联网设备等应用的必备技能。在实际应用中, 阅读详情

相关推荐

Windows C++开发实战:基于hiredis与CMake构建生产级Redis客户端

在现代分布式系统与高性能应用中,缓存与消息队列是提升性能与解耦架构的核心技术。Redis作为高性能的内存数据存储,通过其丰富的数据结构与原子操作,为应用提供了低延迟的数据访问与消息传递能力。其核心原理基于内存操作、单线程事件循环与高效网络I/O模型,确保了极高的吞吐量。在C++工程实践中,直接使用C语言客户端库hiredis能提供最底层的控制与最佳性能,但需妥善处理资源管理与错误重连。特别是在Windows开发环境下,结合CMake构建系统与vcpkg包管理器,可以高效解决依赖管理与跨平台编译的挑战。本文聚

weixin_34302798的博客 346

新Nodejs事件循环

先放一张官方提供的事件循环图 官方事件循环机制解析 timers 本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数 pending callbacks 此阶段为某些系统操作(例如 TCP 错误类型)执行回调。例如,如果一个 TCP 套接字ECONNREFUSED在尝试连接时收到,一些 *nix 系统希望等待报告错误。这将在挂起的回调阶段排队执行 idle, prepare 系统内部调用 poll poll是一个核心阶段,等新I/O事件的触发,以及执行I/O相关回

Nodejs 706

C++高性能网络库Fastsocket:Reactor模式与零拷贝设计实战

网络编程是构建高并发、低延迟服务(如游戏服务器、实时通信系统)的核心技术。其核心原理在于高效管理大量网络连接与I/O事件,常见模型包括Reactor和Proactor。Reactor模式因其在用户态的可控性及与Linux epoll机制的深度契合,成为追求极致性能场景下的主流选择。该模式通过事件驱动,在保证高吞吐量的同时,实现了可预测的低延迟,技术价值显著。在实际应用中,结合边缘触发(ET)模式、多线程协同与智能负载均衡,可有效处理海量短连接或大流量长连接。本文聚焦的Fastsocket网络库,正是基于多线

weixin_30755393的博客 341

libuv笔记 (一)Threads

Threads 线程在现代程序开发中会很常见,当然Libuv也不能缺席这一块,记得你在使用过程中要非常认真的处理 各种原始的同步问题。 线程会在内部使用,用来在执行系统调用时伪造异步的假象。libuv通过线程还可以使得程序异步地执行一个阻塞的任务。方法就是大量地生成新线程,然后收集线程执行返回的结果。 当下有两个占主导地位的线程库:windows下的线程实现和POSIX的pthread。libuv...

雪狼人的宿命 876

Linux C++ 系统编程实战:从环境搭建到并发网络服务端开发

系统编程是理解计算机如何管理硬件和软件资源的核心,它通过系统调用作为用户程序与操作系统内核交互的桥梁,实现对进程、内存、文件和网络等底层资源的直接控制。这种底层控制能力为构建高性能、高并发的系统级应用提供了技术基础,其价值在于能够开发出更高效、更稳定且资源利用率更高的软件,广泛应用于后端服务、AI基础设施和嵌入式系统等领域。本文聚焦于 Linux 环境下的 C++ 开发,通过一个支持多客户端的并发网络服务端实战项目,串联起环境配置、系统编程和网络通信等核心知识点。项目实践涵盖了 Socket 编程、多线程并

chupu2979的博客 504

C++高性能Web服务器:从阻塞I/O到事件驱动架构的实践

在网络编程中,I/O模型是决定服务性能的核心。传统的阻塞式I/O模型为每个连接分配独立线程,在并发量上升时面临资源消耗大、可扩展性差的问题。其原理是线程在数据未就绪时会进入等待状态,导致CPU空闲和频繁的上下文切换。为了解决这一瓶颈,非阻塞I/O与事件驱动模型应运而生,通过I/O多路复用技术(如kqueue、epoll)实现单线程管理大量连接,极大地提升了资源利用率和系统吞吐量。这一技术价值在于能够以极低的资源开销支撑数万甚至数十万的并发连接,特别适用于高并发的网络服务场景,如实时通信、API网关和微服务架

weixin_33698043的博客 428

Libuv 句柄优雅关闭

当服务端与客户端处于连接状态时,如果客户端应用进程突然关闭,我们可以直接使用Libuv中的uv_close暴力断开两端的连接...

田螺姑娘的博客 1547

Socket阻塞与非阻塞模式详解:从原理到高并发实战

在网络编程中,Socket是进程间通信的基础,其工作模式直接决定了程序的性能和可靠性。阻塞模式让线程等待I/O操作完成,编程简单但并发能力受限;非阻塞模式则立即返回状态,将等待责任交给应用程序,为实现高并发奠定了基础。理解这两种模式的原理,是掌握高性能网络服务开发的关键。通过I/O多路复用技术(如epoll、kqueue),可以高效管理大量非阻塞Socket,避免忙轮询带来的CPU浪费,从而构建出能处理数万并发连接的服务。在实际工程中,结合事件循环库(如libevent、libuv)或异步框架(如Netty

cuixie2370的博客 425

Libuv 各个回调(异步)事件的调用时机

下面有一个应用场景,与uv_close的调用时机有关,服务端有一个心跳检测机制,判断连接上来每个会话(Session)是否具有活性。其中,ClientHeartCheck是一个定时器事件,定时时间为3000。

田螺姑娘的博客 1396

Windows网络编程:WSAAsyncSelect异步I/O模型详解与实战

在Windows平台C/S网络编程中,I/O模型的选择直接影响程序性能与开发效率。异步I/O模型通过非阻塞操作和事件通知机制,允许单线程高效管理多个网络连接,避免了传统多线程模型的上下文切换开销与同步复杂性。其技术价值在于将网络事件处理无缝集成到应用程序主循环,尤其适合需要高响应性的交互式软件。在Windows GUI应用中,WSAAsyncSelect函数巧妙地将网络事件转换为窗口消息,利用现有消息驱动架构处理FD_READ、FD_WRITE等事件,实现了网络逻辑与UI事件处理的统一。这种模型特别适用于连

weixin_33692284的博客 387

uWebSockets高性能Web服务实战:从原理到生产级应用

WebSocket协议作为HTML5标准的一部分,实现了浏览器与服务器间的全双工、持久化通信,其核心原理是基于HTTP握手升级建立的TCP长连接,通过数据帧实现高效的双向实时数据传输。这一技术为需要低延迟、高并发的实时应用场景提供了基础通信能力,具有极高的技术价值。在物联网、在线游戏、金融交易和实时协作等对实时性要求严苛的领域,WebSocket是构建消息推送、状态同步等功能的基石。为了充分发挥WebSocket的性能潜力,开发者需要选择高性能的底层实现库。uWebSockets正是一个采用C++17编写、

weixin_34293911的博客 348

Windows C++网络编程:ws2_32.lib静态库详解与实战配置

在C++网络编程中,Socket通信是实现进程间网络数据传输的基础技术,它基于TCP/IP协议栈,通过创建、绑定、监听和连接等操作建立网络端点。其核心原理在于操作系统提供的套接字接口抽象,允许应用程序通过文件描述符风格的句柄进行网络I/O操作。这一技术的工程价值在于实现了跨主机的可靠或不可靠数据传输,是构建客户端-服务器架构、分布式系统的基石。在Windows平台下进行Socket编程时,开发者必须链接ws2_32.lib导入库,它作为Windows Sockets 2 API的桥梁,将代码调用导向系统底层

weixin_34109408的博客 367

C++网络编程核心:Socket API、并发模型与高性能服务器实践

网络编程是构建分布式系统的基石,它基于操作系统提供的套接字接口实现进程间通信。其核心原理在于通过TCP/IP协议栈管理数据包的可靠传输,涉及连接建立的三次握手与终止的四次挥手。这一技术的核心价值在于能够开发出高性能、高并发的服务器应用,如游戏服务器、高频交易系统和物联网网关。在实际工程中,开发者需要深入理解IO多路复用、非阻塞IO等机制来应对海量连接。本文聚焦于C++网络编程的实践,详细解析了从基础的Socket API到复杂的并发模型演进,并探讨了如何通过epoll等现代技术构建稳定高效的网络应用,其中I

weixin_34297704的博客 422

【Linux网络】从0手写Reactor反应堆(三):代码收尾、连接生命周期管理与多Reactor扩展思路

上一篇我们把Reactor的核心逻辑彻底填实了:ET模式下的非阻塞循环读写、应用层缓冲区解决TCP粘包、IO层/协议层/业务层三层解耦,整个请求从接收、解析到响应的链路已经完整跑通。但框架还剩几块收尾工作没有落地:写事件的动态开关只有调用接口没有底层实现、客户端断开或出错时连接没有真正从反应堆移除、文件描述符资源也没有正确释放。本篇我们先把这些代码细节补完,让单Reactor里的每一条连接,从被accept创建、到数据收发、再到异常/正常断开,形成完整的生命周期闭环;随后汇总所有核心文件的完整代码,方便整体

会当凌绝顶,一览众山小 4753

从零封装C++ TCP通信框架:掌握Socket编程核心与C/S模型实践

网络编程是构建分布式系统和后台服务的核心技术,其基础在于理解TCP/IP协议栈和Socket编程模型。TCP协议通过三次握手建立可靠连接,并提供有序、无差错的字节流传输,而Socket则是操作系统提供的网络编程接口,允许应用程序通过文件描述符进行网络通信。掌握Socket编程对于实现客户端-服务器(C/S)架构至关重要,它能帮助开发者构建从即时通讯到游戏服务器等各种网络应用。在实际工程中,直接使用原生Socket API常面临错误处理繁琐、数据边界模糊和跨平台兼容性等挑战。通过封装可复用的TcpClient

aiqixiao2017的博客 415

树莓派Socket编程实战:从TCP/UDP原理到C语言并发服务器实现

Socket(套接字)是网络通信的基石,它定义了不同主机间进程通信的端点。其核心原理基于客户端-服务器模型,通过IP地址和端口号唯一标识通信双方。Socket API(如Berkeley Sockets)提供了统一的编程接口,屏蔽底层网络协议细节,技术价值在于实现了跨平台、跨语言的标准化网络通信能力,广泛应用于Web服务、文件传输、物联网等场景。在物联网(IoT)和嵌入式开发领域,掌握Socket编程尤为关键,它能将树莓派等设备转变为网络节点。本文以树莓派为硬件平台,深入讲解如何使用C语言实现TCP/UDP

weixin_30889885的博客 390

【Linux网络】深入理解Linux epoll:从内核原理到实战代码全解析

在 Linux 高性能网络编程的世界里,I/O 多路转接是绕不开的核心技术。从早期的 select 到 poll,再到如今几乎成为行业标准的 epoll,每一次演进都是为了解决高并发场景下的效率痛点。很多同学都用过 epoll,但对它的理解往往停留在 “三个系统调用” 的表面,对内核里红黑树、就绪队列、回调机制的工作逻辑一知半解,面试时被问到 LT/ET 模式、epoll 高效的本质也常常答不透彻。这篇文章我们就从 “为什么需要 epoll” 讲起,深入内核拆解它的工作原理,再结合一套完整的 C++ 封装代

会当凌绝顶,一览众山小 3983

避坑指南:海康MV_CC_SetIntValue接口的7个典型报错解决方案

本文深度解析海康工业相机SDK中MV_CC_SetIntValue接口的典型报错,提供7个实战解决方案。针对MV_E_CALLORDER、MV_E_PARAMETER等高频错误码,从设备状态、参数值域、依赖关系等核心约束入手,给出清晰的诊断流程与根治代码,帮助开发者构建健壮的参数设置模块,提升开发效率。

y2z3a4b5c的博客 1078

C++多线程与异步IO结合构建高性能实时数据处理系统

在实时数据处理领域,高吞吐与低延迟是核心诉求。其技术原理在于通过并发编程模型充分利用多核CPU与异步I/O能力,以应对海量数据流。从技术价值看,这直接决定了金融交易、物联网等关键系统的性能上限与稳定性。具体实现中,线程池负责管理计算密集型任务,而异步I/O模型则高效处理网络、磁盘等阻塞操作。通过生产者-消费者架构将两者解耦,并结合无锁队列、内存池等优化手段,可构建出高效的数据处理管道。本文聚焦于C++语言,深入探讨如何将多线程与异步I/O深度结合,并针对线程池设计、Future/Promise模式以及生产级

cuyi7076的博客 832

【QT+VS】如何在现有VS项目中添加Qt界面?【全网最详细】

VS项目有了,就缺个QT界面,怎么办呢?这篇文章帮你一键搞定!

深耕CV 4984

FFmpeg实战:5分钟搞定m3u8视频下载与ts文件合并(附完整命令)

本文详细介绍了如何使用FFmpeg快速下载m3u8视频并合并ts文件为mp4格式。通过简洁的命令行操作,读者可以轻松完成视频处理任务,包括环境准备、链接获取、下载合并及性能优化等实用技巧,特别适合需要高效处理流媒体内容的用户。

weixin_30836759的博客 221
上一篇: QT5 “Multicast Receiver Example”组播接收数据的说明
下一篇: 银河麒麟V10sp1中实现双击shell脚本的正常运行
smartcore
博客等级 码龄19年 30粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值