tcp输入数据 慢速路径处理 && oob数据 接收 && 数据接收 tcp_data_queue
大致的处理过程
TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为TCP_URG_NOTYET(urgent point指向的可能不是本报文,而是后续报文或者前面收到的乱序报文),并保存最新的urgent data的sequence和对于的1 BYTE urgent data到tcp_sock的urg_data (如果之前的urgent data没有读取,就会被覆盖)。
用户接收流程:在tcp_recvmsg流程中,如果发现当前的skb的数据中有urgent data,首先拷贝urgent data之前的数据,然后tcp_recvmsg退出,提示用户来接收OOB数据;在用户下一次调用tcp_recvmsg来接收数据的时候,会跳过urgent data,并设置urgent data数据接收完成
/* This is the 'fast' part of urgent handling.
Linxu内核在默认情况下,把urgent data实现为OOB数据
TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为
TCP_URG_NOTYET(urgent point指向的可能不是本报文,而是后续报文或者前面收到的乱序报文),并保存最新的urgent data的sequence
和对于的1 BYTE urgent data到tcp_sock的urg_data (如果之前的urgent data没有读取,就会被覆盖)。 用户接收流程:在tcp_recvmsg流程中,如果发现当前的skb的数据中有urgent data,首先拷贝urgent data之前的数据,
然后tcp_recvmsg退出,提示用户来接收OOB数据;在用户下一次调用tcp_recvmsg来接收数据的时候,会跳过urgent data,
并设置urgent data数据接收完成。 相关的数据结构和定义 urg_data成员,其高8bit为urgent data的接收状态;其低8位为保存的1BYTE urgent数据。
urgent data的接收状态对应的宏的含义描述: #defineTCP_URG_VALID 0x0100 //*urgent data已经读到了tcp_sock::urg_data #defineTCP_URG_NOTYET 0x0200 //*已经发现有urgent data,还没有读取到tcp_sock::urg_data #defineTCP_URG_READ 0x0400 //*urgent data已经被用户通过MSG_OOB读取了 */
static void tcp_urg(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
struct tcp_sock *tp = tcp_sk(sk); /* Check if we get a new urgent pointer - normally not. */
if (th->urg)/*收到了urgent data,则检查和设置urg_data和urg_seq成员*/
tcp_check_urg(sk, th); /* Do we wait for any urgent data? - normally not... */
if (tp->urg_data == TCP_URG_NOTYET) {
u32 ptr = tp->urg_seq - ntohl(th->seq) + (th->doff * 4) -
th->syn; /* Is the urgent pointer pointing into this packet?
发现了有urgent data,但是还没有保存到tp->urg_data*/ */
if (ptr < skb->len) {
u8 tmp;
if (skb_copy_bits(skb, ptr, &tmp, 1))
BUG();
tp->urg_data = TCP_URG_VALID | tmp;
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk);
}
}
}
用户接收数据接口
用户接收URG数据的接口
static int tcp_recv_urg(struct sock *sk, struct msghdr *msg, int len, int flags)
{
struct tcp_sock *tp = tcp_sk(sk); /* No URG data to read. 用户已经读取过 或者没数据*/
if (sock_flag(sk, SOCK_URGINLINE) || !tp->urg_data ||
tp->urg_data == TCP_URG_READ)
return -EINVAL; /* Yes this is right ! */ if (sk->sk_state == TCP_CLOSE && !sock_flag(sk, SOCK_DONE))
return -ENOTCONN;
/*当前的tp->urg_data为合法的数据,可以读取*/ if (tp->urg_data & TCP_URG_VALID) {
int err = 0;
char c = tp->urg_data; if (!(flags & MSG_PEEK))
tp->urg_data = TCP_URG_READ; /* Read urgent data. */
msg->msg_flags |= MSG_OOB; if (len > 0) {
if (!(flags & MSG_TRUNC))
err = memcpy_to_msg(msg, &c, 1);
len = 1;
} else
msg->msg_flags |= MSG_TRUNC; return err ? -EFAULT : len;
} if (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))
return 0; /* Fixed the recv(..., MSG_OOB) behaviour. BSD docs and
* the available implementations agree in this case:
* this call should never block, independent of the
* blocking state of the socket.
* Mike <pall@rz.uni-karlsruhe.de>
*/
return -EAGAIN;
}
用户接收普通数据的接口中的相关处理
在用户接收数据的tcp_recvmsg函数中,在查找到待拷贝的skb后,首先拷贝urgent data数据前的数据,然后退出接收过程,在用户下一次执行tcp_recvmsg的时候跳过urgent data,设置urgent data读取结束
查找到准备拷贝的skb后的处理:
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len)
{
------------------------- /* Urgent data needs to be handled specially. */
if (flags & MSG_OOB)
goto recv_urg; ---------------------------------------------
/* Are we at urgent data? Stop if we have read anything or have SIGURG pending.
在接收完urgent data数据前的所有数据之后, tcp_recvmsg的以下代码片段得到执行,
这段代码退出当前接收过程,提示用户有urgent data数据到来,需要用MSG_OOB来接收 */
if (tp->urg_data && tp->urg_seq == *seq) {
if (copied)
break;
if (signal_pending(current)) {
copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
break;
}
} -------------------------------------
/* Do we have urgent data here? */
if (tp->urg_data) {/* 当前有urg_data数据*/
u32 urg_offset = tp->urg_seq - *seq;
if (urg_offset < used) {/*urgent data在当前待拷贝的数据范围内*/
if (!urg_offset) {/*待拷贝的数据就是urgent data,跨过该urgent data,
只给用户读取后面的数据*/
if (!sock_flag(sk, SOCK_URGINLINE)) {
++*seq;
urg_hole++;
offset++;
used--;
if (!used)
goto skip_copy;
}
} else/*指定只拷贝urgent data数据之前的,完成后在下一次循环
开始的位置,会退出循环,返回用户;下一次用户调用tcp_recvmsg
就进入到上面的分支了*/
used = urg_offset;
}
} --------------------- skip_copy:/*用户读取的数据跨过了urgent point,设置读取结束
开启fast path*/
if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
tp->urg_data = 0;
tcp_fast_path_check(sk);
}
----------------------------------- out:
release_sock(sk);
return err; recv_urg:
err = tcp_recv_urg(sk, msg, len, flags);
goto out; }
TCP的urg数据,由于定义和实现上的混乱,当前已经不建议使用,但是为了兼容之前已经已经存在的实现,该机制会长期在内核中存在,如果不了解该机制及其内核行为,有可能就很难解释一些奇怪的问题:比如某段代码不小心地造成send接口事实上设置了MSG_OOB,就会造成接收端少了一个BYTE
}
/* This is what the send packet queuing engine uses to pass
* TCP per-packet control information to the transmission code.
* We also store the host-order sequence numbers in here too.
* This is 44 bytes if IPV6 is enabled.
* If this grows please adjust skbuff.h:skbuff->cb[xxx] size appropriately.
*/
struct tcp_skb_cb {
__u32 seq; /* Starting sequence number // 当前tcp包的第一个序列号*/
__u32 end_seq; /* SEQ + FIN + SYN + datalen// 表示当前tcp 包结束的序列号:seq + FIN + SYN + 数据长度len */
union {
/* Note : tcp_tw_isn is used in input path only
* (isn chosen by tcp_timewait_state_process())
*
* tcp_gso_segs/size are used in write queue only,
* cf tcp_skb_pcount()/tcp_skb_mss()
*/
__u32 tcp_tw_isn;
struct {
u16 tcp_gso_segs;
u16 tcp_gso_size;
};
};
__u8 tcp_flags; /* TCP header flags. (tcp[13]) tcp头的flag */ __u8 sacked; /* State flags for SACK/FACK. SACK/FACK的状态flag或者是sack option的偏移 */
#define TCPCB_SACKED_ACKED 0x01 /* SKB ACK'd by a SACK block */
#define TCPCB_SACKED_RETRANS 0x02 /* SKB retransmitted */
#define TCPCB_LOST 0x04 /* SKB is lost */
#define TCPCB_TAGBITS 0x07 /* All tag bits */
#define TCPCB_REPAIRED 0x10 /* SKB repaired (no skb_mstamp) */
#define TCPCB_EVER_RETRANS 0x80 /* Ever retransmitted frame */
#define TCPCB_RETRANS (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS| \
TCPCB_REPAIRED) __u8 ip_dsfield; /* IPv4 tos or IPv6 dsfield */
__u8 txstamp_ack:1, /* Record TX timestamp for ack? */
eor:1, /* Is skb MSG_EOR marked? */
unused:6;
__u32 ack_seq; /* Sequence number ACK'd ack(确认)的序列号*/
union {
struct {
/* There is space for up to 24 bytes */
__u32 in_flight:30,/* Bytes in flight at transmit */
is_app_limited:1, /* cwnd not fully used? */
unused:1;
/* pkts S/ACKed so far upon tx of skb, incl retrans: */
__u32 delivered;
/* start of send pipeline phase */
struct skb_mstamp first_tx_mstamp;
/* when we reached the "delivered" count */
struct skb_mstamp delivered_mstamp;
} tx; /* only used for outgoing skbs */
union {
struct inet_skb_parm h4;
#if IS_ENABLED(CONFIG_IPV6)
struct inet6_skb_parm h6;
#endif
} header; /* For incoming skbs */
};
}; static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
bool fragstolen = false;
int eaten = -1;
//没有数据部分,直接释放
if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) {
__kfree_skb(skb);
return;
}
skb_dst_drop(skb);
__skb_pull(skb, tcp_hdr(skb)->doff * 4); tcp_ecn_accept_cwr(tp, skb); tp->rx_opt.dsack = 0; /* Queue data for delivery to the user.
* Packets in sequence go to the receive queue.
* Out of sequence packets to the out_of_order_queue.
*///如果该数据包刚好是下一个要接收的数据,则可以直接copy到用户空间(如果存在且可用
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {//非乱序包
if (tcp_receive_window(tp) == 0)//接受窗口满了,不能接受
goto out_of_window; /* Ok. In sequence. In window. */
if (tp->ucopy.task == current &&//应用程序上下文中,在tcp_recvmsg
tp->copied_seq == tp->rcv_nxt && tp->ucopy.len &&//正要读取这个包,并且应用程序还有剩余缓存
sock_owned_by_user(sk) && !tp->urg_data) {//应用程序持有锁
int chunk = min_t(unsigned int, skb->len,
tp->ucopy.len);/* 带读取长度和数据段长度的较小值 */ __set_current_state(TASK_RUNNING); if (!skb_copy_datagram_msg(skb, 0, tp->ucopy.msg, chunk)) {//copy数据到ucopy中
tp->ucopy.len -= chunk;
tp->copied_seq += chunk;
eaten = (chunk == skb->len);
tcp_rcv_space_adjust(sk);//每次copy数据到ucopy都要调用该函数,动态更新sk_rcvbuf大小
}
} if (eaten <= 0) {//没有copy到ucopy,或者没有全部copy
queue_and_out:
if (eaten < 0) {//没有copy /* 没有拷贝到用户空间,对内存进行检查 */
if (skb_queue_len(&sk->sk_receive_queue) == 0)//sk_receive_queue没有数据
sk_forced_mem_schedule(sk, skb->truesize);//检查是否需要分配配额
else if (tcp_try_rmem_schedule(sk, skb, skb->truesize))//查看接收缓存空间够不够
goto drop;
}
eaten = tcp_queue_rcv(sk, skb, 0, &fragstolen);//添加到sk_receive_queue
}
tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);//尝试更新rcv_nxt
if (skb->len)
tcp_event_data_recv(sk, skb);
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
tcp_fin(sk); //fin处理 if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {//ofo队列非空
tcp_ofo_queue(sk);//尝试把ofo中的包移动到sk_receive_queue中 /* RFC2581. 4.2. SHOULD send immediate ACK, when
* gap in queue is filled.
*/
if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
inet_csk(sk)->icsk_ack.pingpong = 0;//ofo队列被清空了,需要立即发送ack
} if (tp->rx_opt.num_sacks)// 新数据到达导致返回给对方的SACK Block 调整
tcp_sack_remove(tp);//如果当前收到的包带sack,则删除其中不需要的部分,有可能ofo填充造成了rcv_next的移动 tcp_fast_path_check(sk);//当前是slow path, 尝试开启快速路径 重新设置 tcp 首部预测标志 if (eaten > 0)
kfree_skb_partial(skb, fragstolen);//已经全部copy到ucopy,可以释放skb了
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk);//通知poll数据到达
return;
}
/* 重传 */
if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {//判断是否是重传的旧包,标记dsack
/* A retransmit, 2nd most common case. Force an immediate ack. */
NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);//设置dsack out_of_window:
tcp_enter_quickack_mode(sk);//进入快速ack
inet_csk_schedule_ack(sk);///标记需要ack
drop:
tcp_drop(sk, skb);
return;
} /* Out of window. F.e. zero window probe. *///收到的包在接收窗口范围之外
/* 窗口以外的数据,比如零窗口探测报文段 */
if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
goto out_of_window;
//乱序包或者需要dsack都要快速ack tcp_enter_quickack_mode(sk);
/* 数据段重叠 */
if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
/* Partial packet, seq < rcv_next < end_seq *///seq < rcv_next < end_seq,部分旧数据
SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
TCP_SKB_CB(skb)->end_seq); tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);//设置dsack /* If window is closed, drop tail of packet. But after
* remembering D-SACK for its head made in previous line.
*/
if (!tcp_receive_window(tp))//接收窗口不足,快速ack通知对方
goto out_of_window;
goto queue_and_out;//调用tcp_queue_rcv添加到sk_receive_queue,会尝试合并
} tcp_data_queue_ofo(sk, skb);//乱续包,添加到ofo队列
}
tcp输入数据 慢速路径处理 && oob数据 接收 && 数据接收 tcp_data_queue的更多相关文章
- tcp输入数据 慢速路径处理 tcp_data_queue_ofo
tcp_data_queue_ofo 在新内核的实现中ofo队列实际上是一颗红黑树.在tcp_data_queue_ofo中根据序号,查找到合适位置,合并或者添加到rbtree中.同时设置dsack和 ...
- TCP数据接收及快速路径和慢速路径
概述 tcp握手完成后,收到数据包后,调用路径为tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_established在tcp_rcv_established中处理T ...
- TCP输入 之 快速路径和慢速路径
概述 快速路径:用于处理预期的,理想情况下的数据段,在这种情况下,不会对一些边缘情形进行检测,进而达到快速处理的目的: 慢速路径:用于处理那些非预期的,非理想情况下的数据段,即不满足快速路径的情况下数 ...
- tcp ESTABLISHED 接收数据
tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见.这样分类的目的当然是加快数据包的处理,因为在正常情况下,数据包是按 ...
- Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据
1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...
- TCP连接建立的三次握手过程可以携带数据吗?
前几天实验室的群里扔出了这样一个问题:TCP连接建立的三次握手过程可以携带数据吗?突然发现自己还真不清楚这个问题,平日里用tcpdump或者Wireshark抓包时,从来没留意过第三次握手的ACK包有 ...
- 网络编程--使用TCP协议发送接收数据
package com.zhangxueliang.tcp; import java.io.IOException; import java.io.OutputStream; import java. ...
- Oracle 11g R2(11.2.0.4) RAC 数据文件路径错误解决--ORA-01157 ORA-01110: 数据文件
Oracle 11g R2(11.2.0.1) RAC 数据文件路径错误解决--ORA-01157 ORA-01110: 数据文件 oracle 11g R2(11.2.0.4) rac--scan ...
- 真的懂了:TCP协议中的三次握手和四次挥手(关闭连接时, 当收到对方的FIN报文时, 仅仅表示对方不在发送数据了, 但是还能接收数据, 己方也未必全部数据都发送对方了。相当于一开始还没接上话不要紧,后来接上话以后得让人把话讲完)
一.TCP报文格式 下面是TCP报文格式图: (1) 序号, Seq(Sequence number), 占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2) 确 ...
随机推荐
- nginx的变量系统
本来想写一下nginx的脚本引擎的,但是看起来实在是有点庞大,一时间还不知道该从哪里写比较好.就先写一下他的变量系统吧,这是脚本引擎非常重要的组成部分. 首先为了表述清楚先规定几个术语吧 内置变量:n ...
- ES6之数组
数组新增方法 map(可以理解为是映射,以一定规则修改数组每一项并返回全新数组) reduce(可以理解为是汇总,一堆出来一个) filter(可以理解为过滤,筛选的意思,以一定规则拿到符合的项并返回 ...
- php 数组与URL相互转换
php为了数组与url参数相互转换提供了两个函数: 1,数组转换为带&的URL的字符串 例如: $arr =['title'=>'我是小白','name'=>'真的很白','tex ...
- zookeeper在生产环境中的配置(zookeeper3.6)
一,zookeeper中日志的配置 1,快照文件snapshot的目录: dataDir=/data/zookeeper/data 存储快照文件snapshot的目录.默认情况下,事务日志也会存储在这 ...
- 笔趣阁小说 selenium爬取
import re from time import sleep from lxml import etree from selenium import webdriver options = web ...
- C# 创建text文本并写入数据
/// <summary> /// 创建TXT文本并往里面写入数据 /// </summary> /// <param name="FileName" ...
- Linux标准重定向-输入-输出-错误-多重
一切皆文件,都是文件的操作 三种I/O设备 标准的输入输出 程序:指令+数据 读入数据:Input 输出数据:Output 系统中打开一个文件系统自动分配文件描述符,除了0,1,2是固定的,其他的都是 ...
- MySQL备份和恢复[4]-xtrabackup备份工具
xtrabackup工具介绍 Percona 公司 官网:www.percona.com percona-server InnoDB --> XtraDB Xtrabackup备份工具 perc ...
- 【手摸手,带你搭建前后端分离商城系统】03 整合Spring Security token 实现方案,完成主业务登录
[手摸手,带你搭建前后端分离商城系统]03 整合Spring Security token 实现方案,完成主业务登录 上节里面,我们已经将基本的前端 VUE + Element UI 整合到了一起.并 ...
- Graph-GraphSage
MPNN很好地概括了空域卷积的过程,但定义在这个框架下的所有模型都有一个共同的缺陷: 1. 卷积操作针对的对象是整张图,也就意味着要将所有结点放入内存/显存中,才能进行卷积操作.但对实际场景中的大规模 ...