linux服务器接收发送报文
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux服务器接收发送报文相关的知识,希望对你有一定的参考价值。
Java项目部署在linux上,然后通过客户端web页面访问服务器并发送报文(可以理解为服务器自己给自己发送报文,然后自己给自己回报文),我这边发送和服务器接收以及服务器发送都没问题,但Java项目好像并没有接收到报文,返回一个服务器未知异常。我在本地测试的时候(未部署到服务器上)往服务器发送接收都很正常,就是部署到linux以后死活接收不到报文。求高人指点...报文是TCP的
try
input = socket.getInputStream();
catch (SocketTimeoutException e)
return "TCP_ERROR_009:ͨ通讯超时";
catch (IOException e)
return "TCP_Error_007:" + e.getMessage();
finally
try
recv = getString(input);
System.out.println("Recive[" + recv + "]");
input.close();
output.close();
socket.close();
catch (SocketTimeoutException e)
return "TCP_ERROR_009:ͨ通讯超时";
catch (IOException e)
return "TCP_ERROR_005:关闭连接时出现IO异常" + e.getMessage();
catch (Exception e)
return "TCP_ERROR_008:" + e.getMessage();
输出到控制台的结果是:TCP_ERROR_005:关闭连接时出现IO异常
少粘了一个方法:
private static String getString(InputStream input)
throws SocketTimeoutException, IOException
StringBuffer readstr = new StringBuffer();
BufferedReader theInputStream = new BufferedReader(
new InputStreamReader(input));
String readline = null;
while ((readline = theInputStream.readLine()) != null)
readstr.append(readline);
return readstr.toString();
继承的TCP功能的基础上,SCTP提供了一些额外的功能:
1有序传输中的用户数据的多个“流”(流)
“流”是一系列的TCP中指的字节,而在SCTP是指某个系列的用户消息被发送到上层协议,这些消息的顺序与在流之内的其他消息。当建立关联,SCTP的用户的数据流的数目,可被提供给相关联的载体。此号码被商定的流的??数目与源端的用户相关联的消息。 SCTP为每个邮件中的链接发送给同行的流分配的序列号。在接收端,SCTP,以确保在一个给定的消息流的顺序被发送。另一方面,当工作流正在等待的下一个非顺序的用户消息的其他数据流的发送时,将继续下去。
2根据所发现的路径MTU(最大传输单元)的大小的用户数据切片
为了确保一致的SCTP报文发送到较低的路径MTU,SCTP用户消息分得一杯羹。传递给上部SCTP用户在接收端,切片重组。
3选择性确认(SACK)和拥塞控制
选择性确认数据包丢失,TCP序列号被返回给发送者已经成功地接收到的数据字节的序列号(不包括根据确认的字节数)的认可,并在SCTP反馈给发件人丢失,并要求序列号的消息重发。
SCTP使用的TCP拥塞控制技术,包括慢启动,拥塞避免和快速重传。因此,当一个共存和TCP应用程序时,SCTP的应用程序可以接收部分SCTP的网络资源。
4块(块)结合。
选择性地绑定到??SCTP包,即多个用户消息的消息发送到一个或多个数据结构的SCTP - “块”,SCTP储备应用程序消息传递框架边界。不同类型的块可以绑定到一个SCTP报文,但任何一个数据块之前,必须放在控制块。
5路径管理
SCTP路径管理功能主要是负责为目的地的运输提供了一个选择的目标地址的传输地址的远程地址,它是基于两个方面:SCTP用户的说明和合格的目的地。当其他流量控制不能提供可达性信息,定期扫描路径管理功能链接到SCTP报告在远程传输地址发生变化的可达性。 SCTP路径管理功能模块还负责建立链接,该报告的末端的本地地址,传输地址告诉SCTP用户的远程回报。
6,支持多归位
当SCTP传输的数据包的目的IP地址,如果IP地址是不可达的,SCTP消息重新路由到备用的IP地址。因此,在相关联,即使在两端的,可以容忍网络级别的错误的一端。
7对拒绝服务攻击(DoS)
DoS攻击的方法有很多种,最基本的DoS攻击就是利用合理的服务请求来占用过多的资源,从而使合法用户无法得到服务的响应。 SYN洪水攻击是一种拒绝服务攻击实例,是最好的方式了黑客攻击。针对SYN Flooding攻击的目标主机上,SCTP关联的初始化阶段,实施以“Cookie”的安全机制。
8支持多种传输模式
严格有序转移(如TCP)的有序转移(如每流)和无序传输(如UDP)的一部分。
2 SCTP报文结构
SCTP分组结构的数据分组,第一部分可以遵循由可变长度的数据块中的一个或多个。块类型 - 长度 - 值(TLV)格式。源端口,目的端口,校验的意义是与TCP类似的意义。确认标签保存价值的交流,第一次在SCTP握手初始标签。如果任何SCTP报文不包括联想这样的标签,当到达的时间将是在接收端丢弃。
包含的块类型,标记的转让处理,在每个块中的块长度,TLV。不同的块类型,可用于发送控制信息或数据。
发送序列号(TSN)和流序列号(SSN)是两个不同的序列号,TSN,以确保可靠性整个关联的SSN保证整个流的有序性,因此,在发送的数据的可靠性订货区分开来。
3 SCTP数据传输
4.1 SCTP四次握手的原则,抵制SYN洪水攻击
SCTP关联定义为:主机A的IP地址] + [主机的端口] + [IP地址的主机B] + [B主机端口。因此,相应的组的每个端部中的IP地址的任何一个可以是标记相关的,通过四向握手,作为相应的源/目的地地址和结束SCTP主机交换通信状态。
SYN洪水利用所固有的脆弱性,TCP / IP,TCP三次握手面向连接的SYN洪水的存在基础。 SYN Flooding攻击原理是:大量的恶意攻击者向服务器发送一个SYN包,服务器发出一个SYN + ACK数据包无法收到客户端的ACK包(第三次握手无法完成),服务器端将保持一个非常大名单的半连接,消耗大量的CPU时间和内存资源,也能保持此列表中的IP SYN + ACK的重试。服务器端将忙于处理攻击者伪造的TCP连接请求以及没有时间忽略正常的客户请求,从正常的客户的角度来看,服务器失去了响应。
在SCTP四次握手的INIT消息,接收端不保存任何状态信息或分配的任何资源,这样你就可以防止DoS攻击,如SYN洪水。 INIT-ACK消息发送,使用了一种机制 - “状态曲奇”的cookie的发送者建立自己的国家所需的全部信息。
SCTP产生一个Cookie状态过程如下:
1。收到的INIT发出的INIT ACK数据块的信息来创建一个关联的TCB(传输控制块)。
在TCB中,生存在协议参数设置为“有效的Cookie时间,创建日期设置为当前日期。
3根据TCB收集重建的TCB所需的最小的子集的信息,这个子集和密钥来产生一个MAC(消息认证码)。
用最小的子集的信息和MAC产生状态Cookie。
5。在发送的INIT ACK(含状态cookie参数),发送者必须删除TCB,以及任何相关的新的关联的本地资源。
INIT和INIT ACK必须包含建立初始状态所需的参数:一组IP地址,以确保可靠的传输的初始TSN,每一个收到的SCTP包中必须包含初始标签,每一端的请求发出的数据流的数量并在每一端可以支持接收的数据流的数量。交换这些消息,INIT COOKIE-ECHO消息的发送者被送回的状态Cookie。接收端在接收COOKIE-ECHO饼干的状态,完成重建自己的国家和回送COOKIE-ACK确认该协会已成立。 COOKIE-ECHO和COOKIE-ACK的用户数据信息可以绑定到每个包。
因此,使用上述的以这样的方式,即使接收INIT消息,接收终端,也没有任何的资源消耗:它既不分配任何系统资源,并且不保存的新的关联的状态,它是只对口援建的状态状态的Cookie作为一个参数,它包含每一个回送的INIT-ACK消息,并最终状态cookie COOKIE-ECHO消息发送回。
2.2 SCTP的数据交换
正常的两个SCTP主机之间的数据交换。 SCTP主机发送SACK块,用来确认每一个收到的SCTP报文。 SACK的完全描述的接收侧的状态,可以使发送侧决定的重发,因此,在根据对SACK。 SCTP支持TCP快速重传和超时重传算法类似。
SCTP和TCP数据包丢失,使用不同的机制:TCP序列号空缺已被填补缺口,直到收到,发送丢失的数据包数据的序列号是高于之前。但是,SCTP即使收到订单的序列号空缺,并会不断发回数据。
3.3 SCTP关联关闭
面向连接的传输协议,SCTP还可以使用与TCP的三次握手关闭相关,但有一点不同:在“关联关闭”的过程中保持连接打开一个TCP终端,新的数据来自对等体,但不支持TCP SCTP这个“半封闭”状态。 1日发布由主机A“OFF”(关闭)块终止与主机B,主机A就会进入“SHUTDOWN-PENDING”状态,相应的动作是:不再接受上层应用的数据,并且只发送队列中剩余的数据,进入“SHUTDOWN-SENT”状态。
一旦主机B接收到“OFF”挡,进入“SHUTDOWN-RECEIVED”状态,与主机A,不再接受上层应用的数据,只发送队列中剩余的数据。
主机A再发送“关闭”块,剩余的数据已经达到主机B发出的通知,并重申,该协会正在关闭。
当第二个获得“关闭”块,主机B发送确认关闭“块。
随后的主机发送“关闭”结束“块完成关闭的关联。
4结论
SCTP是开发用于传输信令流量,但它有一定的优势,先进的TCP协议机制,如选择性确认,快速重传,无序提交,因此,它也满足高性能传输的需求,这将赋予它更广泛的应用的要求。目前,有各种各样的操作系统都支持SCTP,如Linux,AIX和Solaris上,Windows中,FressBSD。不同的协议之间的互操作性测试取得成功,揭示了SCTP正走向商业产品的道路。
IEFT致力于SCTP进一步的,以使其更好地满足下一代应用的需求,如支持IPv6地址,解决对端的IPv6站点本地和链路本地地址不连接的问题,以及在现有的关联动态添加或删除IP地址,而无需重新启动关联。
此外,在第三代移动通信,SCTP信令承载层的选择之一,它的应用及其性能评价是还待研究。
参考文献:
TCP/IP传输层协议实现 - TCP报文接收/发送(lwip)
(tcp的收发与接收窗口/发送窗口/通告窗口关联比较紧密,接收/发送过程在《TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)》https://blog.csdn.net/arm7star/article/details/117153533 都有介绍,本文对收发过程进行更详细一步介绍。)
1、滑动窗口
1.1、接收窗口(接收滑动窗口)
接收窗口是本地可以接收数据的窗口,接收端只接收窗口内的数据,窗口外的丢弃。
接收到数据,接收窗口左边沿右移,接收窗口减小。
(1)、接收到数据前(4、5、6、7、8、9可接收) | (2)、接收窗口左边沿右移,接收窗口减小(收到4、5、6数据,可接收7、8、9) |
数据被应用层接收,接收窗口大小恢复,接收窗口右边沿右移。
(1)、应用层接收数据前(4、5、6待应用层接收,可接收7、8、9) | (2)、接收窗口恢复,接收窗口右边沿右移(4、5、6被应用层接收,恢复3个字节数据,接收窗口大小恢复3个字节,右边沿右移) |
1.2、发送窗口(发送滑动窗口)
发送窗口与接收窗口类似,发送窗口内的数据才能发送。不考虑拥塞窗口的前提下,如下图所示,接收方通告接收窗口大小为6个字节大小(接收方还可以再接收6个字节的数据,发送方只能再发送6个字节的数据),发送方发送的1、2、3被接收方接收并被确认,发送方的4、5、6已经发送但未被确认,7、8、9为能够发送的数据,10、11不能发送(发送窗口外的数据需要发送时,协议栈会缓存在发送缓存里面,10、11虽然不能发送,但是如果发送缓存够的话,会缓存到发送缓存里面,当数据被确认时,发送窗口移动,缓存的数据如果在发送窗口内,那么就可以发送)。
2、报文接收
tcp报文输入调用tcp_input函数,tcp_input依次查找tcp_active_pcbs、tcp_tw_pcbs、tcp_listen_pcbs链表,已连接的tcp信息保存在tcp_active_pcbs里面(收发双方的IP地址及端口)。(连接建立与终止参考https://blog.csdn.net/arm7star/article/details/116560454 《TCP/IP传输层协议实现 - TCP连接的建立与终止(lwip)》)
2.1、报文解析接收
(释放已被确认接收的报文,更新发送缓存等,接收数据...)
获取tcp首部。
iphdr = p->payload; // 获取ip首部
tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); // 获取tcp首部
解析tcp首部。(源地址,目的地址、seqno、ackno、flags、tcplen)
/* Convert fields in TCP header to host byte order. */
tcphdr->src = ntohs(tcphdr->src);
tcphdr->dest = ntohs(tcphdr->dest);
seqno = tcphdr->seqno = ntohl(tcphdr->seqno);
ackno = tcphdr->ackno = ntohl(tcphdr->ackno);
tcphdr->wnd = ntohs(tcphdr->wnd);
flags = TCPH_FLAGS(tcphdr);
tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);
初始化接收报文的tcp_seg。(输入报文长度、报文数据指针、tcp首部,对于接收窗口内的可接收的报文,输入的报文会拷贝到inseg;如果数据可接受,那么recv_data会指向可接受的数据,recv_flags设置为可接受报文的flags)
/* Set up a tcp_seg structure. */
inseg.next = NULL;
inseg.len = p->tot_len;
inseg.dataptr = p->payload;
inseg.p = p;
inseg.tcphdr = tcphdr;
recv_data = NULL; // 可接受数据的指针(如果期待的下一个序号为4,那么序号为4的报文即为可接受的报文)
recv_flags = 0;
检查是否有被应用层拒绝接收的数据refused_data。(收到可接受的数据recv_data,协议栈会将数据推送到应用层,应用层因为某些原因没办法接收该数据,那么被拒绝接收的recv_data数据会保存在refused_data里面;处理报文前需要先检查是否有被拒绝接收的refused_data数据,如果有,那么需要再次将refused_data推送到应用层,推送成功才处理当前收到的报文(如果推送失败的话,应用层应该没办法处理更多数据,即使当前输入报文可能有数据要接收,但是应用层没办法接收,协议栈直接丢弃该报文即可,让发送方超时重传))
/* If there is data which was previously "refused" by upper layer */
if (pcb->refused_data != NULL) {
/* Notify again application with data previously received. */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: notify kept packet\\n"));
TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err); // 发送数据到应用层
if (err == ERR_OK) { // 发送成功
pcb->refused_data = NULL; // 重置refused_data
} else {
/* drop incoming packets, because pcb is "full" */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: drop incoming packets, because pcb is \\"full\\"\\n"));
TCP_STATS_INC(tcp.drop);
snmp_inc_tcpinerrs();
pbuf_free(p); // 发送数据到应用层失败,释放报文,直接丢弃报,返回
return;
}
}
调用tcp_process处理输入报文(报文标志位、连接状态检查,tmr定时器复位、KEEPALIVE counter重置,处理tcp报文选项),ESTABLISHED状态下的连接调用tcp_receive处理输入报文。
发送窗口更新。(细节参考https://blog.csdn.net/arm7star/article/details/117153533)
if (flags & TCP_ACK) {
right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;
......
重复ack处理,3个以上重复ACK执行快速恢复算法(快速重传、拥塞避免)。(《TCP-IP详解卷 1:协议》21.7 快速重传与快速恢复算法)
if (pcb->lastack == ackno) { // 没有新的数据被确认接收
pcb->acked = 0; // 被确认的数据为0
if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){ // 重复ACK判断
++pcb->dupacks;
if (pcb->dupacks >= 3 && pcb->unacked != NULL) { // 如果一连串收到3个或3个以上的重复ACK,就非常可能是发送的报文段丢失了
if (!(pcb->flags & TF_INFR)) {
/* This is fast retransmit. Retransmit the first unacked segment. */
LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupacks %"U16_F" (%"U32_F"), fast retransmit %"U32_F"\\n",
(u16_t)pcb->dupacks, pcb->lastack,
ntohl(pcb->unacked->tcphdr->seqno)));
tcp_rexmit(pcb); // 重传丢失的数据报文段,而无需等待超时定时器溢出
/* Set ssthresh to max (FlightSize / 2, 2*SMSS) */
/*pcb->ssthresh = LWIP_MAX((pcb->snd_max -
pcb->lastack) / 2,
2 * pcb->mss);*/
/* Set ssthresh to half of the minimum of the current cwnd and the advertised window */
if (pcb->cwnd > pcb->snd_wnd)
pcb->ssthresh = pcb->snd_wnd / 2;
else
pcb->ssthresh = pcb->cwnd / 2; // 慢启动门限ssthresh设置为拥塞窗口的一半
/* The minimum value for ssthresh should be 2 MSS */
if (pcb->ssthresh < 2*pcb->mss) { // 慢启动门限小于2个mss
LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: The minimum value for ssthresh %"U16_F" should be min 2 mss %"U16_F"...\\n", pcb->ssthresh, 2*pcb->mss));
pcb->ssthresh = 2*pcb->mss; // 设置慢启动门限为2个mss
}
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; // 拥塞窗口设置为pcb->ssthresh + 3 * pcb->mss(拥塞窗口大于慢启动门限ssthresh时,执行拥塞避免算法)
pcb->flags |= TF_INFR; // 快速恢复算法(In fast recovery)
} else {
/* Inflate the congestion window, but not if it means that
the value overflows. */
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
}
}
}
} else {
LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupack averted %"U32_F" %"U32_F"\\n",
pcb->snd_wl2 + pcb->snd_wnd, right_wnd_edge));
}
} else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){
有数据被确认接收。(ackno >= pcb->lastack + 1 && ackno <= pcb->snd_nxt)
} else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){ // ackno确认(, ackno - 1]区间的数据被接收,[pcb->lastack, pcb->snd_nxt - 1]区间的数据等待被确认,因此有数据被确认的话,ackno - 1 >= pcb->lastack并且ackno - 1 <= pcb->snd_nxt - 1,即ackno >= pcb->lastack + 1 && ackno <= pcb->snd_nxt。
/* We come here when the ACK acknowledges new data. */
/* Reset the "IN Fast Retransmit" flag, since we are no longer
in fast retransmit. Also reset the congestion window to the
slow start threshold. */
if (pcb->flags & TF_INFR) {
pcb->flags &= ~TF_INFR;
pcb->cwnd = pcb->ssthresh;
}
有数据被确认接收后重置超时重传定时器,恢复发送缓存,更新lastack,更新拥塞窗口cwnd等(慢启动、拥塞避免)。
/* Reset the number of retransmissions. */
pcb->nrtx = 0;
/* Reset the retransmission time-out. */
pcb->rto = (pcb->sa >> 3) + pcb->sv;
/* Update the send buffer space. Diff between the two can never exceed 64K? */
pcb->acked = (u16_t)(ackno - pcb->lastack);
pcb->snd_buf += pcb->acked;
/* Reset the fast retransmit variables. */
pcb->dupacks = 0;
pcb->lastack = ackno;
/* Update the congestion control variables (cwnd and
ssthresh). */
if (pcb->state >= ESTABLISHED) {
if (pcb->cwnd < pcb->ssthresh) {
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss; // 慢启动算法
}
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\\n", pcb->cwnd));
} else {
u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd); // 拥塞避免算法
if (new_cwnd > pcb->cwnd) {
pcb->cwnd = new_cwnd;
}
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"U16_F"\\n", pcb->cwnd));
}
}
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\\n",
ackno,
pcb->unacked != NULL?
ntohl(pcb->unacked->tcphdr->seqno): 0,
pcb->unacked != NULL?
ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0));
被确认接收的数据从unacked队列删除。
/* Remove segment from the unacknowledged list if the incoming
ACK acknowlegdes them. */
while (pcb->unacked != NULL &&
TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) +
TCP_TCPLEN(pcb->unacked), ackno)) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unacked\\n",
ntohl(pcb->unacked->tcphdr->seqno),
ntohl(pcb->unacked->tcphdr->seqno) +
TCP_TCPLEN(pcb->unacked)));
next = pcb->unacked;
pcb->unacked = pcb->unacked->next;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen));
LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
pcb->snd_queuelen -= pbuf_clen(next->p);
tcp_seg_free(next);
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unacked)\\n", (u16_t)pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_receive: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
}
接下来被确认接收的数据从unsent删除。(超时重传,unacked队列的数据会插入unsent表头,因此unsent里面可能包含已发送但unacked的数据)
如果报文有数据,那么处理报文里面的数据。报文处理参考https://blog.csdn.net/arm7star/article/details/117153533 《TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)》"3.1、接收窗口的更新"。
2.2、报文接收后处理
数据被确认接收以及有可接受的数据时,需要通知应用层。
tcp_process处理报文后返回tcp_input,tcp_input对报文处理结果进行处理。
有数据被确认接收,需要调用sent函数(select(lwip_select)函数有监听sent事件的功能,应用层阻塞在lwip_select等待写事件时,需要调用TCP_EVENT_SENT唤醒应用层)
/* If the application has registered a "sent" function to be
called when new send buffer space is available, we call it
now. */
if (pcb->acked > 0) {
TCP_EVENT_SENT(pcb, pcb->acked, err); // sent事件处理
}
接收到数据recv_data,需要发送到应用层(报文如果有PSH标志,需要将PSH标志传递到应用层)。
if (recv_data != NULL) { // 有可接受的数据
if(flags & TCP_PSH) { // 报文含有PSH标志
recv_data->flags |= PBUF_FLAG_PUSH; // 传递PSH标志到应用层
}
/* Notify application that data has been received. */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); // 发送数据到应用层(lwip内部为mail box)
/* If the upper layer can't receive this data, store it */
if (err != ERR_OK) { // 应用层拒绝接收数据(mail box已经满了)
pcb->refused_data = recv_data; // 拒收的数据保存在refused_data里面,下次收到报文时再次尝试发送到应用层,如果下次仍发送失败,则丢弃下次收到的报文
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \\"full\\"\\n"));
}
}
FIN报文处理。(应用层接读数据阻塞时,如果传输层收到FIN报文,需要让应用层返回,lwip通过发送一个NULL数据给应用层,应用层收到NULL的数据时,认为收到了FIN标记,读数据将返回)
/* If a FIN segment was received, we call the callback
function with a NULL buffer to indicate EOF. */
if (recv_flags & TF_GOT_FIN) { // 报文带有FIN标记
TCP_EVENT_RECV(pcb, NULL, ERR_OK, err); // 发送一个NULL数据包到应用层
}
发送更多报文。(有数据被确认接收了,发送窗口里面有更多数据可以发送(unsent发送窗口里面的数据可以发送),调用tcp_output发送数据;不考虑拥塞窗口的情况下,总有发送窗口大小的数据在发送,tcp_output会把发送窗口占满,如果发送窗口太大,导致网络拥塞,则拥塞窗口会限制同一时间能发送报文的数量)
/* If there were no errors, we try to send something out. */
if (err == ERR_OK) {
tcp_output(pcb);
}
3、报文发送
在数据被确认接收后,tcp_input会调用tcp_output发送更多unsent的数据,因此tcp_output可以认为是发送缓存里面的数据到发送窗口。发送unsent队列里面的报文比较简单,如果unsent里面的报文在发送窗口/拥塞窗口范围内,那么就发送;另外,tcp_output有调用tcp_do_output_nagle,tcp_do_output_nagle主要在发送端用于避免糊涂窗口综合症(《TCP-IP详解卷 1:协议》22.3 糊涂窗口综合症);unsent队列发送可以参考https://blog.csdn.net/arm7star/article/details/117153533《TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)》"4.1、报文发送/加入发送队列"。
应用层数据发送到unsent队列是通过tcp_enqueue函数实现。tcp_enqueue函数原型为“err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len, u8_t flags, u8_t apiflags, u8_t optflags)”,arg即为应用层write/sengmsg的数据,len即为应用层write/sengmsg数据的长度,socket封装了对pcb及其他参数的处理,应用层调用write/sengmsg函数,协议栈会找到socket对应的tcp_pcb并调用tcp_enqueue。
发送队列/发送缓存主要涉及snd_lbb、snd_buf、unsent,snd_lbb(Sequence number of next byte to be buffered)为下一个缓存报文的序号(unsent、unacked之外的第一个序号),snd_buf为发送缓存的大小(可用缓存的大小,数据加入发送队列时,发送缓存减小,数据被确认接收时,恢复发送缓存),unsent前面介绍过,如果unsent队列不为空、当前发送的数据比较少,unsent最末一个报文可能可以装下当前的数据,那么可能需要合并成一个报文。
加入发送缓存/发送队列的过程如下。
3.1、发送的数据组装成tcp报文队列
发送缓存、缓存报文序号等获取。
/* fail on too much data */
if (len > pcb->snd_buf) { // 本次要发送的数据长度大于可用的缓存
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue: too much data (len=%"U16_F" > snd_buf=%"U16_F")\\n", len, pcb->snd_buf));
pcb->flags |= TF_NAGLEMEMERR;
return ERR_MEM; // 返回内存错误(内存不够)
}
left = len; // 剩余需要缓存的数据left(一次发送的数据可能过大,需要缓存成多个报文)
ptr = arg; // 数据指针
optlen = LWIP_TCP_OPT_LENGTH(optflags);
/* seqno will be the sequence number of the first segment enqueued
* by the call to this function. */
seqno = pcb->snd_lbb; // 当前缓存报文的seqno
发送队列长度检查。(虽然发送缓存够大,但是发送队列有限制,因此也不能缓存超过发送队列的数据;发送队列包括unacked、unsent;发送队列长度是按pbuf节点来计算的,就是内存链表有多少个节点,简单理解就是发送队列(unacked、unsent)里面有多少不连续的内存数据块;pbuf_clen计算一个pbuf有多少内存节点)
/* If total number of pbufs on the unsent/unacked queues exceeds the
* configured maximum, return an error */
queuelen = pcb->snd_queuelen; // 发送队列长度
/* check for configured max queuelen and possible overflow */
if ((queuelen >= TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue: too long queue %"U16_F" (max %"U16_F")\\n", queuelen, TCP_SND_QUEUELEN));
TCP_STATS_INC(tcp.memerr);
pcb->flags |= TF_NAGLEMEMERR;
return ERR_MEM;
}
应用层发送的数据组装成tcp报文,放入queue队列(queue队列保存的是当前发送的数据的tcp报文链表;发送的数据过大时,会将数据拆分保存在一个个的tcp报文里面,这些报文组成一个queue链表)。
while (queue == NULL || left > 0) {
/* The segment length (including options) should be at most the MSS */
seglen = left > (pcb->mss - optlen) ? (pcb->mss - optlen) : left; // 如果当前剩余数据加上选项数据长度大于一个mss报文,那么拆分剩余的数据,先组装一个mss大小的报文
/* Allocate memory for tcp_seg, and fill in fields. */
seg = memp_malloc(MEMP_TCP_SEG);
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_enqueue: could not allocate memory for tcp_seg\\n"));
goto memerr;
}
seg->next = NULL; // next为空
seg->p = NULL; // 负载为空(还没挂接数据)
/* first segment of to-be-queued data? */
if (queue == NULL) {
queue = seg; // 当前报文为本次缓存的第一个报文,queue指向第一个拆分的报文
}
/* subsequent segments of to-be-queued data */
else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("useg != NULL", useg != NULL);
useg->next = seg; // useg指向前一次循环拆分的报文,当前报文链接到前一个拆分报文的后面
}
/* remember last segment of to-be-queued data for next iteration */
useg = seg; // useg指向本次循环拆分的报文,下次循环时,useg就指向前一次循环拆分的报文
/* If copy is set, memory should be allocated
* and data copied into pbuf, otherwise data comes from
* ROM or other static memory, and need not be copied. */
if (apiflags & TCP_WRITE_FLAG_COPY) { // 写拷贝,应用层的数据需要拷贝到报文里面
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen + optlen, PBUF_RAM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_enqueue : could not allocate memory for pbuf copy size %"U16_F"\\n", seglen));
goto memerr;
}
LWIP_ASSERT("check that first pbuf can hold the complete seglen",
(seg->p->len >= seglen + optlen));
queuelen += pbuf_clen(seg->p); // pbuf_clen计算Count number of pbufs in a chain,非内存大小,增加queuelen
if (arg != NULL) {
MEMCPY((char *)seg->p->payload + optlen, ptr, seglen); // 拷贝应用层数据到报文里面
}
seg->dataptr = seg->p->payload; // 更新dataptr
}
/* do not copy data */
else {
/* First, allocate a pbuf for the headers. */
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) { // 不需要拷贝数据,只分配optlen大小的内存来保存选项数据
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_enqueue: could not allocate memory for header pbuf\\n"));
goto memerr;
}
queuelen += pbuf_clen(seg->p); // 增加queuelen
/* Second, allocate a pbuf for holding the data.
* since the referenced data is available at least until it is sent out on the
* link (as it has to be ACKed by the remote party) we can safely use PBUF_ROM
* instead of PBUF_REF here.
*/
if (left > 0) {
if ((p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) { // 申请一个pbuf(没有申请保存数据的内存空间,应用层数据不拷贝到报文里面,pbuf的数据指针指向应用层数据地址)
/* If allocation fails, we have to deallocate the header pbuf as well. */
pbuf_free(seg->p);
seg->p = NULL;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_enqueue: could not allocate memory for zero-copy pbuf\\n"));
goto memerr;
}
++queuelen; // 应用层的数据是连续的,只占一个连续内存块,因此queuelen只需要加1即可
/* reference the non-volatile payload data */
p->payload = ptr; // payload指向应用层数据地址
seg->dataptr = ptr; // dataptr指向应用层数据地址
/* Concatenate the headers and data pbufs together. */
pbuf_cat(seg->p/*header*/, p/*data*/);
p = NULL;
}
}
/* Now that there are more segments queued, we check again if the
length of the queue exceeds the configured maximum or overflows. */
if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_enqueue: queue too long %"U16_F" (%"U16_F")\\n", queuelen, TCP_SND_QUEUELEN));
goto memerr;
}
seg->len = seglen; // 设置报文长度
/* build TCP header */
if (pbuf_header(seg->p, TCP_HLEN)) { // 申请tcp首部空间
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_enqueue: no room for TCP header in pbuf.\\n"));
TCP_STATS_INC(tcp.err);
goto memerr;
}
seg->tcphdr = seg->p->payload; // tcp首部设置
seg->tcphdr->src = htons(pcb->local_port);
seg->tcphdr->dest = htons(pcb->remote_port);
seg->tcphdr->seqno = htonl(seqno);
seg->tcphdr->urgp = 0;
TCPH_FLAGS_SET(seg->tcphdr, flags);
/* don't fill in tcphdr->ackno and tcphdr->wnd until later */
seg->flags = optflags;
/* Set the length of the header */
TCPH_HDRLEN_SET(seg->tcphdr, (5 + optlen / 4));
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_enqueue: queueing %"U32_F":%"U32_F" (0x%"X16_F")\\n",
ntohl(seg->tcphdr->seqno),
ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
(u16_t)flags));
left -= seglen; // 剩余数长度减去已拆分成tcp报文的数据长度(剩余的left数据需要组装tcp报文)
seqno += seglen; // 更新下个报文的seqno
ptr = (void *)((u8_t *)ptr + seglen); // 下一个报文数据的指针
}
3.2、本次发送数据的报文队列插入unsent队列
获取queue报文插入的位置。(如果unsent队列不为空,那么需要插入到unsent队列末尾,否则queue就是未发送的队列)
/* Now that the data to be enqueued has been broken up into TCP
segments in the queue variable, we add them to the end of the
pcb->unsent queue. */
if (pcb->unsent == NULL) { // unsent队列为空
useg = NULL;
}
else {
for (useg = pcb->unsent; useg->next != NULL; useg = useg->next); // 找到unsent队列的尾节点(unsent不为空,待发送数据的报文插入unsent末尾)
}
unsent末尾节点报文与queue的第一个节点合并。(TCP_SYN|TCP_FIN相关的报文不能合并,合并的两个报文不能超过一个mss最大报文,合并的两个报文flags要相等;不能合并时,直接将queue插入到unsent末尾即可)
if (useg != NULL &&
TCP_TCPLEN(useg) != 0 &&
!(TCPH_FLAGS(useg->tcphdr) & (TCP_SYN | TCP_FIN)) &&
!(flags & (TCP_SYN | TCP_FIN)) &&
/* fit within max seg size */
(useg->len + queue->len <= pcb->mss) &&
/* only concatenate segments with the same options */
(useg->flags == queue->flags)) {
/* Remove TCP header from first segment of our to-be-queued list */
if(pbuf_header(queue->p, -(TCP_HLEN + optlen))) {
/* Can we cope with this failing? Just assert for now */
LWIP_ASSERT("pbuf_header failed\\n", 0);
TCP_STATS_INC(tcp.err);
goto memerr;
}
if (queue->p->len == 0) {
/* free the first (header-only) pbuf if it is now empty (contained only headers) */
struct pbuf *old_q = queue->p;
queue->p = queue->p->next;
old_q->next = NULL;
queuelen--;
pbuf_free(old_q);
}
LWIP_ASSERT("zero-length pbuf", (queue->p != NULL) && (queue->p->len > 0));
pbuf_cat(useg->p, queue->p);
useg->len += queue->len;
useg->next = queue->next;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("tcp_enqueue: chaining segments, new len %"U16_F"\\n", useg->len));
if (seg == queue) {
seg = useg;
seglen = useg->len;
}
memp_free(MEMP_TCP_SEG, queue);
}
3.3、发送缓存更新
发送缓存、seqno更新。
if ((flags & TCP_SYN) || (flags & TCP_FIN)) { // TCP_SYN|TCP_FIN报文占一个字节
++len;
}
if (flags & TCP_FIN) {
pcb->flags |= TF_FIN;
}
pcb->snd_lbb += len; // 更新下一个缓存报文的seqno
pcb->snd_buf -= len; // 发送缓存大小减去len(非TCP_SYN|TCP_FIN报文,len就是数据的长度,TCP_SYN|TCP_FIN报文,len就需要在数据长度上加1个字节,TCP_SYN|TCP_FIN占一个数据)
3.4、PSH标志设置
应用层已经发送完一次数据,最后一次的sendmsg不带MORE标志,最后一个报文添加PSH标志。(对端收到PSH报文会立即返回,而不等待接收足够数据,PSH就是发送端期望接收端收完这些数据后应该尽快处理,不用等待更多数据,接下来收到的数据与当前没有关联(例如发送端希望发送两个消息“hello”和“world”,语义上“hello”和“world”是独立的,接收端收到“hello”即可返回,下次在接收"world",而不是一次接收“helloworld”);write操作理论上每个数据后面都有设置PSH标志,write没办法设置more参数;ceph异步消息就使用sendmsg发送消息,ceph消息是buffer list结构,而且一个完整的消息不一定在一个buffer list里面,因此buffer list前面的消息都设置一个more标志,消息的最后那部分数据才不设置more标志)
/* Set the PSH flag in the last segment that we enqueued, but only
if the segment has data (indicated by seglen > 0). */
if (seg != NULL && seglen > 0 && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
报文加入未发送队列后,调用tcp_output发送报文。
以上是关于linux服务器接收发送报文的主要内容,如果未能解决你的问题,请参考以下文章
Linux客户端和Window服务器端udp socket通信不能成功