我的板子当tcp client,连接pc上的tcp server。
我是用send()函数的返回值来判断tcp是否连接正常的。当返回值大于0时,这个值是发送的数据长度。
正常情况下,socket断开后,send()返回值是-1.
发现,会出现这种情况,pc端关掉tcp server软件。我的板子这里send()仍能成功,返回仍是数据长度。这是为什么呢?
环境是rtt+lwip。
离线
我现在是有两个线程,can定时采集数据,然后发送信号量,通知tcp线程进行数据发送。另还有互斥量,因为can采集数据放到了个全局结构体数组里,而tcp线程发送数据时用到这个结构体数组。
发现tcp server关闭后,板子的tcp send()仍能正常发送,我没法根据返回值判断tcp连接状态。在tcp server关闭后,板子tcp send()发送了大概5分钟以后,终端里不再打印tcp线程的打印信息了。感觉tcp线程被阻塞了。
查看can通知tcp的信号量,一直在增加。
离线
有可能是tcp server的关闭流程不正确,导致操作系统内核没有将相应的tcp资源释放,此时客户端的send请求依然被操作系统响应了。
板子和pc都连到了局域网内的交换机上。为了验证这种可能性,pc上我关掉tcp server应用程序,板子串口打印tcp client仍能发送。在这种状态下,我拔掉了pc的网线。结果板子打印还是tcp client的send()还是返回发送数据的长度。
应该还是这个板子的问题。
离线
本来两个线程,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)
离线
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)
离线
现在感觉是我对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循环,进行下一次重连操作。
离线
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)
离线
socket有好多配置项(block/non-block/keep-alive等),API函数也有flags,说了一大堆也不见代码是什么鸟样。一般人都懒得给你猜……
"Talk is cheap. Show me the fcking code!" —— Linus Torvalds
感谢,tcp client设置了keepalive后,如果server主动断开,过一会client会自动判断为断开了。
哈,问题已解决。折腾了我好几天。
离线