Skip to content

[component][at_socket] udp一个socket跟多个地址通讯bug修复、DNS加锁防止多线程错乱、实现at_geth… #9403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 9, 2024

Conversation

Ryan-CW-Code
Copy link
Contributor

…ostbyname_r、稳定性优化

拉取/合并请求描述:(PR description)

[

为什么提交这份PR (why to submit this PR)

你的解决方案是什么 (what is your solution)

请提供验证的bsp和config (provide the config and bsp)

  • BSP:
  • .config:
  • action:

]

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用formatting 等源码格式化工具确保格式符合RT-Thread代码规范 This PR complies with RT-Thread code specification

@Ryan-CW-Code
Copy link
Contributor Author

Ryan-CW-Code commented Sep 6, 2024

测试内容

ec800创建两个tcp客户端连接到电脑进行echo测试
ec800用一个udp socket依次向4个不同端口的udp发送报文
使用公司产品进行功能测试正常

测试截图

4g流量消耗不起,测试数据量很低

image

测试代码

写的很随意请见谅,能测试功能就行

#define TAG ("atSocket")

#define rlog_a(fmt, ...) ulog_a(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define rlog_e(fmt, ...) ulog_e(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define rlog_w(fmt, ...) ulog_w(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define rlog_i(fmt, ...) ulog_i(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define rlog_d(fmt, ...) ulog_d(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define rlog_v(fmt, ...) ulog_v(TAG, "%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)

struct clientEchoInfo
{
    char serviceIP[32];
    int32_t servicePort;
};

static struct at_device_ec800x _dev =
    {
        .device_name = "ec800x",
        .client_name = "uart2",
        .reset_pin = GET_PIN(B, 6),
        .power_key_pin = GET_PIN(B, 7),
        .power_status_pin = -1,
        .wakeup_pin = -1,
        .recv_bufsz = 1536,
        .send_bufsz = 512,
        .rssi = 99,
        .power_ctrl = NULL,
};

static struct netdev *RyanNetdev = NULL;

static void neDevStatusChangeCallback(struct netdev *netdev, enum netdev_cb_type type)
{
    rlog_i("atSocket nedev state: %d", type);
}

static int atSocketStart(int argc, char *argv[])
{
    if (NULL != RyanNetdev)
    {
        rlog_w("atSocket已经启动,不要重复选择");
        return -1;
    }

    // 网络初始化
    struct at_device_ec800x *ec800x = &_dev;
    at_device_register(&(ec800x->device),
                       ec800x->device_name,
                       ec800x->client_name,
                       AT_DEVICE_CLASS_EC800X,
                       (void *)ec800x);

    struct netdev *RyanNetdev = netdev_get_by_name("ec800x"); // netdev
    if (NULL == RyanNetdev)
    {
        LOG_E("No device found");
        RT_ASSERT(NULL);
    }

    RyanNetdev = netdev_get_by_name("ec800x"); // netdev
    if (NULL == RyanNetdev)
    {
        rlog_e("No device found");
        return -1;
    }

    netdev_set_default(RyanNetdev);
    netdev_set_status_callback(RyanNetdev, neDevStatusChangeCallback);

    rlog_i("at_socket 启动成功");

    return 0;
}

static int atSocketUdpClientSendMultiple(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入udp服务器的IP, port ");
        return 0;
    }

    char *serviceIP = argv[2];
    int32_t servicePort = atoi(argv[3]);

    // 创建通讯的udp套接字(没有port, ip)
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    rlog_i("UDP客户端套接字sockfd: %d", sockfd);

    char buf[] = "This is a udp client test message";
    for (uint32_t i = 0; i < 4; i++)
    {
        struct sockaddr_in ser_addr = {
            .sin_family = AF_INET,
            .sin_port = htons(servicePort + i),     // 将主机字节序转换成网络字节序
            .sin_addr.s_addr = inet_addr(serviceIP) // 将服务器ip地址转换为32位整型数据
        };

        sendto(sockfd, buf, strlen(buf),
               0, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
    }

    // 关闭套接字
    closesocket(sockfd);
    return 0;
}

static int atSocketUdpClient(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入udp服务器的IP, port ");
        return 0;
    }

    char *serviceIP = argv[2];
    int32_t servicePort = atoi(argv[3]);

    // 创建通讯的udp套接字(没有port, ip)
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    rlog_i("UDP客户端套接字sockfd: %d", sockfd);

    // 定义一个IPv4地址结构, 存放服务器的地址信息(目标主机)
    struct sockaddr_in ser_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(servicePort),         // 将主机字节序转换成网络字节序
        .sin_addr.s_addr = inet_addr(serviceIP) // 将服务器ip地址转换为32位整型数据
    };

    char buf[] = "This is a udp client test message";
    sendto(sockfd, buf, strlen(buf),
           0, (struct sockaddr *)&ser_addr, sizeof(ser_addr));

    // 关闭套接字
    closesocket(sockfd);
    return 0;
}

