网络篇之recv

网络篇之socket

网络篇之bind

网络篇之listen

网络篇之accept

网络篇之connect

网络篇之三次握手(SYN+ACK)

网络篇之三次握手(ACK)

网络篇之send

一 接收应用层数据

 在《网络篇之send》中已经提到过,已建立连接的套接字,将接收到的数据保存在sk_receive_queue列表中,c库中的recv,就是从这个列表中读取数据的。其调用路径是:

sys_recv --> sock_recvmsg --> sock_recvmsg_nosec --> tcp_recvmsg

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);
	int copied = 0;
	u32 peek_seq;
	u32 *seq;
	unsigned long used;
	int err;
	int target;		/* Read at least this many bytes */
	long timeo;
	struct sk_buff *skb, *last;
	u32 urg_hole = 0;
	struct scm_timestamping tss;
	bool has_tss = false;

	if (unlikely(flags & MSG_ERRQUEUE))
		return inet_recv_error(sk, msg, len, addr_len);

	if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&
	    (sk->sk_state == TCP_ESTABLISHED))
		sk_busy_loop(sk, nonblock);

	// 锁住套接字,其实就是设置sk->sk_lock.owned = 1
	// 当产生软中断调用tcp_v4_rcv获取套接字sock发现sock处于进程
	// 上下文,就把数据包加入到balock_queue队列
	lock_sock(sk);

	err = -ENOTCONN;
	// 处于监听状态
	if (sk->sk_state == TCP_LISTEN)
		goto out;

	// 查询时间,如果是非堵塞模式就是0
	timeo = sock_rcvtimeo(sk, nonblock);

	/* Urgent data needs to be handled specially. */
	// 紧急处理数据
	if (flags & MSG_OOB)
		goto recv_urg;

	if (unlikely(tp->repair)) {
		... ...
	}

	// 未读数据包的开始序列号
	seq = &tp->copied_seq;
	if (flags & MSG_PEEK) {
		peek_seq = tp->copied_seq;
		seq = &peek_seq;
	}

	// 取len和sk->rcvlowat中的最小值
	// MSG_WAITALL标志是判断是否要接收完整的数据包后再拷贝复制数据包
	target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);

	do {
		u32 offset;

		... ...

		/* Next get a buffer. */

		last = skb_peek_tail(&sk->sk_receive_queue);

		// 未读数据包的序列号和已经读取数据包的序列号差
		// 如果这个差小于数据包长度skb->,表示这是我们要找的数据包
		// 因为是最小的序列号
		skb_queue_walk(&sk->sk_receive_queue, skb) {
			last = skb;
			/* Now that we have two receive queues this
			 * shouldn't happen.
			 */
			if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
				 "recvmsg 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; // TCP_SKB_CB => ((struct tcp_skb_cb *)&((__skb)->cb[0]))

			// 如果是syn包就跳过
			if (unlikely(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) {
				pr_err_once("%s: found a SYN, please report !\n", __func__);
				offset--;
			}

			// 找到skb
			if (offset < skb->len)
				goto found_ok_skb;
			
			// 如果是fin包
			if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
				goto found_fin_ok;
			WARN(!(flags & MSG_PEEK),
			     "recvmsg bug 2: copied %X seq %X rcvnxt %X fl %X\n",
			     *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags);
		}

		/* Well, if we have backlog, try to process it now yet. */

		// 缓冲区receive_queue队列中已经没有数据
		// 而且backlog_queue队列也没有数据了,就跳出循环
		if (copied >= target && !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 {
			... ...
		}

		// 根据已经复制数据长度copied清除receive_queue队列,回复对端ack包
		tcp_cleanup_rbuf(sk, copied);

		// 数据包复制完毕
		if (copied >= target) {
			/* Do not sleep, just process backlog. */
			// 从backlog队列中复制数据包到receive_queue队列
			release_sock(sk);
			lock_sock(sk);
		} else {
			// 已经没有数据要处理,将套接字放入等待状态,进程进入睡眠
			sk_wait_data(sk, &timeo, last);
		}

		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));
			peek_seq = tp->copied_seq;
		}
		continue;

	// 处理sk_receive_queue队列中的数据包
	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? */
		// 首先查看是否有紧急数据需要处理
		// 如果设置套接字选项设置了SO_OOBINLINE就不需要处理紧急数据,因为有单独处理
		if (tp->urg_data) {
			... ...
		}

		if (!(flags & MSG_TRUNC)) {
			err = skb_copy_datagram_msg(skb, offset, msg, used);
			if (err) {
				/* Exception. Bailout! */
				if (!copied)
					copied = -EFAULT;
				break;
			}
		}

		// 更新数据包序列号
		*seq += used;
		// 更新已复制的数据长度
		copied += used;
		// 更新剩下需要复制的数据长度
		len -= used;

		// 更新跳转tcp接收窗口
		tcp_rcv_space_adjust(sk);

skip_copy:
		if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
			tp->urg_data = 0;
			tcp_fast_path_check(sk);
		}
		if (used + offset < skb->len)
			continue;

		if (TCP_SKB_CB(skb)->has_rxtstamp) {
			tcp_update_recv_tstamps(skb, &tss);
			has_tss = true;
		}
		if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
			goto found_fin_ok;
		if (!(flags & MSG_PEEK))
			sk_eat_skb(sk, skb);
		continue;

	found_fin_ok:
		/* Process the FIN. */
		++*seq;
		if (!(flags & 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
	 */

	if (has_tss)
		tcp_recv_timestamp(msg, sk, &tss);

	/* Clean up data we have read: This will do ACK frames. */
	tcp_cleanup_rbuf(sk, copied);

	release_sock(sk);
	return copied;

out:
	release_sock(sk);
	return err;

recv_urg:
	err = tcp_recv_urg(sk, msg, len, flags);
	goto out;

recv_sndq:
	err = tcp_peek_sndq(sk, msg, len);
	goto out;
}

skb_queue_walk(&sk->sk_receive_queue, skb) 

1. 调用skb_queue_walk遍历sk_receive_queue列表,其中skb为列表中的元素;

2. 经过校验,合法的数据包,跳转到found_ok_skb处,调用skb_copy_datagram_msg将数据拷贝到msg中,最终返回给用户空间;

3. 调用sk_eat_skb将skb从列表中删除,并释放skb的资源;

4. 在堵塞模式下,如果未读取到指定的长度,则调用sk_wait_data将当前进程添加到sk中的等待队列中;

二 接收并处理ack 

用户空间调用send函数发送的数据,发送到链路层后,不确定对端是否会收到这些数据,因此会将数据保存在sk->tcp_rtx_queue列表中。当收到对端发送的ack确认包后,将数据从sk --> tcp_rtx_queue列表中删除。这块逻辑,是在tcp_rtx_queue_unlink_and_free中,其调用路径是:

tcp_rcv_established --> tcp_ack --> tcp_clean_rtx_queue --> tcp_rtx_queue_unlink_and_free

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值