一 接收应用层数据
在《网络篇之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