static void atSocketTcpEchoTask(void *argument)
{
    struct clientEchoInfo echoInfo;
    struct clientEchoInfo *echoInfo2 = (struct clientEchoInfo *)argument;

    strcpy(echoInfo.serviceIP, echoInfo2->serviceIP);
    echoInfo.servicePort = echoInfo2->servicePort;

    int32_t result = 0;

    // 创建一个TCP套接字 SOCK_STREAM
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    rlog_i("TCP客户端套接字sockfd: %d", sockfd);

    // bind是可选的,这里使用,纯粹为了演示
    static uint16_t sin_port = 45876;
    struct sockaddr_in my_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(sin_port++),
        .sin_addr.s_addr = htonl(INADDR_ANY)};
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));

    // connect链接服务器
    struct sockaddr_in ser_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(echoInfo.servicePort),         // 服务器的端口
        .sin_addr.s_addr = inet_addr(echoInfo.serviceIP) // 服务器的IP
    };

    // 如果sockfd没有绑定固定的IP以及端口
    // 正常情况,在调用connect时候 系统给sockfd分配自身IP以及随机端口
    // 堆区此库atSocket实现,是在socket时进行绑定的
    result = connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
    if (0 != result)
    {
        rlog_i("connect错误, 目标ip: %s, 目标端口: %d, err code: %s", echoInfo.serviceIP, echoInfo.servicePort, strerror(errno));
        goto __exit;
    }

    char buf[512] = {0};
    while (1)
    {
        int32_t recvLen = recv(sockfd, buf, sizeof(buf), 0);
        if (recvLen > 0)
        {
            send(sockfd, buf, recvLen, 0);
        }
    }

__exit:
    // 关闭套接字
    closesocket(sockfd);
}

static int atSocketTcpClientEcho(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入tcp服务器的IP, port ");
        return 0;
    }

    static struct clientEchoInfo echoInfo = {0};
    strcpy(echoInfo.serviceIP, argv[2]);
    echoInfo.servicePort = atoi(argv[3]);

    rt_thread_t hid = rt_thread_create("tcpService",        // 线程name
                                       atSocketTcpEchoTask, // 线程入口函数
                                       (void *)&echoInfo,   // 线程入口函数参数
                                       2048,                // 线程栈大小
                                       16,                  // 线程优先级
                                       5);                  // 线程时间片

    if (NULL == hid)
    {
        rlog_w("创建tcp echo线程失败");
        return -1;
    }

    rt_thread_startup(hid);

    return 0;
}

static int atSocketTcpClient(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入tcp服务器的IP, port ");
        return 0;
    }

    char *serviceIP = argv[2];
    int32_t servicePort = atoi(argv[3]);

    int32_t result = 0;

    // 创建一个TCP套接字 SOCK_STREAM
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    rlog_i("TCP客户端套接字sockfd: %d", sockfd);

    // bind是可选的,这里使用,纯粹为了演示
    // !此库atSocket实现, 不推荐使用bind,使用bind会释放之前申请socket,重新申请。这是因为atSocket特性造成
    struct sockaddr_in my_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(44876),
        .sin_addr.s_addr = htonl(INADDR_ANY)};
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));

    // connect链接服务器
    struct sockaddr_in ser_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(servicePort),         // 服务器的端口
        .sin_addr.s_addr = inet_addr(serviceIP) // 服务器的IP
    };

    // 如果sockfd没有绑定固定的IP以及端口
    // 正常情况,在调用connect时候 系统给sockfd分配自身IP以及随机端口
    // 堆区此库atSocket实现,是在socket时进行绑定的
    result = connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
    if (0 != result)
    {
        rlog_i("connect错误, 目标ip: %s, 目标端口: %d, err code: %s", serviceIP, servicePort, strerror(errno));
        goto __exit;
    }

    char buf[] = "This is a tdp client test message";

    result = send(sockfd, buf, strlen(buf), 0);
    if (result < 0)
    {
        rlog_i("send错误, 目标ip: %s, 目标端口: %s, err code: %s", serviceIP, servicePort, strerror(errno));
        goto __exit;
    }

__exit:
    // 关闭套接字
    closesocket(sockfd);
    return 0;
}

