通信tcp读取数据的一点分析"/>
socket通信tcp读取数据的一点分析
一、数据的接收流程
1、接收函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
2、接收函数最终会调用到内核协议栈下的tcp_rcvmsg,当接收队列sk_receive_queue中没有数据时,进程就会通过sk_wait_data函数进入睡眠状态
//tcp_recvmsg->sk_wait_data
int sk_wait_data(struct sock *sk, long *timeo, const struct sk_buff *skb)
{DEFINE_WAIT_FUNC(wait, woken_wake_function);int rc;add_wait_queue(sk_sleep(sk), &wait); //声明wait_queue,添加到sk_sleep(sk)的wait_queue_head中sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);rc = sk_wait_event(sk, timeo, skb_peek_tail(&sk->sk_receive_queue) != skb, &wait); \\state=TASK_INTERRUPTIBLE,进入休眠sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);remove_wait_queue(sk_sleep(sk), &wait);return rc;
}
EXPORT_SYMBOL(sk_wait_data);
2.1、其中wait_queue的队列头是sk_sleep(sk)
//等待队列wait_queue_head位于sk->sk_wq->wait中
static inline wait_queue_head_t *sk_sleep(struct sock *sk)
{BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0);return &rcu_dereference_raw(sk->sk_wq)->wait;
}
struct sock *sk的成员变量说明
sock.h - include/net/sock.h - Linux source code (v5.14.6) - Bootlin
2.2、添加到wait_queue_head中
static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{struct list_head *head = &wq_head->head;struct wait_queue_entry *wq;list_for_each_entry(wq, &wq_head->head, entry) {if (!(wq->flags & WQ_FLAG_PRIORITY))break;head = &wq->entry;}list_add(&wq_entry->entry, head); //添加到队列中
}
2.3、设置state=TASK_INTERRUPTIBLE,进入睡眠
#define sk_wait_event(__sk, __timeo, __condition, __wait) \({ int __rc; \release_sock(__sk); \__rc = __condition; \if (!__rc) { \*(__timeo) = wait_woken(__wait, \TASK_INTERRUPTIBLE, \*(__timeo)); \} \sched_annotate_sleep(); \lock_sock(__sk); \__rc = __condition; \__rc; \})
2.4、睡眠流程图
3、当底层收到数据后,数据会存放到sk_receive_queue队列中,并且唤醒休眠的进程
3.1、数据包到来后,会触发中断,然后在软中断中完成数据的接收,在tcp_rcv_state_process函数中根据当前状态完成数据的处理。此时肯定是完成三次握手进入ESTABLISHED状态
tcp_rcv_state_processcase TCP_ESTABLISHED:tcp_data_queuetcp_queue_rcv //数据存放到sk->sk_recevie_queue数据接收队列中sk->sk_data_ready(sk) //sock_def_readable void sock_init_data(struct socket *sock, struct sock *sk)完成挂载
3.2、数据准备好后,sk_data_ready唤醒进程
static void sock_def_readable(struct sock *sk)
{struct socket_wq *wq;rcu_read_lock();wq = rcu_dereference(sk->sk_wq);if (skwq_has_sleeper(wq)) //有进程在此socket上存在等待队列wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |POLLRDNORM | POLLRDBAND); //唤醒sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);rcu_read_unlock();
}#define wake_up_interruptible_sync_poll(x, m) \__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))//__wake_up_sync_key->__wake_up_common_lock->__wake_up_common
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,int nr_exclusive, int wake_flags, void *key,wait_queue_entry_t *bookmark) //nr_exclusive=1,只唤醒一个,避免出现惊群现象
{wait_queue_entry_t *curr, *next;int cnt = 0;if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {curr = list_next_entry(bookmark, entry);list_del(&bookmark->entry);bookmark->flags = 0;} elsecurr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);if (&curr->entry == &wq_head->head)return nr_exclusive;list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {unsigned flags = curr->flags;int ret;if (flags & WQ_FLAG_BOOKMARK)continue;ret = curr->func(curr, mode, wake_flags, key); //此回调函数唤醒,在初始化的时候有定义DEFINE_WAIT_FUNC(wait, woken_wake_function);,或者会有默认函数if (ret < 0)break;if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)break;if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) &&(&next->entry != &wq_head->head)) {bookmark->flags = WQ_FLAG_BOOKMARK;list_add_tail(&bookmark->entry, &next->entry);break;}}return nr_exclusive;
}//woken_wake_function->default_wake_function
int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags,void *key)
{return try_to_wake_up(curr->private, mode, wake_flags);
}
EXPORT_SYMBOL(default_wake_function);
3.3、唤醒流程图
4、以上分析的是只有一路的C/S连接,假如两路以上的C/S连接怎么办?
每创建一路C/S就创建一个线程,一路线程处于睡眠,不影响另外一路的执行。当然此方法操作起来方便,但是消耗资源比较多。可以通过IO多路复用来解决。
更多推荐
socket通信tcp读取数据的一点分析
发布评论