shutdown-rd 分析
以前的印象中,shutdown rd后,就不能从fd里面读到数据,今天测试时和预计的不一样!
可以看到shutdown后 rd后,还是可以read 数据。分析tcp_rcvmsg可以看到确实能读到数据
// 定义一个接收TCP消息的函数 static int tcp_recvmsg_locked(struct sock *sk, // 指向表示网络连接的sock结构体的指针 struct msghdr *msg, // 指向msghdr结构体的指针,用来存储接收的消息头 size_t len, // 指定希望接收的字节数 int flags, // 指定接收行为的标志位,如MSG_PEEK等 struct scm_timestamping_internal *tss, // 用于存储时间戳信息的结构体 int *cmsg_flags) // 用来输出控制消息标志的整数指针 { // 将传入的sock结构体转换为tcp_sock结构体 struct tcp_sock *tp = tcp_sk(sk); int copied = 0; // 已复制的字节数 u32 peek_seq; // 用于MSG_PEEK操作时的序列号 u32 *seq; // 指向当前处理的数据序列号 unsigned long used; // 未使用的变量,可以移除 int err; // 错误处理变量 int target; // 最小读取字节数 long timeo; // 接收超时时间 struct sk_buff *skb, *last; // 指向数据包的指针 u32 urg_hole = 0; // 紧急数据处理时使用的变量 // 如果socket未连接,则设置错误状态为ENOTCONN err = -ENOTCONN; if (sk->sk_state == TCP_LISTEN) // 如果socket处于监听状态 goto out; // 跳转到函数末尾处理错误 // 如果设置了接收消息队列的标志 if (tp->recvmsg_inq) { *cmsg_flags = TCP_CMSG_INQ; // 设置控制消息标志 msg->msg_get_inq = 1; // 设置消息标志 } // 设置接收超时时间 timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); // 处理紧急数据 if (flags & MSG_OOB) // 如果有MSG_OOB标志,则处理外带数据 goto recv_urg; // 跳转到处理紧急数据的代码段 // 如果在修复模式下 if (unlikely(tp->repair)) { err = -EPERM; // 设置错误为"操作不允许" if (!(flags & MSG_PEEK)) // 如果没有设置MSG_PEEK标志 goto out; // 直接处理错误 if (tp->repair_queue == TCP_SEND_QUEUE) // 如果是发送队列 goto recv_sndq; // 跳转到处理发送队列的代码段 err = -EINVAL; // 设置错误为"无效参数" if (tp->repair_queue == TCP_NO_QUEUE) // 如果没有队列 goto out; // 处理错误 // 处理常见的接收队列中的MSG_PEEK } // 设置序列号指针 seq = &tp->copied_seq; if (flags & MSG_PEEK) { // 如果设置了MSG_PEEK标志 peek_seq = tp->copied_seq; // 复制当前序列号 seq = &peek_seq; // 指向peek_seq } // 设置目标读取字节数 target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); /* 继续处理接收消息循环 */ do { u32 offset; // 计算从skb中复制数据的偏移量 /* 如果当前是紧急数据,并且已经有复制的数据或者有挂起的SIGURG信号 */ if (unlikely(tp->urg_data) && tp->urg_seq == *seq) { if (copied) // 如果已经复制了数据,则退出循环 break; if (signal_pending(current)) { // 如果有挂起的信号 copied = timeo ? sock_intr_errno(timeo) : -EAGAIN; // 根据超时设置错误码 break; } } /* 获取接收队列中的下一个数据包 */ last = skb_peek_tail( &sk->sk_receive_queue); // 获取接收队列的最后一个skb skb_queue_walk(&sk->sk_receive_queue, skb) { // 遍历接收队列中的每个skb last = skb; /* 检查序列号,防止异常 */ 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)) { pr_err_once( "%s: found a SYN, please report !\n", __func__); offset--; // 如果是SYN包,则偏移量减1 } if (offset < skb->len) // 如果偏移量小于skb长度,说明找到了合适的skb goto found_ok_skb; if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) // 如果遇到FIN标志,则处理结束 goto 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); } /* 如果有后台数据,现在尝试处理它 */ if (copied >= target && !READ_ONCE(sk->sk_backlog.tail)) break; if (copied) { if (!timeo || sk->sk_err || sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN) || signal_pending(current)) break; } else { if (sock_flag(sk, SOCK_DONE)) break; if (sk->sk_err) { copied = sock_error( sk); // 如果有socket错误,设置copied为错误码 break; } if (sk->sk_shutdown & RCV_SHUTDOWN) // 如果socket关闭了接收功能 break; if (sk->sk_state == TCP_CLOSE) { /* 用户试图从未连接的socket读取时出现 */ copied = -ENOTCONN; break; } if (!timeo) { copied = -EAGAIN; // 没有超时设置,返回EAGAIN break; } if (signal_pending(current)) { // 检查是否有信号等待处理 copied = sock_intr_errno( timeo); // 根据超时返回错误 break; } } if (copied >= target) { /* 不睡眠,直接处理后台数据 */ __sk_flush_backlog(sk); } else { tcp_cleanup_rbuf(sk, copied); // 清理接收缓冲区 err = sk_wait_data(sk, &timeo, last); // 等待数据到达 if (err < 0) { err = copied ?: err; // 如果出错,设置错误码 goto out; } } 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; // 更新peek序列号以处理MSG_PEEK竞态条件 } continue; found_ok_skb: // 计算可以使用的数据量 used = skb->len - offset; if (len < used) used = len; // 检查当前位置是否有紧急数据 if (unlikely(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); // 更新序列号,跳过紧急指针 urg_hole++; // 紧急数据洞增加 offset++; // 偏移量增加 used--; // 可用数据减少 if (!used) goto skip_copy; // 如果没有可用数据,则跳过复制 } } else used = urg_offset; // 如果紧急数据在中间,调整可用数据长度 } } if (!(flags & MSG_TRUNC)) { err = skb_copy_datagram_msg( skb, offset, msg, used); // 从skb复制数据到用户msg if (err) { // 如果复制出错 if (!copied) copied = -EFAULT; // 如果还没复制任何数据,则返回错误 break; } } WRITE_ONCE(*seq, *seq + used); // 更新序列号 copied += used; // 更新已复制的总字节数 len -= used; // 减少还需复制的长度 tcp_rcv_space_adjust(sk); // 调整接收窗口 skip_copy: if (unlikely(tp->urg_data) && after(tp->copied_seq, tp->urg_seq)) { WRITE_ONCE(tp->urg_data, 0); // 如果处理完紧急数据,清除紧急数据标志 tcp_fast_path_check(sk); // 检查是否可以快速处理路径 } if (TCP_SKB_CB(skb)->has_rxtstamp) { tcp_update_recv_tstamps( skb, tss); // 如果有接收时间戳,更新时间戳 *cmsg_flags |= TCP_CMSG_TS; // 设置控制消息标志 } if (used + offset < skb->len) continue; // 如果skb中还有未处理的数据,继续处理 if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) goto found_fin_ok; // 如果遇到FIN标志,处理连接关闭 if (!(flags & MSG_PEEK)) tcp_eat_recv_skb(sk, skb); // 如果不是peek操作,释放skb continue; found_fin_ok: // 处理FIN包 WRITE_ONCE(*seq, *seq + 1); // 更新序列号 if (!(flags & MSG_PEEK)) tcp_eat_recv_skb(sk, skb); // 如果不是peek操作,如果一个skb内数据被拷贝完, 释放skb break; } while (len > 0); // 清理读取的数据,发送ACK tcp_cleanup_rbuf(sk, copied); return copied; // 返回复制的字节数 out: return err; // 返回错误码 recv_urg: err = tcp_recv_urg(sk, msg, len, flags); // 处理紧急数据接收 goto out; recv_sndq: err = tcp_peek_sndq(sk, msg, len); // 处理发送队列的peek goto out; }
也就是正常逻辑为:found_ok_skb---->skb_copy_datagram_msg----->skip_copy--->continue-->继续查找ok_skb,如果找不到,则根据copied值来决定不同分支情况处理
如果是被RCV_SHUTDOWN则会触发,found_ok_skb---->skb_copy_datagram_msg----->skip_copy--->continue-->继续查找ok_skb,如果找不到,, copied大于0, 进入
if (sk->sk_shutdown & RCV_SHUTDOWN) --->break; 最后处理完!
// Verify behavior for the sequence: // shutdown(SHUT_RD), receive, send, shutdown(SHUT_WR), close(). `../common/defaults.sh` // Initialize a server socket. 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 +0 bind(3, ..., ...) = 0 +0 listen(3, 1) = 0 +0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7> +0 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 8> +0 < . 1:1(0) ack 1 win 257 +0 accept(3, ..., ...) = 4 +0.1 shutdown(4, SHUT_RD) = 0 +0.1 read(4, ..., 1000) = 0 // You would think that after SHUT_RD we would respond to incoming // data with a RST and not queue the data for reading, but we actually // ACK the data, enqueue it for reading, and can read() the data. // AFAICT in 2003 Andi Kleen seems to have decided that this case is too // obscure to slow down the fast path for receiving and reading data: // http://marc.info/?l=linux-netdev&m=105774722214242&w=2 // So.... // Verify that receiving and reading still works. +0 < . 1:1001(1000) ack 1 win 257 +0 > . 1:1(0) ack 1001 +0`sleep 2` +0 read(4, ..., 1000) = 1000 +0 < . 1001:2001(1000) ack 1 win 257 +0 > . 1:1(0) ack 2001 +0`sleep 2` +0 read(4, ..., 1000) = 1000 // Verify that writing and sending still works. +.01 write(4, ..., 1000) = 1000 +.01 > P. 1:1001(1000) ack 2001 +0 < . 2001:2001(0) ack 1001 win 257 +.01 shutdown(4, SHUT_WR) = 0 +0 > F. 1001:1001(0) ack 2001 +0 < . 2001:2001(0) ack 1002 win 257 +0 write(4, ..., 1000) = -1 EPIPE (Broken pipe) +.01 close(4) = 0 +.01 < F. 2001:2001(0) ack 1002 win 257 +0 > . 1002:1002(0) ack 2002
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子

浙公网安备 33010602011771号