/* linux-5.10.x\net\ipv4\tcp.c
* 从接收缓冲区中读取数据,并将其拷贝到用户提供的缓冲区中。该函数还负责处理一些特殊情况,如接收紧急数据和查看发送队列中的数据
1、从接收缓冲区中读取数据,并将其拷贝到用户提供的缓冲区中
2、处理接收到的FIN标志,表示连接的一端已关闭
3、处理MSG_PEEK标志,如果设置了该标志,则不会从接收缓冲区中删除数据
4、处理接收时间戳,如果设置了相应的控制消息标志,则会设置接收时间戳
5、处理接收队列长度控制消息,如果设置了相应的控制消息标志,则会获取接收队列长度的提示,并设置控制消息
*/
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len)
{
struct tcp_sock *tp = tcp_sk(sk); //将TCP套接字的指针转为tcp_sock类型的指针,以便访问TCP套接字特定的成员变量
int copied = 0; //用于记录拷贝的字节数
u32 peek_seq; //用于记录MSG_PEEK的序列号
u32 *seq; //用于指向数据包的序列号
unsigned long used; //用于记录已使用的缓冲区大小
int err, inq; //用于记录错误码和入队标志
int target; /* Read at least this many bytes 表示至少需要读取的字节数*/
long timeo; //表示接收超时时间
struct sk_buff *skb, *last; //用于在数据包链表中遍历
u32 urg_hole = 0; //表示紧急数据的空隙
struct scm_timestamping_internal tss; //用于存储时间戳信息
int cmsg_flags; //表示控制消息的标志位
/* unlikely()宏不会改变程序逻辑,只用于提示编译器某个条件的发生概率较低,让编译器更加关注对其他分支的优化,从而提高代码执行的效率 */
if (unlikely(flags & MSG_ERRQUEUE)) //有错误队列消息
return inet_recv_error(sk, msg, len, addr_len); //处理接收错误队列,并返回结果
if (sk_can_busy_loop(sk) && skb_queue_empty_lockless(&sk->sk_receive_queue) &&
(sk->sk_state == TCP_ESTABLISHED)) //套接字类型支持忙等待、接收队列为空、TCP套接字处于已建立状态
sk_busy_loop(sk, nonblock); //在非阻塞模式下进行忙等待
lock_sock(sk); //锁定套接字,以保证在执行后续操作时的线程安全
err = -ENOTCONN; //将错误码设置为未连接
if (sk->sk_state == TCP_LISTEN) //如果套接字状态为监听状态
goto out; //则跳转到out标签,表示连接未建立
cmsg_flags = tp->recvmsg_inq ? 1 : 0; //根据TCP套接字接收消息入队标志,设置控制消息的标志位
timeo = sock_rcvtimeo(sk, nonblock); //获取接收超时时间
/* Urgent data needs to be handled specially. */
if (flags & MSG_OOB) //如果有应急数据
goto recv_urg; //则跳转到recv_urg标签,表示接收紧急数据
if (unlikely(tp->repair)) { //如果TCP套接字处于损坏修复状态
err = -EPERM; //将错误码设置为权限不足
if (!(flags & MSG_PEEK)) //如果flags参数中不包含MSG_PEEK标志位
goto out; //则跳转到out标签,表示权限错误
if (tp->repair_queue == TCP_SEND_QUEUE) //如果修复队列为发送队列
goto recv_sndq; //则跳转到处理发送队列
err = -EINVAL; //将错误码设置为无效参数
if (tp->repair_queue == TCP_NO_QUEUE) //如果修复队列为无队列
goto out; //跳转到out标签,表示无效参数
/* 'common' recv queue MSG_PEEK-ing 正在使用 MSG_PEEK 模式进行接收操作*/
}
seq = &tp->copied_seq; //将指针seq指向TCP套接字的copied_seq,用于记录已拷贝的序列号
if (flags & MSG_PEEK) { //是否启用了数据的查看(peek)模式
peek_seq = tp->copied_seq; //用于记录MSG_PEEK操作的序列号
seq = &peek_seq; //表示当前操作使用MSG_PEEK选项的序列号
}
/*MSG_WAITALL表示是否使用等待所有数据到达的模式,len是期望接收的数据长度*/
target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); //计算接收目标的最小数据长度
/* 开始一个循环,用于接收数据包中的数据 */
do {
u32 offset;
/* Are we at urgent data? Stop if we have read anything or have SIGURG pending.
我们现在处于紧急数据模式吗?如果已经读取了任何数据或有SIGUR Gpending,请停止.
"SIGURG pending"指的是在套接字上挂起了一个未处理的SIGURG信号,即已经接收到但尚未处理的紧急数据。
如果存在未处理的SIGURG信号,意味着在处理紧急数据之前应该先处理该信号。
当套接字处于紧急数据模式时,可能会收到一个SIGURG信号
*/
if (tp->urg_data && tp->urg_seq == *seq) { //存在紧急数据,且序列号与当前操作的序列号匹配
if (copied) //如果已经复制了部分数据,则跳出循环
break;
if (signal_pending(current)) { //当前进程有中断信号等待 SIGURG pending
copied = timeo ? sock_intr_errno(timeo) : -EAGAIN; //将copied设置为接收超时的错误码或者-EAGAIN
break; //跳出循环
}
}
/* Next get a buffer. */
last = skb_peek_tail(&sk->sk_receive_queue); //获取接收队列中最后一个数据包的指针
skb_queue_walk(&sk->sk_receive_queue, skb) { //遍历接收队列sk_receive_queue中的每个数据包
last = skb; //记录最后一个数据包的指针
/* Now that we have two receive queues this
* shouldn't happen.现在我们有两个接收队列,这种情况不应该发生
*/
if (WARN(before(*seq, TCP_SKB_CB(skb)->seq), //如果当前数据包的序列号小于操作序列号,说明存在序列号错误
"TCP recvmsg seq # bug: copied %X, seq %X, rcvnxt %X, fl %X\n",
*seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
flags)) //在这里输出错误提示信息,并跳出循环
break;
offset = *seq - TCP_SKB_CB(skb)->seq; //计算当前数据包的序列号偏移量
if (unlikely(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) { //如果当前数据包的TCP标志中包含SYN标志(建立连接),说明是连接建立的第一个数据包
pr_err_once("%s: found a SYN, please report !\n", __func__);//输出错误信息
offset--; //并减少偏移量
}
if (offset < skb->len) //如果偏移量小于当前数据包的长度
goto found_ok_skb; //说明从当前数据包开始有可读取的数据,跳转到found_ok_skb标签进行处理
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) //如果当前数据包的TCP标志中包含FIN标志(结束连接)
goto found_fin_ok; //则跳转到found_fin_ok标签进行处理
WARN(!(flags & MSG_PEEK),
"TCP recvmsg seq # bug 2: copied %X, seq %X, rcvnxt %X, fl %X\n",
*seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags); //如果不是使用MSG_PEEK选项的操作,输出错误提示信息
}
/* Well, if we have backlog, try to process it now yet.
如果存在挂起的连接请求队列(backlog),尝试立即处理它们
连接队列sk_backlog可以用于存储待处理的传入连接请求,在连接请求到达但还未被处理时,会被添加到连接队列中。
当连接队列为空时,表示当前没有挂起的连接请求需要处理
*/
if (copied >= target && !READ_ONCE(sk->sk_backlog.tail)) //如已拷贝字节数不小于目标字节数,且套接字的接收队列没有挂起的连接请求
break; //跳出循环
if (copied) { //如果已经拷贝了一部分数据
if (sk->sk_err || //套接字存在错误
sk->sk_state == TCP_CLOSE || //套接字的状态为连接已关闭
(sk->sk_shutdown & RCV_SHUTDOWN) || //套接字的接收端关闭
!timeo || //未设置超时时间
signal_pending(current)) //当前进程有中断信号等待
break; //如果满足以上条件,则跳出循环
} else { //如果还没有拷贝任何数据
if (sock_flag(sk, SOCK_DONE)) //套接字已经完成,则跳出循环
break;
if (sk->sk_err) {
copied = sock_error(sk); //套接字存在错误,则跳出循环
break;
}
if (sk->sk_shutdown & RCV_SHUTDOWN) //套接字的接收端关闭,则跳出循环
break;
if (sk->sk_state == TCP_CLOSE) { //套接字的状态为连接已关闭,则跳出循环
/* This occurs when user tries to read
* from never connected socket.
*/
copied = -ENOTCONN; //错误码 传输端点未连接
break;
}
if (!timeo) { //未设置超时时间,则跳出循环
copied = -EAGAIN; //错误码 资源暂时不可用,请重试
break;
}
if (signal_pending(current)) { //当前进程有中断信号等待,则跳出循环
copied = sock_intr_errno(timeo); //获取的错误码赋给copied变量
break;
}
}
tcp_cleanup_rbuf(sk, copied); //清理接收缓冲区中的已拷贝数据
if (copied >= target) { //如果拷贝的数据量大于或等于目标数据量
/* Do not sleep, just process backlog. */
release_sock(sk); //释放套接字的锁
lock_sock(sk); //重新获取套接字的锁
} else { //如果没有收到足够数据,阻塞当前进程
sk_wait_data(sk, &timeo, last); //等待接收缓冲区sk_receive_queue中有新的数据到达,或者达到指定的超时时间
}
/* peek_seq:观察序列号表示应用程序期望接收的下一个数据包的序列号。
copied:已拷贝的数据量是已经从网络接收到应用程序缓冲区的数据量。
urg_hole:紧急数据空洞是在紧急数据之前未接收到的数据的数量。
tp->copied_seq:TCP已拷贝数据序列号
如果以下算式不相等,就意味着在MSG_PEEK中存在竞争问题,
即应用程序期望接收的数据已经被其他操作(如其他线程)读取或移除了*/
if ((flags & MSG_PEEK) && //如果是只查看数据,而不取走数据
(peek_seq - copied - urg_hole != tp->copied_seq)) { //且应用程序期望接收的数据已经被其他操作(如其他线程)读取或移除了
net_dbg_ratelimited("TCP(%s:%d): Application bug, race in MSG_PEEK\n",
current->comm,
task_pid_nr(current)); //输出调试消息,指示存在MSG_PEEK中的竞争问题
peek_seq = tp->copied_seq; //将观察序列号设置为已拷贝数据序列号,以确保应用程序接收到正确的数据
}
continue; //继续循环的下一次迭代
found_ok_skb:
/* Ok so how much can we use? */
used = skb->len - offset; //计算使用长度(从接收缓冲区中要拷贝的数据的实际长度)
if (len < used) //如果要拷贝的数据长度小于实际使用长度
used = len; //则使用长度为要拷贝的数据长度
/* Do we have urgent data here? */
if (tp->urg_data) { //如果存在紧急数据
u32 urg_offset = tp->urg_seq - *seq; //计算紧急序列号与当前序列号的差值
if (urg_offset < used) { //如果差值小于使用长度
if (!urg_offset) { //如果差值为零
if (!sock_flag(sk, SOCK_URGINLINE)) { //如果不支持紧急数据内联
WRITE_ONCE(*seq, *seq + 1); //原子操作地将当前序列号加1
urg_hole++; //增加紧急数据空洞的数量
offset++; //增加偏移量
used--; //减少使用长度
if (!used) //如果使用长度为零,则跳转到标签skip_copy处
goto skip_copy;
}
} else
used = urg_offset; //否则,使用长度为差值
}
}
if (!(flags & MSG_TRUNC)) { //如果报文未被截断
err = skb_copy_datagram_msg(skb, offset, msg, used); //从给定的skb数据报中将数据拷贝到msg消息结构体的缓冲区中
if (err) { //拷贝出现错误
/* Exception. Bailout! 异常。中止!*/
if (!copied) //如果之前没有拷贝成功过
copied = -EFAULT; //则将copied设置为-EFAULT(拷贝错误)
break; //跳出循环
}
}
WRITE_ONCE(*seq, *seq + used); //原子操作将序列号增加used(已使用长度)
copied += used; //将已拷贝的数据长度增加used
len -= used; //将剩余要拷贝的数据长度减少used
tcp_rcv_space_adjust(sk); //根据已复制的字节数和时间差动态调整TCP接收窗口大小,适度调整接收缓冲区大小
skip_copy:
if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) { //如果存在紧急数据,且已拷贝的数据序列号大于紧急数据序列号
tp->urg_data = 0; //将紧急数据标志清零
tcp_fast_path_check(sk); //检测是否可以启用TCP的快速路径传输
}
if (TCP_SKB_CB(skb)->has_rxtstamp) { //如果数据包中有接收时间戳信息
tcp_update_recv_tstamps(skb, &tss); //则更新接收时间戳
cmsg_flags |= 2; //并设置cmsg_flags的第2位为1
}
if (used + offset < skb->len) //如果已使用长度加上偏移量小于数据包的总长度
continue; //则继续下一次循环
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) //如果数据包的TCP标志中包含FIN标志(结束连接)
goto found_fin_ok;
if (!(flags & MSG_PEEK)) //如果标志中不包含MSG_PEEK(只查看数据,而不取走数据)
sk_eat_skb(sk, skb); //则丢弃数据包
continue; //继续下一次循环
found_fin_ok:
/* Process the FIN. */
WRITE_ONCE(*seq, *seq + 1); //原子操作将序列号增加1
if (!(flags & MSG_PEEK)) //如果标志中不包含MSG_PEEK(只查看数据,而不取走数据)
sk_eat_skb(sk, skb); //则丢弃数据包
break; //跳出循环
} while (len > 0); //当还有剩余要拷贝的数据时,继续循环
/* According to UNIX98, msg_name/msg_namelen are ignored
* on connected socket. I was just happy when found this 8) --ANK
根据UNIX98标准,在连接的套接字上忽略msg_name/msg_namelen
*/
/* Clean up data we have read: This will do ACK frames. 清理我们已经读取的数据:这将执行ACK帧 */
tcp_cleanup_rbuf(sk, copied); //清理接收缓冲区
release_sock(sk); //释放套接字的引用计数,表示不再使用套接字
if (cmsg_flags) { //需要设置控制消息
if (cmsg_flags & 2) //表示需要设置接收时间戳控制消息
tcp_recv_timestamp(msg, sk, &tss); //设置接收时间戳
if (cmsg_flags & 1) { //表示需要设置接收队列长度控制消息
inq = tcp_inq_hint(sk); //获取接收队列长度的提示
put_cmsg(msg, SOL_TCP, TCP_CM_INQ, sizeof(inq), &inq); //设置控制消息
}
}
return copied; //返回已拷贝的数据长度
out:
release_sock(sk); //释放套接字的引用计数,表示不再使用套接字
return err;
recv_urg:
err = tcp_recv_urg(sk, msg, len, flags); //处理接收到的紧急数据,并将处理结果赋给err
goto out;
recv_sndq:
err = tcp_peek_sndq(sk, msg, len); //查看发送队列中的数据,并将结果赋给err
goto out;
}
linux内核代码-注释详解:tcp_recvmsg
最新推荐文章于 2024-04-27 21:09:02 发布