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