2024年1月13日发(作者:)
竭诚为您提供优质文档/双击可除
lwiptcp接收处理函数
篇一:LwIp之三Tcp层发送相关
LwIp之Tcp层发送相关
20XX-05-1600:42:09
标签:
原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。否则将追究法律责任。/214870/158415
20XX-5-11LwIp之Tcp层发送相关
现在我们正式开始进入对Tcp的研究,它属于传输层协议,它为应用程序提供了可靠的字节流服务。在LwIp中基本的Tcp处理过程被分割为六个功能函数的实现:tcp_input(),tcp_process(),tcp_receive()【与Tcp输入有关】,tcp_write(),tcp_enqueue(),tcp_output()【用于Tcp输出】。这些是从大的方面来划分的。
1 28
现在先从小部tcp.c文件来分析一下:我们知道这里的函数都是被socket那一层的最终调用的。为了利于分析,我选择lwip_send函数来分析,具体不多说,最终调用到了
staticerr_tdo_writemore(structnetconn*conn)这个函数,当然这期间也做了不少工作,最主要的就是把发送数据的指针放到了msg的指定变量中
r=dataptr;//指针
=size;//长度
这些又经过转化放到了netconn的write_msg中
最后就是对do_writemore的调用了,下面详细分析这个函数。
这个函数的最直接调用有以下几个:
available=tcp_sndbuf(conn->);
err=tcp_write(conn->,dataptr,len,conn->write_msg->gs);err=tcp_output_nagle(conn->);
err=tcp_output(conn->);
好,先看tcp_sndbuf这个。从其参数,我们应该想像pcb的重要性。
#definetcp_sndbuf(pcb)((pcb)->snd_buf)//由下面分析得这里直接返回buf大小那就继续跟踪这个pcb好了。
2 28
是的,这还得从lwip_socket开始,在创建连接netconn的时候调用了do_newconn,它接着调用了pcb_new,在这个函数中如果是tcp的话有以下代码
msg->conn->=tcp_new();
哈哈,还记得吧,在前面我讨论到了这里,就没有再讨论了。嗯,现在开始吧。
*createsanewTcpprotocolcontrolblockbutdoesntplaceiton
*anyoftheTcppcblists.
*Thepcbisnotputonanylistuntilbindingusingtcp_bind().
structtcp_pcb*tcp_new(void)
{
returntcp_alloc(Tcp_pRIo_noRmAL);
}
也许注释的意义更大一些吧,哈哈,至此,我们从注释可以知道,tcp_bind的作用是把tcp的pcb放入list中,至少是某一个list
×××××××××××××××××××××××××××××××××××××××既然都提到了,似乎不说说有点过意不去啊。Lwid_bind函数最终会调用到
3 28
tcp_bind【当然是tcp为例进行分析】。这个函数也比较有意思,在进入正题之前先来了下面这么个调用
if(port==0)
{
port=tcp_new_port();
}
意思很明显,就是分配个新的端口号,有意思的就是这个端口号的分配函数tcp_new_portstaticu16_t
tcp_new_port(void)
{
structtcp_pcb*pcb;
#ifndefTcp_LocAL_poRT_RAnge_sTART
#defineTcp_LocAL_poRT_RAnge_sTART4096
#defineTcp_LocAL_poRT_RAnge_enD0x7fff
#endif
staticu16_tport=Tcp_LocAL_poRT_RAnge_sTART;
again:
if(++port>Tcp_LocAL_poRT_RAnge_enD){
port=Tcp_LocAL_poRT_RAnge_sTART;
}
for(pcb=tcp_active_pcbs;pcb!=nuLL;pcb=pcb->next){
4 28
if(pcb->local_port==port){
gotoagain;
}
}
for(pcb=tcp_tw_pcbs;pcb!=nuLL;pcb=pcb->next){
if(pcb->local_port==port){
gotoagain;
}
}
for(pcb=(structtcp_pcb*)tcp_listen_;pcb!=nuLL;pcb=pcb->next){if(pcb->local_port==port){
gotoagain;
}
}
returnport;
}分别检查了3个pcb链表,本来我不想把这个函数的实现列在这里的,但是这里告诉了我们一些东西,至少我们知道有3个pcb的链表,分别是tcp_active_pcbs【处于接受发送数据状态的pcbs】、tcp_tw_pcbs【处于时间等待状态的pcbs】、tcp_listen_pcbs【处于监听状态的pcbs】。
似乎tcp_bind函数更有意思,它分别检查了4个pcbs
5 28
链表,除了上面的三个还有一个tcp_bound_pcbs
【处于已经绑定但还没有连接或者监听pcbs】。作用是是否有与目前pcb的ipaddr相同的pcb存在。
不过这些都不是最重要的,Tcp_Reg(才是我们的目的,果然如此,直接加入到了tcp_bound_pcbs链表中了。
×××××××××××××××××××××××××××××××××××××××structtcp_pcb*tcp_alloc(u8_tprio)//是的,你没看错,这个参数是学名是叫优先级{
pcb=memp_malloc(memp_Tcp_pcb);
if(pcb!=nuLL)
{
memset(pcb,0,sizeof(structtcp_pcb));
pcb->prio=Tcp_pRIo_noRmAL;
pcb->snd_buf=Tcp_snD_buF;//对的,别的可以先不管,这就是我们要找的东西
pcb->snd_queuelen=0;
pcb->rcv_wnd=Tcp_wnD;
pcb->rcv_ann_wnd=Tcp_wnD;
pcb->tos=0;
pcb->ttl=Tcp_TTL;
6 28
/*Thesendmssisupdatedwhenanmssoptionisreceived.*/
pcb->mss=(Tcp_mss>536)?536:Tcp_mss;
pcb->rto=3000/Tcp_sLow_InTeRVAL;
pcb->sa=0;
pcb->sv=3000/Tcp_sLow_InTeRVAL;
pcb->rtime=-1;
pcb->cwnd=1;
iss=tcp_next_iss();
pcb->snd_wl2=iss;
pcb->snd_nxt=iss;
pcb->snd_max=iss;
pcb->lastack=iss;
pcb->snd_lbb=iss;
pcb->tmr=tcp_ticks;
#ifLwIp_cALLbAcK_ApI
pcb->recv=tcp_recv_null;
#endif/*LwIp_cALLbAcK_ApI*/
/*InitKeepALIVetimer*/
pcb->keep_idle=Tcp_KeepIDLe_DeFAuLT;
#ifLwIp_Tcp_KeepALIVe
pcb->keep_intvl=Tcp_KeepInTVL_DeFAuLT;
pcb->keep_cnt=Tcp_KeepcnT_DeFAuLT;
7 28
#endif/*LwIp_Tcp_KeepALIVe*/pcb->polltmr=0;
pcb->keep_cnt_sent=0;
}
returnpcb;
}就是一个tcp_pcb的结构体初始化过程,使用默认值填充该结构
好了,下面接着走,该轮到tcp_write了吧
*writedataforsending(butdoesnotsenditimmediately).
*Itwaitsintheexpectationofmoredatabeingsentsoon(as
*itcansendthemmoreefficientlybycombiningthemtogether).
*Topromptthesystemtosenddatanow,calltcp_output()after
*callingtcp_write().
【srccoretcp_out.c】
err_ttcp_write(structtcp_pcb*pcb,constvoid*data,u16_tlen,u8_tapiflags){
8 28
/*connectionisinvalidstatefordatatransmission?*/
if(pcb->state==esTAbLIsheD||
pcb->state==cLose_wAIT||
pcb->state==sYn_senT||
pcb->state==sYn_RcVD)
{
if(len>0)
{
returntcp_enqueue(pcb,(void*)data,len,0,apiflags,nuLL,0);
}
returneRR_oK;
}
else
{
returneRR_conn;
}
}
这个函数确实够简单的了,检查pcb状态,直接入队列。嗯,还是觉得看注释比看函数实现过瘾。下面的这个
9 28
tcp_enqueue才是个大头函数,lwip协议栈的设计与实现文档中是这么介绍的:应用层调用tcp_write()函数以实现发送数据,接着tcp_write()函数再将控制权交给tcp_enqueue(),这个函数会在必要时将数据分割为适当大小的Tcp段,然后再把这些Tcp段放到所属连接的传输队列中【pcb->unsent】。函数的注释是这样写的:
*enqueueeitherdataorTcpoptions(butnotboth)fortranmission
*calledbytcp_connect(),tcp_listen_input(),tcp_send_ctrl()andtcp_write().
咱们接着上面往下看,tcp_output_nagle这个函数实际上是个宏,通过预判断以决定是否调用tcp_output。也就是说最后的执行是tcp_output来实现的。这个函数或许比tcp_enqueue还要恐怖,从体积上来看。这里也只做一个简单介绍,具体请参阅tcp_out.c文档。该函数的注释是这么说的:Findoutwhatwecansendandsendit。文档是这么解释的:tcp_output函数会检查现在是不是
能够发送数据,也就是判断接收器窗口是否拥有足够大的空间,阻塞窗口是否也足够大,如果条件满足,它先填充未被tcp_enqueue函数填充的tcp报头字段,接着就使用
10 28
ip_route或者ip_output_if函数发送数据。
本文出自“bluefish”博客,请务必保留此出处/214870/158415
篇二:LwIp之socKeT的实现
LwIp之socKeT的实现
/214870/158413
Lwip协议栈的实现目的,无非是要上层用来实现app的socket编程。好,我们就从socket开始。为了兼容性,lwip的socket应该也是提供标准的socket接口函数,恩,没错,在srcincludelwipsocket.h文件中可以看到下面的宏定义:#ifLwIp_compAT_socKeTs
#defineaccept(a,b,c)lwip_accept(a,b,c)
#definebind(a,b,c)lwip_bind(a,b,c)
#defineshutdown(a,b)lwip_shutdown(a,b)
#defineclosesocket(s)lwip_close(s)
#defineconnect(a,b,c)lwip_connect(a,b,c)
#definegetsockname(a,b,c)lwip_getsockname(a,b,c)
#definegetpeername(a,b,c)lwip_getpeername(a,b,c)
#definesetsockopt(a,b,c,d,e)lwip_setsockopt(a,b,c,d,e)
11 28
#definegetsockopt(a,b,c,d,e)lwip_getsockopt(a,b,c,d,e)
#definelisten(a,b)lwip_listen(a,b)
#definerecv(a,b,c,d)lwip_recv(a,b,c,d)
#definerecvfrom(a,b,c,d,e,f)lwip_recvfrom(a,b,c,d,e,f)
#definesend(a,b,c,d)lwip_send(a,b,c,d)
#definesendto(a,b,c,d,e,f)lwip_sendto(a,b,c,d,e,f)
#definesocket(a,b,c)lwip_socket(a,b,c)
#defineselect(a,b,c,d,e)lwip_select(a,b,c,d,e)
#defineioctlsocket(a,b,c)lwip_ioctl(a,b,c)
#ifLwIp_posIx_socKeTs_Io_nAmes
#defineread(a,b,c)lwip_read(a,b,c)
#definewrite(a,b,c)lwip_write(a,b,c)
#defineclose(s)lwip_close(s)
先不说实际的实现函数,光看这些定义的宏,就是标准socket所必须有的接口。接着看这些实际的函数实现。这些函数实现在srcapisocket.c中。先看下接受连接的函数,这个是tcp的
原型:
12 28
intlwip_accept(ints,structsockaddr*addr,socklen_t*addrlen)可以看到这里的socket类型参数s,实际上是个int型
在这个函数中的第一个函数调用是sock=get_socket(s);
这里的sock变量类型是lwip_socket,定义如下:
/**containsallinternalpointersandstatesusedforasocket*/
structlwip_socket{
/**socketscurrentlyarebuiltonnetconns,eachsockethasonenetconn*/structnetconn*conn;
/**datathatwasleftfromthepreviousread*/
structnetbuf*lastdata;
/**offsetinthedatathatwasleftfromthepreviousread*/
u16_tlastoffset;
/**numberoftimesdatawasreceived,setbyevent_callback(),
testedbythereceiveandselectfunctions*/
13 28
u16_trcvevent;
/**numberoftimesdatawasreceived,setbyevent_callback(),
testedbyselect*/
u16_tsendevent;
/**socketflags(currently,onlyusedforo_nonbLocK)*/
u16_tflags;
/**lasterrorthatoccurredonthissocket*/
interr;
};
好,这个结构先不管它,接着看下get_socket函数的实现【也是在srcapisocket.c文件中】,在这里我们看到这样一条语句sock=很明显,返回值也是这个sock,它是根据传进来的序列号在sockets数组中找到对应的元素并返回该元素的地址。好了,那么这个sockets数组是在哪里被赋值了这些元素的呢?
进行到这里似乎应该从标准的socket编程的开始,也就是socket函数讲起,那我们就顺便看一下。它对应的实际实现是下面这个函数
Intlwip_socket(intdomain,inttype,intprotocol)
14 28
【srcapisocket.c】这个函数根据不同的协议类型,也就是函数中的type参数,创建了一个netconn结构体的指针,接着就是用这个指针作为参数调用了alloc_socket函数,下面具体看下这个函数的实现
staticintalloc_socket(structnetconn*newconn)
{
inti;
/*protectsocketarray*/
sys_sem_wait(socksem);
/*allocateanewsocketidentifier*/
for(i=0;i if(!sockets[i].conn){
sockets[i].conn=newconn;
sockets[i].lastdata=nuLL;
sockets[i].lastoffset=0;
sockets[i].rcvevent=0;
sockets[i].sendevent=1;/*Tcpsendbufisempty*/
sockets[i].flags=0;
sockets[i].err=0;
sys_sem_signal(socksem);
returni;
}
}
15 28
sys_sem_signal(socksem);
return-1;
}
对了,就是这个时候对全局变量sockets数组的元素赋值的。
既然都来到这里了,那就顺便看下netconn结构的情况吧。它的学名叫netconndescriptor
/**Anetconndescriptor*/
structnetconn
{
/**typeofthenetconn(Tcp,uDporRAw)*/
enumnetconn_typetype;
/**currentstateofthenetconn*/
enumnetconn_statestate;
/**thelwIpinternalprotocolcontrolblock*/
union{
structip_pcb*ip;
structtcp_pcb*tcp;
structudp_pcb*udp;
structraw_pcb*raw;
}pcb;
/**thelasterrorthisnetconnhad*/
16 28
err_terr;
/**semthatisusedtosynchroneouslyexecutefunctionsinthecorecontext*/sys_sem_top_completed;
/**mboxwherereceivedpacketsarestoreduntiltheyarefetched
bythenetconnapplicationthread(cangrowquitebig)*/
sys_mbox_trecvmbox;
/**mboxwherenewconnectionsarestoreduntilprocessed
bytheapplicationth(:lwiptcp接收处理函数)read*/
sys_mbox_tacceptmbox;
/**onlyusedforsocketlayer*/
intsocket;
#ifLwIp_so_RcVTImeo
/**timeouttowaitfornewdatatobereceived
(orconnectionstoarriveforlisteningnetconns)*/
intrecv_timeout;
#endif/*LwIp_so_RcVTImeo*/
#ifLwIp_so_RcVbuF
17 28
/**maximumamountofbytesqueuedinrecvmbox*/
intrecv_bufsize;
#endif/*LwIp_so_RcVbuF*/
u16_trecv_avail;
/**Tcp:whendatapassedtonetconn_writedoesntfitintothesendbuffer,thistemporarilystoresthemessage.*/
structapi_msg_msg*write_msg;
/**Tcp:whendatapassedtonetconn_writedoesntfitintothesendbuffer,thistemporarilystoreshowmuchisalreadysent.*/
intwrite_offset;
#ifLwIp_TcpIp_coRe_LocKIng
/**Tcp:whendatapassedtonetconn_writedoesntfitintothesendbuffer,thistemporarilystoreswhethertowakeuptheoriginalapplicationtaskifdatacouldntbesentinthefirsttry.*/
u8_twrite_delayed;
#endif/*LwIp_TcpIp_coRe_LocKIng*/
18 28
/**Acallbackfunctionthatisinformedabouteventsforthisnetconn*/netconn_callbackcallback;
};【srcincludelwipapi.h】
到此,对这个结构都有些什么,做了一个大概的了解。
下面以socK_sTReAm类型为例,看下netconn的new过程:
在lwip_socket函数中有
casesocK_DgRAm:
conn=netconn_new_with_callback((protocol==IppRoTo_uDpLITe)?neTconn_uDpLITe:neTconn_uDp,event_callback);#definenetconn_new_with_callback(t,c)
netconn_new_with_proto_and_callback(t,0,c)
简略实现如下:
structnetconn*
netconn_new_with_proto_and_callback(enumnetconn_typet,u8_tproto,netconn_callbackcallback)
{
structnetconn*conn;
structapi_msgmsg;
conn=netconn_alloc(t,callback);
19 28
if(conn!=nuLL)
{
on=do_newconn;
=proto;
=conn;
TcpIp_ApImsg(
}
returnconn;
}
主要就看TcpIp_ApImsg了,这个宏有两个定义,一个是LwIp_TcpIp_coRe_LocKIng的,一个非locking的。分别分析这两个不同类型的函数
*callthelowerpartofanetconn_*function
*ThisfunctionhasexclusiveaccesstolwIpcorecodebylockingit
*beforethefunctioniscalled.
err_ttcpip_apimsg_lock(structapi_msg*apimsg)【这个是可以locking的】{
LocK_TcpIp_coRe();
apimsg->function(
unLocK_TcpIp_coRe();
20 28
returneRR_oK;
}
*callthelowerpartofanetconn_*function
*Thisfunctionisthenrunninginthethreadcontext
*oftcpip_threadandhasexclusiveaccesstolwIpcorecode.
err_ttcpip_apimsg(structapi_msg*apimsg)【此为非locking的】
{
structtcpip_msgmsg;
if(mbox!=sYs_mbox_nuLL){
=TcpIp_msg_ApI;
=apimsg;
sys_mbox_post(mbox,
sys_arch_sem_wait(apimsg->->op_completed,0);
returneRR_oK;
}
returneRR_VAL;
}
其实,功能都是一样的,都是要对apimsg->function函数的调用。只是途径不一样而已。看看它们的功能说明就
21 28
知道了。这么来说apimsg->function的调用很重要了。从netconn_new_with_proto_and_callback函数的实现,可以知道这个function就是do_newconn
Voiddo_newconn(structapi_msg_msg*msg)
{
if(msg->conn->==nuLL){
pcb_new(msg);
}
篇三:lwip浅析tcp
一Tcp结构
Tcp报文头部的代码比特位(6bit,也有文档称之为flag标志位),有6种:
A.uRgurgentpointer紧急指针
b.ADKacknowledgenumber确认号
c.pshpush推送操作
D.RsTresetconnection复位连接
e.sYnsynchronizesequencenumber同步序列号
F.FInendofdata发送方字节流已发完
当建立一个新的连接时,sYn标志才会变1.而由于发送AcK无需任何代价,因为32bit的确认序列和AcK标志一样,总是Tcp首部的一部分,因此在建立连接之后,这个字段(AcK
22 28
和确认序列号)总是被设置。
二Tcp状态装换图
>
二Tcp控制块
structtcp_pcb{
Ip_pcb;//这是一个宏,描述了连接的Ip相关信息,包括双方Ip地址,TTL等信息structtcp_pcb*next;//Tcp控制块链表指针
enumtcp_statestate;//Tcp连接状态,为状态图中的描述状态。如图二
u8_tprio;//该控制块的优先级
void*callback_arg;//
u16_tlocal_port;//本地端口
u16_tremote_port;//远程端口
u8_tflags;//附加状态信息,如连接是快速恢复、一个延迟的AcK是否被发送等待#defineTF_AcK_DeLAY(u8_t)0x01u/*DelayedAcK.*///这些宏定义是flags字段#defineTF_AcK_now(u8_t)0x02u/*ImmediateAcK.*///定义的掩码
#defineTF_InFR(u8_t)0x04u/*Infastrecovery.*/
23 28
#defineTF_ReseT(u8_t)0x08u/*connectionwasreset.*/复位连接
#defineTF_cLoseD(u8_t)0x10u/*connectionwassucessfullyclosed.*/连接成功关闭#defineTF_goT_FIn(u8_t)0x20u/*connectionwasclosedbytheremoteend.*/收到FIn包(连接被远端关闭)
#defineTF_noDeLAY(u8_t)0x40u/*Disablenaglealgorithm*///是否启用nagle算法u32_trcv_nxt;//期望接收的下一个字节,它向发送端AcK的序号
u16_trcv_wnd;//接收窗口
u16_trcv_ann_wnd;//通告窗口大小
u32_ttmr;//该字段记录pcb创建的时间
u8_tpolltmr,pollinterval;//三个定时器
u16_trtime;//重传定时,该值随时间增加,大于等于RTo时(重传发生,RTo=RTo*2,重传次数tx达到最大次数是,终止定时器,重置连接)
u16_tmss;//mTu是1500字节,除去Tcp+Ip头的40个字节,真正的数据传输可以有1460,这就是所谓的mss,mss是每一个Tcp报文段中数据字段的最大长度,注意:只是数据部分的字段,不包括Tcp的头
24 28
/****beginRTT估计相关的参数***********/
u32_trttest;//估计得到的500ms的滴答数,tcp_ticks–rttest获取某次Tcp包往返的时间,用于计算RTT
u32_trtseq;//记录测试RTT用于往返的包的序号
s16_tsa,sv;//RTT估计出的平均值和方差
/****endRTT估计相关的参数***********/
u16_trto;//重传的时间,与rtime比较
u8_ttx;//重发的次数,该字段在数据包多次超时时被用到,与RTo有关系。
//快速重传/恢复相关的参数
u32_tlastack;//最大已经确认的序号
u8_tdupacks;//上面这个序号被重传的次数
//阻塞控制相关参数
u16_tcwnd;//连接的当前阻塞窗口
u16_tssthresh;//慢启动值
//发送相关参数
u32_tsnd_nxt,//下一个要发送的字节序号
snd_max,//最高的发送字节序号
snd_wnd,//发送窗口
snd_wl1,snd_wl2,//上次窗口更新时,记录的seqno,ackno
25 28
snd_lbb;//发送队列中最后一个字节序号
u16_tacked;//
u16_tsnd_buf;//可用的发送缓冲字节数
u8_tsnd_queuelen;//可用的发送包数
structtcp_seg*unsent;//未发送的数据队列
structtcp_seg*unacked;//未确认的数据队列
structtcp_seg*ooseq;//接收到序列以外的数据包队列
#ifLwIp_cALLbAcK_ApI//回调函数,用于接收,发送tcp包
err_t(*sent)(void*arg,structtcp_pcb*pcb,u16_tspace);
err_t(*recv)(void*arg,structtcp_pcb*pcb,structpbuf*p,err_terr);//
err_t(*connected)(void*arg,structtcp_pcb*pcb,err_terr);
err_t(*accept)(void*arg,structtcp_pcb*newpcb,err_terr);
err_t(*poll)(void*arg,structtcp_pcb*pcb);
26 28
void(*errf)(void*arg,err_terr);
#endif/*LwIp_cALLbAcK_ApI*/
//坚持保活定时器,用于接收方在对端窗口为0时,主动查询窗口是否为非0(避免返回AcK通告窗口大小时,AcK丢包,而一直死锁)
u32_tkeep_idle;//记录保活探测时间,长时间查询空闲时,定时查询对端是否关闭。#ifLwIp_Tcp_KeepALIVe
u32_tkeep_intvl;//
u32_tkeep_cnt;
#endif/*LwIp_Tcp_KeepALIVe*/
u32_tpersist_cnt;//定时记数,超出某值,发出探测包。
u8_tpersist_backoff;//坚持定时器是否开启
u8_tkeep_cnt_sent;//已经发送的保活数据包的个数
};
三Tcp滑动窗口
四Tcp超时重传
在Tcp两端交互过程中,数据和确认都有可能丢失。Tcp通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出时还没有收到确认,它就重传该数据。对任何Tcp协议实现而言,怎样决定超时间隔和如何确定重传的频率是提
27 28
高Tcp性能的关键。
这节讲解Tcp的超时重传机制,Tcp控制块tcp_pcb内部的相关字段为rtime、rttest、rtseq、sa、sv、rto、tx,太多了,先不要晕!
与超时时间间隔密切相关的是往返时间(RTT)的估计。RTT是某个字节的数据被发出到该字节确认返回的时间间隔。由于路由器和网络流量均会变化,因此RTT可能经常会发生变化,Tcp应该跟踪这些变化并相应地改变其超时时间。
在某段时间内发送方可能会连续发送多个数据包,但发送方只能选择一个发送包启动定时器,估计其RTT值,另外,一个报文段被重发和该报文的确认到来之前不应该更新估计器。协议中利用一些优化算法平滑RTT的值,并根据RTT值设置RTo的值,即下一个数据包的重传超时时间。
28 28


发布评论