# 重传程序 ## 主要socket和线程 ``` 如果不走tun的时候会启下面两个socket 接收源数据 g_Send.m_sRecvSocket // 源数据 接收socket g_usUdpRecvPort g_uiUdpRecvIp g_Send.m_sRecvSocket2 // 源数据 接收socket g_uiUdpRecvIp g_usUdpRecvPort2 g_Send.m_sPkgSendSocket // 重传数据包 发送socket g_uiPkgSendIp g_usPkgSendPort g_Send.m_sAckRecvSocket // 重传包接收应答socket g_uiPkgSendIp g_usAckRecvPort g_Recv.m_sRecvSocket // 重传数据包 接收socket g_uiPkgRecvIp g_usPkgRecvPort g_Recv.m_sAckSendSocket // 重传数据包 应答发送socket g_uiAckSendIp g_usAckSendPort 如果开启 SEND_BY_RAW static SOCKET m_hSocket IPPROTO_UDP g_strUdpSendIp g_usUdpSendPort 否则 g_Recv.m_sSendSocket g_uiUdpSendIp g_usUdpSendPort ``` ​ pthread_t tidRecv:pthread_create( &tidRecv, 0, ThreadRecverProcessRecv, E_RECV_RECV ); for ( UCHAR i = 0; i < g_Recv.m_ucSendNum; i++ ){ tidRevSen : pthread_create( &tidRevSen, 0, ThreadRecverProcessSend, (LPVOID)i); } for ( UCHAR i = 0; i < g_ucRetryNum; i++ ){ tidSendURetry : pthread_create( &tidSendURetry, 0, ThreadSenderProcessRetry, i); } if(g_ucRecvByTun == 0){ tidSendRecv : pthread_create(&tidSendRecv,0,ThreadSenderPorcessRecv, E_SEND_RECV); } tidACKTimer :pthread_create(&tidACKTimer ,0, AckTimerCB,NULL); tidSenAck :pthread_create(&tidSenAck ,0, ThreadSenderPorcessAck,E_SEND_ACK); tidStaticTimer:pthread_create(&tidStaticTimer,0, StaticTimerCB ,NULL ); tidHeartTimer :pthread_create(&tidHeartTimer,0,HeartbeatCheckCB ,E_SEND_RECV); tidStaticSec :pthread_create(&tidStaticSec,0, StaticSec,NULL ); ## tun数据接收 配置文件中`g_ucRecvByTun`或者`g_ucSendByTun`开启,会启动tun读取线程: ``` pthread_create(&tid1,NULL,do_read_fun,&g_index) ``` 在`do_read_fun`函数中进行tun数据的读取,读取出来的数据会放到`g_Send.m_pPkgBuf`里面。 `g_Send.m_pPkgBuf`的结构如下: ![](media/image-20240516133737362.png) 在do_read_fun函数中会把读取到的数据`g_buf[index-1]`传入g_readdatafunc函数,即SenderRecv函数 ```c int nread = read(g_fd[index-1],g_buf[index-1],ETH_FRAME_LEN); if (g_readdatafunc[index-1] != NULL){ g_readdatafunc[index-1](g_buf[index-1],nread,index); } ``` 注意:上面的在`read`函数中传入的缓存区`g_buf[index-1]`中,而`g_buf[index-1]`里面存放的缓存区如下: ```c //设置接收缓存 void SenderSetRecvBuffer(void) { g_uiAddr = g_Send.m_cLastPkgInfo[E_SEND_RECV].GetAddr(); g_uiAddr++; if(g_uiAddr > g_uiSendPkgNum) { g_uiAddr = 1; } /* * 从这里可以看出:g_buf[index-1] = g_Send.m_pPkgBuf[0].m_abyData * g_Send.m_pPkgBuf[1].m_abyData * …… * g_Send.m_pPkgBuf[g_uiSendPkgNum].m_abyData * 这里面的索引值就是 g_uiAddr 该值的来源为g_Send.m_cLastPkgInfo[E_SEND_RECV].GetAddr() * 把接收tun上数据发送出去后,更新该值,设置下个可用接收缓存区到g_buf[index-1]中,这个点后面会讲 */ SendPkg &tPkg = *( g_Send.m_pPkgBuf + g_uiAddr - 1 ); // 定位、地址偏移 setbuff(tPkg.m_abyData,1); { g_buf[index-1] = buff; } } ``` 有了上面的知识,我们接着分析,SenderRecv函数 ## tun上数据发送到对端准备 ```c void SenderRecv(unsigned char* buff,int recvlen,int index) { if ( g_bServiceRun ) { // 发送端接收数据写入缓存, 并发往接收端 UNIT_ADDR tFrom, tTo; tFrom.uiAddr = INADDR_ANY; tFrom.usPort = 0; tTo.uiAddr = INADDR_ANY; tTo.usPort = g_usUdpRecvPort; /* * 这就是上面在tun read中 已经把接收的数据放入到tPkg.m_abyData中 */ SendPkg &tPkg = *( g_Send.m_pPkgBuf + g_uiAddr - 1 ); /* * 该函数仅仅是填充 重传包 头信息 即填充 m_tHead */ tPkg.PutData(buff, recvlen, tFrom, tTo); { m_tHead.usDataLen = usLen; ULONGLONG timep = ULLGetLocalTime(); m_tHead.ullRecvTime = timep; m_tHead.uiSourceAddr = tFrom.uiAddr; m_tHead.usSourcePort = tFrom.usPort; m_tHead.uiTargetAddr = tTo.uiAddr; m_tHead.usTargetPort = tTo.usPort; m_tHead.ucRetryFlag = 0; m_tHead.ucCrc= CRC8((BYTE*)&m_tHead,sizeof(m_tHead)-sizeof(m_tHead.ucCrc)); } g_Send.m_cLastPkgInfo[E_SEND_RECV].SetInfo(g_uiAddr); // 位置更新 // g_ucRetryNum 存放的重传次数,一般我们设置为3次 if ( g_ucRetryNum ) { // 仅当重传检查空闲时,设置各线程开始位置 该部分单独分析 UINT uiNextAddr = g_Send.m_cLastPkgInfo[ 0 ].GetAddr(); if ( !uiNextAddr ) { printf( "[%u] Starting retry check.\n", E_SEND_RECV ); for ( UCHAR i = 0; i < g_ucRetryNum; i++ ) { g_Send.m_cLastPkgInfo[i].SetInfo( g_uiAddr ); } } } if ((g_lSendMaxLen == 0) || (g_lSenderSend < g_lSendMaxLen)) { // 该函数是把数据发送到对端 后面分析 g_Send.SendToRecv(g_uiAddr, tPkg); g_lSenderSend += recvlen + sizeof(PKG_HEAD); } // 设置下个可用的接收缓存 SenderSetRecvBuffer(); } } ``` 上面反复提到`g_Send.m_cLastPkgInfo[0 ].GetAddr()`函数,这里介绍下: ```c enum E_GROUP_TYPE { E_SEND_RECV = MAX_RETRY_NUM, // 发送端源数据接收线程 MAX_RETRY_NUM=5 E_SEND_ACK, // 发送端分包应答接收线程 E_SEND_MAX, E_RECV_RECV, // 接收端接收线程 E_RECV_SEND, // 接收端发送线程 }; ``` ![](media/image-20240521085153168.png) 接上面代码,假如我们设置重传次数为3 即`g_ucRetryNum=3` ```c if ( g_ucRetryNum ) { UINT uiNextAddr = g_Send.m_cLastPkgInfo[ 0 ].GetAddr(); if ( !uiNextAddr ) { printf( "[%u] Starting retry check.\n", E_SEND_RECV ); for ( UCHAR i = 0; i < g_ucRetryNum; i++ ) { g_Send.m_cLastPkgInfo[i].SetInfo( g_uiAddr ); } } } ``` 如果uiNextAddr=0(ps:刚开始就是0)执行完上面代码后就如下 ![](media/image-20240521090341182.png) 如果uiNextAddr=1 执行完上面代码后就如下 ![](media/image-20240521090438225.png) 所以综上所述: 1. tun上接收数据前(假设第一次),会首先调用一次`SenderSetRecvBuffer`函数 ```c g_uiAddr = g_Send.m_cLastPkgInfo[E_SEND_RECV].GetAddr(); //g_uiAddr=0 g_uiAddr++ SendPkg &tPkg = *( g_Send.m_pPkgBuf + g_uiAddr - 1 ); // 取 g_Send.m_pPkgBuf[0] g_buf[index-1] = tPkg.m_abyData; //此时g_uiAddr=1 ``` 目的是把`g_buf[index-1]=g_Send.m_pPkgBuf[0].m_abyData` 2. tun 上read函数,把数据从内核态 copy到 `g_buf[index-1]`中,即`g_Send.m_pPkgBuf[0].m_abyData`中 3. 然后调用`SenderRecv`函数: - 填充重传数据包头信息 `m_tHead` - 设置`g_Send.m_cLastPkgInfo[E_SEND_RECV].SetInfo(g_uiAddr)` 即`g_Send.m_cLastPkgInfo[5].SetInfo(1)` ![](media/image-20240521092121173.png) - 更新下面的: ```c uiNextAddr = g_Send.m_cLastPkgInfo[ 0 ].GetAddr() // 此时肯定为0 ``` 执行完 ![](media/image-20240521090341182.png) 4. 然后调用`g_Send.SendToRecv(g_uiAddr, tPkg);`函数把数据发送到对端 5. 然后再次调用`SenderSetRecvBuffer()`函数, ``` g_uiAddr = g_Send.m_cLastPkgInfo[E_SEND_RECV].GetAddr(); //g_uiAddr=1 g_uiAddr++ SendPkg &tPkg = *( g_Send.m_pPkgBuf + g_uiAddr - 1 ); // 取 g_Send.m_pPkgBuf[1] g_buf[index-1] = tPkg.m_abyData; //此时g_uiAddr=2 ``` 6. 如此反复循环 ## tun上数据发送到对端 下面主要分析`g_Send.SendToRecv(g_uiAddr, tPkg)`函数 ```c void SendCtrl::SendToRecv( UINT uiAddr, SendPkg &tPkg ) { // 准备将数据发往接收端 // 发送端统计 g_ulSenderTotal++; g_ulSenderRecvCount++; BOOL bSend = TRUE; if ( g_ucLostTest ) { if ( g_usLostCount >= g_usLostGroup ) { SetupLost(); } g_usLostCount++; USHORT usIndex = g_usLostCount - 1; if (g_abLost[ usIndex ] ) { bSend = FALSE; } } SOCKADDR_IN to; if( SEND_TRANS_MODE_ZUBO != g_ucSendTransMode ) { // 遍历接收端发送分包 PUNIT_ADDR pRecvAddr = m_pRecvAddrSet; PUNIT_HEART pUnitHeart = m_pHeartSet; UCHAR* pucAckFlag = tPkg.m_pucAckFlag; // SOCKADDR_IN to; /* 根据 m_ucRecvNum 数量 把数据 分发到不同对端 * 这里存在三个变量 * m_pRecvAddrSet: 解析RECV01里面的数据得到 * m_pucAckFlag : 解析RECV01里面的数据得到 * m_pHeartSet : * : */ for ( UCHAR i = 1; i <= m_ucRecvNum; i++, pRecvAddr++, pUnitHeart++, pucAckFlag++ ) { UNIT_ADDR& tRecvAddr = *pRecvAddr; UNIT_HEART& tUnitHeart = *pUnitHeart; UCHAR& tucAckFlag = *pucAckFlag; tPkg.Lock(); tucAckFlag = FALSE; // 默认为FALSE 指示该包是否需要重传的 tPkg.Unlock(); //printf( "sendto -> %u : %u.\n", tRecvAddr.uiAddr, tRecvAddr.usPort ); SETSOCKADDR( to, tRecvAddr.uiAddr, tRecvAddr.usPort ); if ( bSend && tUnitHeart.bUnitIsLive ) { tPkg.SendPkgToLock( m_sPkgSendSocket, to ); } } } else { // 这里是主播 先不考虑 遍历接收端发送分包 UCHAR* pucAckFlag = tPkg.m_pucAckFlag; // SOCKADDR_IN to; for ( UCHAR i = 1; i <= m_ucRecvNum; i++, pucAckFlag++ ) { UCHAR& tucAckFlag = *pucAckFlag; tPkg.Lock(); tucAckFlag = FALSE; tPkg.Unlock(); } //printf( "sendto -> %u : %u.\n", g_uiSendTransMcIp, g_usSendTransMcPort ); SETSOCKADDR( to, g_uiSendTransMcIp, g_usSendTransMcPort ); tPkg.SendPkgToLock( m_sPkgSendSocket, to ); } } ``` 上面提到的`m_pRecvAddrSet`、`m_pucAckFlag`和`m_pHeartSet`解析如下: ``` // 数据目标地址定义 typedef struct tagUnitAddr { UINT uiAddr; // 地址 USHORT usPort; // 端口 } UNIT_ADDR, *PUNIT_ADDR; // 目标心跳定义 typedef struct tagUnitHeart { UINT uiLastHeartTime; // 最后心跳时间 BOOL bUnitIsLive; // 心跳激活标志 } UNIT_HEART, *PUNIT_HEART; // 下面是重要的数据地址保存,针对多连接 g_Send.m_pRecvAddrSet = new UNIT_ADDR[ g_Send.m_ucRecvNum ]; g_Send.m_pHeartSet = new UNIT_HEART[ g_Send.m_ucRecvNum ]; RECV01格式如下: RECV01 = 对端ip:PKG_RECV_PORT:ACK_SEND_PORT RECV_NUM记录有多少个重传单元,每个重传单元从RECV01开始,一直到RECV_NUM数量,举例如下 RECV_NUM = 2 RECV01 = 10.129.43.7:24602:24603 RECV02 = 10.129.43.7:24602:24603 g_Send.m_pRecvAddrSet[0]-------》解析 RECV01 得到 {.uiAddr=对端ip .usPort=PKG_RECV_PORT} m_pRecvAddrSet[1]-------》解析 RECV02 得到 {.uiAddr=对端ip .usPort=PKG_RECV_PORT} m_pRecvAddrSet[……]------》解析 RECV…… 得到 {.uiAddr=对端ip .usPort=PKG_RECV_PORT} m_pRecvAddrSet[RECV_NUM]》解析 RECV0n 得到 {.uiAddr=对端ip .usPort=PKG_RECV_PORT} ``` 1. `tPkg.m_pucAckFlag` 一个数据包中就会有RECV_NUM个,代表着多个接收端 ![](media/image-20240521100255586.png) 2. `g_Send.m_pRecvAddrSet` 解析下面的数据, ``` # 接收端数目(范围1-100,默认1) RECV_NUM = 1 # 接收端信息:目标地址,分包接收端口,应答发送端口(默认127.0.0.1:20002:20003) RECV01 = 10.129.43.7:24602:24603 ``` 当接收tun上数据后,会遍历该地址进行 分别发送 3. `g_Send.m_pHeartSet` ## 对端接收处理 重传数据包接收线程ThreadRecverProcessRecv,对应的ip和端口如下: ![](media/image-20240521104500038.png) 接收过程如下: 1. 首先获取一个缓存区 ``` PkgUnit& tPkgUnit = *( g_Recv.m_pPkgUnitBuf + g_Recv.m_uiPkgUnitPutAddrNum - 1 ); ``` 默认初始化`g_Recv.m_uiPkgUnitPutAddrNum=1`的。所以这里相当于 ``` tPkgUnit = g_Recv.m_pPkgUnitBuf[0] ``` 注意:`g_Recv.m_pPkgUnitBuf`的结构如下: ![](media/image-20240521105232190.png) 2. recvfrom 接收数据 ```c recvlen = recvfrom( g_Recv.m_sRecvSocket, (char*)tPkgUnit.GetHeadAddr(), (int)tPkgUnit.GetPkgBufLen(), 0, (SOCKADDR*)&from, &fromlen ); ``` 接收的数据放到对应的PkgUnit中 3. 放入数据处理单元 ```c if ( g_Recv.ProcessPkgUnit( g_Recv.m_uiPkgUnitPutAddrNum, tHead, tFrom ) ) { // 接收缓存 移动下个空闲位置 g_Recv.m_uiPkgUnitPutAddrNum++; if ( g_Recv.m_uiPkgUnitPutAddrNum > g_uiRecvPkgTotalNum ) { g_Recv.m_uiPkgUnitPutAddrNum = 1; } } ``` 4. 接下来分析`g_Recv.ProcessPkgUnit`函数 遍历所有的接收单元,找到与接收数据对应的单元,进行处理 ```c BOOL RecvCtrl::ProcessPkgUnit( UINT uiPkgUnitAddrNum, PKG_HEAD& tHead, UNIT_ADDR& tFrom ) { RecvUnit* pUnit = m_pUnitSet; for ( UCHAR i = 1; i <= m_ucSendNum; i++, pUnit++ ) { RecvUnit& tUnit = *pUnit; // 找到接收单元 if ( tUnit.m_tSendAddr.uiAddr == tFrom.uiAddr && tUnit.m_tSendAddr.usPort == tFrom.usPort ) { bNotFound = FALSE; // 接收端接收分包写入缓存 tUnit.PutIntoIndex( uiPkgUnitAddrNum, tHead, i ); break; } } } ``` ``` g_Recv.m_ucSendNum = g_ucSendNum g_Recv.m_pUnitSet = new RecvUnit[ g_Recv.m_ucSendNum ]; SEND01 =对端ip:PKG_SEND_PORT:ACK_RECV_PORT g_Recv.m_pUnitSet = RecvUnit[0]-解析SEND01得到-》m_pIndexBuf = RecvIndex[0] [……] RecvIndex[g_uiRecvPkgNum] m_tSendAddr ={.uiAddr=对端ip .usPort=PKG_SEND_PORT} m_tAckAddr ={.uiAddr=对端ip .usPort=ACK_RECV_PORT} RecvUnit[2]-解析SEND02得到-》m_pIndexBuf = RecvIndex[0] [……] RecvIndex[g_uiRecvPkgNum] m_tSendAddr ={.uiAddr=对端ip .usPort=PKG_SEND_PORT} m_tAckAddr ={.uiAddr=对端ip .usPort=ACK_RECV_PORT} ``` ![](media/image-20240521115850980.png) 5. 接上分析,当找到接收单元后,开始把数据传给对应的接收单元处理 ```c void RecvUnit::PutIntoIndex( UINT uiPkgUnitAddrNum, PKG_HEAD& tHead, UCHAR ucUnitIndex ) { // 目标索引 RecvIndex &tIndex = *( m_pIndexBuf + tHead.uiAddrNum - 1 ); // 判断是否出现时间跳变 BOOL bTimeGapBig = FALSE; // 当前差值 LONGLONG llDelayTime; // 对非重传包进行校时处理 if ( !tHead.ucRetryFlag ) { ULONGLONG timep = ULLGetLocalTime(); // 对于新收到的分包, 需要考虑发送端与接收端的时差 //printf(" timep = %lld , tHead.ullRecvTime = %lld \n",timep,tHead.ullRecvTime); // 当前差值 llDelayTime = timep - tHead.ullRecvTime; //printf("llDelayTime = %lld , timep = %lld , tHead.ullRecvTime = %lld \n",llDelayTime,timep,tHead.ullRecvTime); // 时差变化量 LONGLONG llDelayHold = llDelayTime - m_llDelayFromSender; // 1毫秒 = 10,000 百纳秒 llDelayHold = llDelayHold;// / 10000; // 与发送端时差调整门限比较, 毫秒 if ( -llDelayHold >= g_uiDelayHold || llDelayHold >= g_uiDelayHold ) { bTimeGapBig = TRUE; } } // 更新索引 BOOL bRet = tIndex.UpdateIndex( uiPkgUnitAddrNum, bTimeGapBig ); // 记录下当前位置 SetNextRecvInfo( tHead.uiAddrNum, tHead.ullRecvTime, bTimeGapBig ); bRet &= ( m_cNextSendInfo.GetAddr() == tHead.uiAddrNum ); if ( !bRet && m_bRecvOverflow ) { printf( "[%u] recv_unit[%#2u] addr<%#6u> buffer over-write.\n" , E_RECV_RECV, ucUnitIndex, tHead.uiAddrNum ); m_bRecvOverflow = FALSE; m_cNextSendInfo.SetInfo( 0 ); } m_bRecvOverflow = bRet; // 发送应答 SendAck( tHead.uiAddrNum, tHead.ullRecvTime ); if ( bTimeGapBig ) { m_llDelayFromSender = llDelayTime; printf( "[%u] .... the time gap between recv_unit[%#2u][ %#6u, %#14llu ] and Sender[%s:%u] is %u ms.\n" , E_RECV_RECV, ucUnitIndex, tHead.uiAddrNum, tHead.ullRecvTime , m_achSendIp, m_tSendAddr.usPort, m_llDelayFromSender );// / 10000 ); m_cNextSendInfo.SetInfo( 0 ); } // 仅当接收缓存空闲时, 设置各线程开始位置 UINT uiNextAddr; uiNextAddr = m_cNextSendInfo.GetAddr(); if ( !uiNextAddr ) { printf( "[%u] recv_unit[%#2u] Starting send progress.\n", E_RECV_RECV, ucUnitIndex ); m_cNextSendInfo.SetInfo( tHead.uiAddrNum ); } } ``` 6. ## 对端通过tun把数据发送出去 在`ThreadRecverProcessSend`线程中,把数据通过tun发送出去 ```c void *ThreadRecverProcessSend( LPVOID lpThreadParameter ) { //SOCKADDR_IN to; struct timeval tv; DWORD dwTemp = reinterpret_cast( lpThreadParameter ); UCHAR ucUnitIndex = static_cast( dwTemp ); RecvUnit& tUnit = *( g_Recv.m_pUnitSet + ucUnitIndex ); UINT uiNextAddr, uiStopAddr; while( g_bServiceRun ) { //printf(" ProcessSend \n "); // 当前待处理分包的地址 uiNextAddr = tUnit.m_cNextSendInfo.GetAddr(); // 接收结束位置的分包地址(就是最后一个分包) uiStopAddr = tUnit.m_cNextRecvInfo.GetAddr(); //printf( "[%u] .... recv_unit[%#2u] 接收缓存[%#6u]-[%#6u].\n" // , E_RECV_SEND, ucUnitIndex, uiStopAddr, uiNextAddr); // 缓存是否空闲: // 1、条件1:已知待处理分包的地址; // 2、条件2:已知待处理分包的地址相对接收结束位置是不同的位置, 或者接收结束位置的分包(就是最后一个分包)还没有处理; if ( uiNextAddr && ( uiNextAddr != uiStopAddr || tUnit.m_bMoveNext ) ) { //printf(" ProcessSend 1 \n "); if ( !tUnit.m_bMoveNext ) { // 接收结束位置已改变, 发送应该处理下一个分包 uiNextAddr++; if ( uiNextAddr > g_uiRecvPkgNum ) { uiNextAddr = 1; } tUnit.m_cNextSendInfo.SetInfo( uiNextAddr ); // 默认分包地址递增, 待当前分包处理完之后再判断是不是最后一个分包 tUnit.m_bMoveNext = TRUE; } // 当前时间 ULONGLONG timep = ULLGetLocalTime(); // 定位当前索引 RecvIndex &tIndex = *( tUnit.m_pIndexBuf + uiNextAddr - 1 ); // 默认立即处理当前分包 BOOL bNoWait = TRUE; if ( tIndex.IsUsed() ) { PkgUnit& tPkgUnit = *( g_Recv.m_pPkgUnitBuf + tIndex.GetPkgUnitAddrNum() - 1 ); if ( ( tPkgUnit.GetHeadRecvTime() + tUnit.m_llDelayFromSender + g_ullDelayPeriod ) > timep ) { // 启动时间未到 bNoWait = FALSE; } } if ( bNoWait ) { tIndex.SendData( tUnit.m_tSendAddr, ucUnitIndex ); if ( ValidPrint( PRINT_FLAG_SPEED ) ) { if ( 0 == uiNextAddr % g_usGroupSize ) { DWORD dwNew1 = GetTickCount(); printf( "[%u] .... recv_unit[%#2u] ---- ---- ---> %#6u ticks, 起始 [%#6u], group of PKGs sent by Recver.\n" , E_RECV_SEND, ucUnitIndex, dwNew1 - tUnit.m_dwLastTick, uiNextAddr - g_usGroupSize + 1 ); tUnit.m_dwLastTick = dwNew1; } } if ( ValidPrint( PRINT_FLAG_GAP ) ) { if ( 0 == uiNextAddr % g_usGroupSize ) { UINT uiAddrCap = CalcAddrGap( uiNextAddr, uiStopAddr ); float fUsed = (float) 100 * uiAddrCap / (float) g_uiRecvPkgNum; if ( fUsed > tUnit.m_fMaxUsed ) { tUnit.m_fMaxUsed = fUsed; } printf( "[%u] .... recv_unit[%#2u] Recver buffer [%#6u]-[%#6u] length = %#6u(%#2.1f%%-%#2.1f%%).\n" , E_RECV_SEND, ucUnitIndex, uiStopAddr, uiNextAddr , uiAddrCap, fUsed, tUnit.m_fMaxUsed ); } } if ( uiNextAddr == uiStopAddr ) { // 所有接收的分包都已发送完, 不再继续处理下一个 tUnit.m_bMoveNext = FALSE; // only for test //printf( "last addr: send[%#6u], recv[%#6u].\n", g_Send.GetAddr(), uiNextAddr ); } else { uiNextAddr++; if ( uiNextAddr > g_uiRecvPkgNum ) { uiNextAddr = 1; } tUnit.m_cNextSendInfo.SetInfo( uiNextAddr ); } continue; // while() } // if () } // if ( && ) //tUnit.WaitNotifyHeart(); tv.tv_sec=0; //tv.tv_usec=1000; tv.tv_usec = g_uiSelectWaitUsec; select(0,NULL,NULL,NULL,&tv); } // while( g_bServiceRun ) return (void *)E_RECV_SEND; } ``` ## 多连接todo 假如有两台STA链接AC的时候,STA的配置如下: ![](media/image-20240522094202160.png) ![](media/image-20240522094027516.png) 1. STA1 ``` [SENDER] # 本地ip,用该ip发送 重传包 PKG_SEND_IP = 192.168.1.110 # 重传包数据发送端口 也就是对端的接收端口 PKG_SEND_PORT = 13502 # 接收端数目 对于STA来说,只有AC一个 RECV_NUM = 1 # 接收端信息:AC地址,AC分包接收端口,AC发送到本地应答包的发送端口 RECV01 = 10.129.43.7:24602:24603 [RECVER] # STA接收AC 重传包IP 本地IP PKG_RECV_IP = 192.168.1.110 # STA分包数据接收端口 对端AC数据包往这个端口发 PKG_RECV_PORT = 24602 # 发送端数目 SEND_NUM = 1 # 发送端信息:AC地址,AC分包发送端口,AC接收STA应答接收端口(默认127.0.0.1:10002:10003) SEND01 = 10.129.43.7:13502:13503 ``` 2. STA2 3. AC 4.