linux内核代码-注释详解:tcp_recvmsg

/* 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值