您尚未登录。

楼主 # 2024-03-07 10:20:35

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

我的板子当tcp client,连接pc上的tcp server。
我是用send()函数的返回值来判断tcp是否连接正常的。当返回值大于0时,这个值是发送的数据长度。
正常情况下,socket断开后,send()返回值是-1.

发现,会出现这种情况,pc端关掉tcp server软件。我的板子这里send()仍能成功,返回仍是数据长度。这是为什么呢?

环境是rtt+lwip。

离线

楼主 #1 2024-03-07 11:18:00

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

我现在是有两个线程,can定时采集数据,然后发送信号量,通知tcp线程进行数据发送。另还有互斥量,因为can采集数据放到了个全局结构体数组里,而tcp线程发送数据时用到这个结构体数组。

发现tcp server关闭后,板子的tcp send()仍能正常发送,我没法根据返回值判断tcp连接状态。在tcp server关闭后,板子tcp send()发送了大概5分钟以后,终端里不再打印tcp线程的打印信息了。感觉tcp线程被阻塞了。
查看can通知tcp的信号量,一直在增加。

离线

#2 2024-03-07 13:22:16

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 662
积分: 795
个人网站

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

有可能是tcp server的关闭流程不正确,导致操作系统内核没有将相应的tcp资源释放,此时客户端的send请求依然被操作系统响应了。

离线

楼主 #3 2024-03-07 13:46:09

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

海石生风 说:

有可能是tcp server的关闭流程不正确,导致操作系统内核没有将相应的tcp资源释放,此时客户端的send请求依然被操作系统响应了。

板子和pc都连到了局域网内的交换机上。为了验证这种可能性,pc上我关掉tcp server应用程序,板子串口打印tcp client仍能发送。在这种状态下,我拔掉了pc的网线。结果板子打印还是tcp client的send()还是返回发送数据的长度。

应该还是这个板子的问题。

离线

楼主 #4 2024-03-07 14:13:03

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

本来两个线程,can线程和tcp线程。有时候,会发现tcp线程不再打印信息,感觉像是阻塞了。
用list_sem看,多了个sem87这个信号量,阻塞了tcp线程。但我没建这个信号量啊。

semaphore        v   suspend thread
---------------- --- --------------
sem220           000 1:lwip_test_exampl
sem87            000 1:udp and tcp thre
sem6             000 0
sem3             000 0
can data sem     042 0
rxSem            000 1:canRxThread
can0tl           001 0
shrx             000 0
sem0             000 1:ping_thread
qspi0_s          000 0
heap_cma         001 0
heap_sys         001 0

--------------------------------

https://club.rt-thread.org/ask/question/822103778869d33f.html
在rtt论坛也搜到了类似问题,但没找到解决办法。
貌似就是lwip创建了个信号量,阻塞住了创建socket的这个线程。

最近编辑记录 Gentlepig (2024-03-07 14:37:16)

离线

楼主 #5 2024-03-08 13:58:08

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

list_sem看到的信号量列表里,sem加数字的这4个信号量,应该都是lwip建立的。但是不知道对应的哪几个。