static int atSocketGetHostByName(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入版本、带解析的域名信息。 版本1使用线程安全版本, 0非线程安全版本");
        return 0;
    }

    uint8_t choice = atoi(argv[2]);
    char *nameStr = argv[3];

    if (0 == choice)
    {
        struct hostent *hent;
        hent = gethostbyname(nameStr);

        if (NULL == hent)
        {
            rlog_e("gethostbyname error for hostname: %s", nameStr);
            return 0;
        }

        rlog_i("name: %s, addrtype: %d, AF_INET: %d, len:%d",
               hent->h_name, hent->h_addrtype, AF_INET,
               hent->h_length);

        for (uint8_t i = 0; hent->h_aliases[i]; i++)
            rlog_i("alias hostname: %s", hent->h_aliases[i]);

        for (uint8_t i = 0; hent->h_addr_list[i]; i++)
            rlog_i("host addr is: %s", inet_ntoa(*(struct in_addr *)hent->h_addr_list[i]));
    }

    else
    {
        char buf[512];
        int ret;
        struct hostent hostinfo, *phost;

        if (0 != gethostbyname_r(nameStr, &hostinfo, buf, sizeof(buf), &phost, &ret))
        {
            rlog_e("gethostbyname: %s, ret:%d", nameStr, ret);
            return 0;
        }

        rlog_i("name: %s, addrtype: %d, AF_INET: %d, len: %d",
               phost->h_name, phost->h_addrtype, AF_INET,
               phost->h_length);

        for (uint8_t i = 0; hostinfo.h_aliases[i]; i++)
            rlog_i("alias hostname: %s", hostinfo.h_aliases[i]);

        for (uint8_t i = 0; hostinfo.h_addr_list[i]; i++)
            rlog_i("host addr is: %s", inet_ntoa(*((struct in_addr *)hostinfo.h_addr_list[i])));
    }

    return 0;
}

static int atSocketGetAddrInfo(int argc, char *argv[])
{
    if (argc < 4)
    {
        rlog_w("请输入要解析的域名和端口");
        return 0;
    }

    char *nameStr = argv[2];
    char *namePort = argv[3];

    struct addrinfo *addrList = NULL,
                    *aip;

    struct addrinfo hints = {0};

    int result = getaddrinfo(nameStr, namePort, &hints, &addrList);
    if (0 != result)
    {
        rlog_e("getaddrinfo: %s ret:%d", nameStr, result);
        return 0;
    }

    struct sockaddr_in *sinp;
    const char *addr;
    char buf[40];

    for (aip = addrList; aip != NULL; aip = aip->ai_next)
    {
        sinp = (struct sockaddr_in *)aip->ai_addr;
        addr = inet_ntop(AF_INET, &sinp->sin_addr, buf, sizeof(buf));
        rlog_i("addr: %s, port: %d", addr ? addr : "unknow ", ntohs(sinp->sin_port));
    }

    if (NULL != addrList)
        freeaddrinfo(addrList);

    return 0;
}

/**
 * @brief mqtt msh命令
 *
 */
struct RyanatSocketCmdDes
{
    const char *cmd;
    const char *explain;
    int (*fun)(int argc, char *argv[]);
};

static int atSocketHelp(int argc, char *argv[]);

static struct RyanatSocketCmdDes cmdTab[] = {
    {"help", "打印帮助信息", atSocketHelp},
    {"start", "启动RyanatSocket", atSocketStart},
    {"udpClient", "atSocket udp客户端 param: ip, port", atSocketUdpClient},
    {"udpClientSendMultiple", "atSocket udp服务端echo测试 param: ip, port", atSocketUdpClientSendMultiple},

    {"tcpClient", "atSocket tcp客户端 param: ip, port", atSocketTcpClient},
    {"tcpClientEcho", "atSocket tcp客户端echo测试 param: ip, port", atSocketTcpClientEcho},

    {"gethostbyname", "atSocket 根据域名解析地址信息", atSocketGetHostByName},
    {"getaddrinfo", "atSocket 根据域名解析地址信息", atSocketGetAddrInfo}

};

static int atSocketHelp(int argc, char *argv[])
{

    for (uint8_t i = 0; i < sizeof(cmdTab) / sizeof(cmdTab[0]); i++)
        rt_kprintf("atSocket %-16s %s\r\n", cmdTab[i].cmd, cmdTab[i].explain);

    return 0;
}

static int atSocketMsh(int argc, char *argv[])
{
    int32_t i = 0,
            result = 0;
    struct RyanatSocketCmdDes *runCmd = NULL;

    if (argc == 1)
    {
        atSocketHelp(argc, argv);
        return 0;
    }

    for (i = 0; i < sizeof(cmdTab) / sizeof(cmdTab[0]); i++)
    {
        if (rt_strcmp(cmdTab[i].cmd, argv[1]) == 0)
        {
            runCmd = &cmdTab[i];
            break;
        }
    }

    if (runCmd == NULL)
    {
        atSocketHelp(argc, argv);
        return 0;
    }

    if (runCmd->fun != NULL)
        result = runCmd->fun(argc, argv);

    return result;
}

MSH_CMD_EXPORT_ALIAS(atSocketMsh, atSocket, atSocket command);

@mysterywolf mysterywolf requested a review from Rbb666 September 6, 2024 21:45
if (device->class->socket_ops->at_domain_resolve(name, ipstr) < 0)
{
return RT_NULL;
rt_mutex_release(at_dlock);
return -2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-2 表示什么意思?

@@ -1345,62 +1384,104 @@ static uint32_t ipstr_to_u32(char *ipstr)
return *(uint32_t *) ipBytes;
}

struct hostent *at_gethostbyname(const char *name)
static int gethostbyname_by_device(const char *name, ip_addr_t *addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要对这个函数的返回值增加注释说明

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants