socket通信tcp读取数据的一点分析

编程入门 行业动态 更新时间:2024-10-07 02:32:42

socket<a href=https://www.elefans.com/category/jswz/34/1769529.html style=通信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读取数据的一点分析

本文发布于:2024-02-06 09:29:21,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1747930.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:通信   数据   socket   tcp

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!