src/api/tcpip.c:485:  err_t err = sys_sem_new(&call->sem, 0);
src/api/sockets.c:2026:      if (sys_sem_new(&API_SELECT_CB_VAR_REF(select_cb).sem, 0) != ERR_OK) {
src/api/sockets.c:2371:    if (sys_sem_new(&API_SELECT_CB_VAR_REF(select_cb).sem, 0) != ERR_OK) {
src/api/api_lib.c:1318:  err = sys_sem_new(API_EXPR_REF(API_VAR_REF(msg).sem), 0);
src/api/api_msg.c:752:  if (sys_sem_new(&conn->op_completed, 0) != ERR_OK) {
src/core/sys.c:139:    err_t err = sys_sem_new(&delaysem, 0);

-------------------------------------

今天试验,发现断开tcp server后,板子继续tcp send()能返回发送数据长度时,不一定有信号量阻塞tcp线程。

 /> list_sem
semaphore        v   suspend thread
---------------- --- --------------
sem126           000 1:lwip_test_exampl
sem53            000 0
sem6             000 0
sem3             000 0
can data sem     000 1:udp and tcp thre
rxSem            000 1:canRxThread
can0tl           001 0
shrx             000 0
sem0             000 1:ping_thread
qspi0_s          000 0
heap_cma         001 0
heap_sys         001 0

是这样持续了几分钟后,tcp线程才被一个信号量阻塞。

----------------------------------------------

这是sdk里网络驱动里,发送数据的底层函数:

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    aicmac_netif_t *aic_netif = (aicmac_netif_t *)netif;
    struct pbuf *q;
    aicmac_dma_desc_t *pdesc;
    int ret = ERR_OK;
#ifndef CONFIG_MAC_ZERO_COPY_TXBUF
    u8 *buffer;
    uint16_t framelength = 0;
    uint32_t bufferoffset = 0;
    uint32_t byteslefttocopy = 0;
    uint32_t payloadoffset = 0;
#else
    uint32_t p_cnt = 0;
    uint32_t p_type = 0;
    uint32_t empty_desc_cnt = 0;
    uint32_t index;
    uint32_t tmpreg = 0;
    uint32_t i = 0;
#endif

    pr_debug("%s\n", __func__);

    if ((netif == NULL) || (p == NULL)){
        pr_err("%s invalid parameter.\n", __func__);
        return ERR_MEM;
    }

    aicos_mutex_take(eth_tx_mutex, AICOS_WAIT_FOREVER);

    pdesc = dctl[aic_netif->port].tx_desc_p;
    /* before read: invalid cache */
    aicmac_dcache_invalid((uintptr_t)pdesc, sizeof(aicmac_dma_desc_t));

#ifndef CONFIG_MAC_ZERO_COPY_TXBUF
    buffer = (u8 *)(unsigned long)(pdesc->buff1_addr);
    bufferoffset = 0;

    for (q = p; q != NULL; q = q->next) {
        if ((pdesc->control & ETH_DMATxDesc_OWN) != (u32)RESET) {
            pr_err("%s no enough desc for transmit.(len = %u)\n", __func__, q->len);
            ret = ERR_MEM;
            goto error;
        }

        /* Get bytes in current lwIP buffer  */
        byteslefttocopy = q->len;
        payloadoffset = 0;

        /* Check if the length of data to copy is bigger than Tx buffer size*/
        while ((byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE) {
            /* Copy data to Tx buffer*/
            memcpy((u8_t *)((u8_t *)buffer + bufferoffset),
                   (u8_t *)((u8_t *)q->payload + payloadoffset),
                   (ETH_TX_BUF_SIZE - bufferoffset));
            /* after write: flush cache */
            aicmac_dcache_clean((uintptr_t)((u8_t *)buffer + bufferoffset),
                                (ETH_TX_BUF_SIZE - bufferoffset));

            /* Point to next descriptor */
            pdesc = (aicmac_dma_desc_t *)(unsigned long)(pdesc->buff2_addr);
            /* before read: invalid cache */
            aicmac_dcache_invalid((uintptr_t)pdesc, sizeof(aicmac_dma_desc_t));

            /* Check if the buffer is available */
            if ((pdesc->control & ETH_DMATxDesc_OWN) != (u32)RESET) {
                pr_err("%s no enough desc for transmit.(len = %u)\n", __func__, q->len);
                ret = ERR_MEM;
                goto error;
            }

            buffer = (u8 *)(unsigned long)(pdesc->buff1_addr);

            byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
            payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
            framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
            bufferoffset = 0;
        }

        /* Copy the remaining bytes */
        memcpy((u8_t *)((u8_t *)buffer + bufferoffset),
               (u8_t *)((u8_t *)q->payload + payloadoffset), byteslefttocopy);
        /* after write: flush cache */
        aicmac_dcache_clean((uintptr_t)((u8_t *)buffer + bufferoffset),
                            byteslefttocopy);
        bufferoffset = bufferoffset + byteslefttocopy;
        framelength = framelength + byteslefttocopy;
    }

    /* Prepare transmit descriptors to give to DMA*/
    aicmac_submit_tx_frame(aic_netif->port, framelength);
#else
    /* Count number of pbufs in a chain */
    q = p;
    while (q != NULL) {
        if (q->len > ETH_DMATxDesc_TBS1){
            pr_err("%s too large pbuf.(len = %d)\n", __func__, q->len);
            ret = ERR_MEM;
            goto error;
        }
        p_cnt++;
        q = q->next;
    }

    /* Scan empty descriptor for DMA tx */
    while (((pdesc->control & ETH_DMATxDesc_OWN) == (uint32_t)RESET) &&
           (empty_desc_cnt < ETH_RXBUFNB)) {

        empty_desc_cnt++;
        if (empty_desc_cnt >= p_cnt)
            break;

        /* Point to next descriptor */
        pdesc = (aicmac_dma_desc_t *)(unsigned long)(pdesc->buff2_addr);
        if (pdesc == dctl[aic_netif->port].tx_desc_unconfirm_p){
            pr_info("%s don't overwrite unconfirm area.\n", __func__);
            break;
        }

        /* before read: invalid cache */
        aicmac_dcache_invalid((uintptr_t)pdesc, sizeof(aicmac_dma_desc_t));
    }

    if (p_cnt > empty_desc_cnt){
        pr_err("%s no enough desc for transmit pbuf.(pbuf_cnt = %d, empty_desc = %d)\n",
                __func__, p_cnt, empty_desc_cnt);
        ret = ERR_MEM;
        goto error;
    }

    pbuf_ref(p);
    q = p;
    p_type = p->type_internal;
    for(i=0; i<p_cnt; i++){
        index = pdesc->reserved1;
        if (index >= ETH_RXBUFNB){
            pr_err("%s get dma desc index err.\n", __func__);
            pbuf_free(p);
            ret = ERR_MEM;
            goto error;
        }

        if (i == (p_cnt-1)){
            dctl[aic_netif->port].tx_buff[index] = p;
        }else{
            dctl[aic_netif->port].tx_buff[index] = NULL;
        }

        /* flush data cache */
        if (p_type == PBUF_POOL){
            aicmac_dcache_clean((uintptr_t)q->payload, q->len);
        }else{
            aicos_dcache_clean_range((unsigned long *)q->payload, q->len);
        }

        /* Set Buffer1 address pointer */
        pdesc->buff1_addr =
            (uint32_t)(unsigned long)(q->payload);
        /* Set frame size */
        pdesc->buff_size = (q->len & ETH_DMATxDesc_TBS1);
        /* after write: flush cache */
        aicmac_dcache_clean((uintptr_t)&pdesc->buff_size, 2*sizeof(uint32_t));

        /*set LAST and FIRST segment */
        tmpreg =  ETH_DMATxDesc_TCH;
        if (i == 0)
            tmpreg |= ETH_DMATxDesc_FS;
        if (i == (p_cnt-1))
            tmpreg |= ETH_DMATxDesc_LS | ETH_DMATxDesc_IC;
        /* TCP/IP Tx Checksum Insertion */
        if (mac_config[aic_netif->port].coe_tx)
            tmpreg |= ETH_DMATxDesc_CIC_TCPUDPICMP_Full;
        /* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
        tmpreg |= ETH_DMATxDesc_OWN;
        pdesc->control = tmpreg;
        /* after write: flush cache */
        aicmac_dcache_clean((uintptr_t)&pdesc->control, sizeof(uint32_t));

        /* Point to next descriptor */
        pdesc = (aicmac_dma_desc_t *)(unsigned long)(pdesc->buff2_addr);
        q = q->next;
    }

    dctl[aic_netif->port].tx_desc_p = pdesc;

    /* Resume DMA transmission */
    aicmac_resume_dma_tx(aic_netif->port);
#endif

error:
    /* Give semaphore and exit */
    aicos_mutex_give(eth_tx_mutex);

    return ret;
}

看代码,用rtt+lwip,建了两个线程,eth_rx和eth_phy_poll。
大概是这样的:
eth_phy_poll线程,检查phy状态,连上或没连上。个人感觉这里指的不是socket连接状态,可能是网线是否连通。
eth_rx线程,等待网口中断发送来的信号,根据其值,判断是接收中断,还是发送完成中断。然后进行响应处理。

实在想不出,tcp socket的连接状态,由哪里判断的。

最近编辑记录 Gentlepig (2024-03-08 15:51:08)

离线

楼主 #6 2024-03-09 09:36:37

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

现在感觉是我对tcp的连接/断开理解不正确。

尝试只用一个板子当作tcp client连接pc的tcp server,建立tcp连接后,pc端断开tcp server或直接关掉tcp server软件,板子作为tcp client的send()函数,都能返回-1,这和预想的一样。试验了很多次,都是这个结果。

但是当用两个板子都作为tcp client去连接pc的tcp server时,断开tcp server,两个板子的send()函数,至少有一个能返回-1。有时候两个都返回-1,有时候一个返回-1,另一个仍然返回发送的数据长度。

感觉tcp server端,不能保证和所有的tcp client实现完整的断开操作。

那么,tcp client端,就适合用send()返回值来判断tcp的连接状态。
还有其他办法来判断tcp连接状态吗?tcp client这里,我需要判断tcp连接状态退出tcp while循环,进行下一次重连操作。

离线

楼主 #7 2024-03-09 11:05:39

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

https://blog.csdn.net/awangdea99/article/details/107227193

服务端使用tcplistener接收连接请求。客户端使用tcpclient.connect主动连接。

在一对一的情况下(1个服务端只连接1个客户端时),服务端调用client.Close()主动关闭连接后,客户端接收函数(revString = br.ReadString();)立马报异常,因此可通过捕获此异常来进行重连操作。

但是,当一对多的情况下(1个服务端同时连接多个客户端),服务端对所有的client调用client.Close()主动关闭连接后,经常有少数(一般是一个)客户端无法捕获上面的异常,因此就无法通过捕获异常来重连。

为了解决上述问题,我试了网上很多方法,都不能解决此问题:

这是通病吗?

最近编辑记录 Gentlepig (2024-03-09 11:07:39)

离线

#8 2024-03-09 11:59:50

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 662
积分: 795
个人网站

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

socket有好多配置项(block/non-block/keep-alive等),API函数也有flags,说了一大堆也不见代码是什么鸟样。一般人都懒得给你猜……

"Talk is cheap. Show me the fcking code!" —— Linus Torvalds

最近编辑记录 海石生风 (2024-03-09 12:06:35)

离线

楼主 #9 2024-03-09 15:01:10

Gentlepig
会员
注册时间: 2018-10-24
已发帖子: 1,379
积分: 1344.5

Re: tcp client连接tcp sever,当tcp sever关闭后,tcp client仍能发送成功?

海石生风 说:

socket有好多配置项(block/non-block/keep-alive等),API函数也有flags,说了一大堆也不见代码是什么鸟样。一般人都懒得给你猜……

"Talk is cheap. Show me the fcking code!" —— Linus Torvalds

感谢,tcp client设置了keepalive后,如果server主动断开,过一会client会自动判断为断开了。
哈,问题已解决。折腾了我好几天。

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn