IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> libuv套接字绑定失败导致的退出异常 -> 正文阅读

[网络协议]libuv套接字绑定失败导致的退出异常

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);

方案二
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;
}

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-19 12:24:57  更:2021-08-19 12:27:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:38:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码