【Linux 内核网络协议栈源码剖析】recvfrom 函数剖析

【Linux 内核网络协议栈源码剖析】recvfrom 函数剖析继前篇介绍完sendto数据发送函数后,这里介绍数据接收函数recvfrom。一、应用层——recvfrom函数对于这个函数有必要分析一下,先看看这个dup例子。服务器端中调用recvfrom函数,并未指定发送端(客户端)的地址,换句话说这个函数是一个被动函数,有点类似于tcp协议中服务器listen之后阻塞,等待客户端connect。这里则是服务器端recvfrom后,等待客户端

大家好,又见面了,我是你们的朋友全栈君。

继前篇介绍完sendto 数据发送函数 后,这里介绍数据接收函数 recvfrom。

一、应用层——recvfrom 函数

对于这个函数有必要分析一下,先看看这个dup例子。服务器端中调用recvfrom函数,并未指定发送端(客户端)的地址,换句话说这个函数是一个被动函数,有点类似于tcp协议中服务器listen 之后阻塞,等待客户端connect。这里则是服务器端recvfrom后,等待客户端sendto,服务器端recvfrom接收到客户端的数据包,也顺便知道了发送端的地址,于是将其填充到recvfrom的最后两个参数中,这样服务器端就获得了客户端的地址,然后服务器端就可sendto数据给客户端。(TCP同理)

想想也是,服务器怎么可能实现知道全球这么多客户的地址呢?但服务器采用的是大家广为人知的地址,比如你访问谷歌搜索,你知道谷歌的网址,但谷歌事先肯定不知道它众多访问者的地址,所以是客户端先主动访问,发送数据之后,谷歌才知道该客户端的地址,然后返回访问信息。

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, const void *buff, size_t nbytes, int flags,
	const struct sockaddr *from, socklen_t *addrlen);
//若成功返回读到的字节数,否则返回-1
/*参数解析。这里sockfd是接收,from那边是发送
前面三个参数分别表示:套接字描述符,指向写出缓冲区的指针和写字节数。
与sendto不同是后面的参数,recvfrom的最后两个参数类似于accept的最后两个参数,返回时其中套接字地址结构的内容告诉我们是谁发送了数据报
*/

二、BSD Socket 层——sock_recvfrom 函数

/*
 *	Receive a frame from the socket and optionally record the address of the 
 *	sender. We verify the buffers are writable and if needed move the
 *	sender address from kernel to user space.
 */
//从指定的远端地址接收数据,主要用于UDP协议
//从addr指定的源端接收len大小的数据,然后缓存到buff缓冲区
//该函数还要返回远端地址信息,存放在addr指定的地址结构中
static int sock_recvfrom(int fd, void * buff, int len, unsigned flags,
	     struct sockaddr *addr, int *addr_len)
{
	struct socket *sock;
	struct file *file;
	char address[MAX_SOCK_ADDR];
	int err;
	int alen;
	//参数有效性检查
	if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
		return(-EBADF);
	//通过文件描述符找到对应socket结构
	if (!(sock = sockfd_lookup(fd, NULL))) 
	  	return(-ENOTSOCK);
	if(len<0)
		return -EINVAL;
	if(len==0)
		return 0;
    //检查缓冲区域是否可写
	err=verify_area(VERIFY_WRITE,buff,len);
	if(err)
	  	return err;
    //调用下层函数inet_recvfrom
	len=sock->ops->recvfrom(sock, buff, len, (file->f_flags & O_NONBLOCK),
		     flags, (struct sockaddr *)address, &alen);

	if(len<0)
	 	return len;
	//对比可知,这里是sock_recvfrom相比sock_sendto多出来的一部分
	//它的作用便是将发送端(客户端)的地址信息填充到addr中,就是获取客户端的地址信息
	if(addr!=NULL && (err=move_addr_to_user(address,alen, addr, addr_len))<0)
	  	return err;

	return len;
}

三、INET Socket 层——inet_recvfrom 函数

/*
 *	The assorted BSD I/O operations
 */
 //其功能与inet_sendto函数类似
static int inet_recvfrom(struct socket *sock, void *ubuf, int size, int noblock, 
		   unsigned flags, struct sockaddr *sin, int *addr_len )
{
    //获取对应sock结构
	struct sock *sk = (struct sock *) sock->data;
	
	if (sk->prot->recvfrom == NULL) 
		return(-EOPNOTSUPP);
	if(sk->err)
		return inet_error(sk);
	/* We may need to bind the socket. */
	//检查是否绑定了端口,没有的话就自动绑定一个,就服务器端而言,肯定是有的
	if(inet_autobind(sk)!=0)
		return(-EAGAIN);
	//调用下层udp_recvfrom函数
	return(sk->prot->recvfrom(sk, (unsigned char *) ubuf, size, noblock, flags,
			     (struct sockaddr_in*)sin, addr_len));
}

四、传输层——udp_recvfrom 函数

/*
 * 	This should be easy, if there is something there we\
 * 	return it, otherwise we block.
 */
 //接收数据包,并返回对端地址(如果需要的话)
int udp_recvfrom(struct sock *sk, unsigned char *to, int len,
	     int noblock, unsigned flags, struct sockaddr_in *sin,
	     int *addr_len)
{
  	int copied = 0;
  	int truesize;
  	struct sk_buff *skb;
  	int er;

	/*
	 *	Check any passed addresses
	 */
	 
  	if (addr_len) 
  		*addr_len=sizeof(*sin);
  
	/*
	 *	From here the generic datagram does a lot of the work. Come
	 *	the finished NET3, it will do _ALL_ the work!
	 */
	//从接收队列中获取数据包 	
	skb=skb_recv_datagram(sk,flags,noblock,&er);
	if(skb==NULL)
  		return er;
	
    //数据包数据部分(数据报)长度
  	truesize = skb->len;
	//读取长度检查设置,udp是面向报文的,其接收到的每个数据包都是独立的
	//如果用户要求读取的小于可读取的,那么剩下的将被丢弃(本版本协议栈就是这么干的)
  	copied = min(len, truesize);

  	/*
  	 *	FIXME : should use udp header size info value 
  	 */
  	//拷贝skb数据包中的数据负载到to缓冲区中 
  	//这里就是数据转移的地方,将数据从数据包中转移出来到缓存区
	skb_copy_datagram(skb,sizeof(struct udphdr),to,copied);
	sk->stamp=skb->stamp;//记录时间

	/* Copy the address. */
	//如果要求返回远端地址的话,这里就是拷贝远端地址信息了,含端口号和ip地址
	if (sin) 
	{
		sin->sin_family = AF_INET;//地址族
		sin->sin_port = skb->h.uh->source;//端口号
		sin->sin_addr.s_addr = skb->daddr;//ip地址,这里是目的ip地址,有点困惑?
  	}
    //释放该数据包
  	skb_free_datagram(skb);
  	release_sock(sk);
  	return(truesize);//返回读取(接收)到的数据的大小
}

上面在数据处理方面,调用了三个数据报文处理函数(net\inet\Datagram.c):skb_recv_datagram()、skb_copy_datagram()、skb_free_datagram() 

skb_recv_datagram()

/*
 *	Get a datagram skbuff, understands the peeking, nonblocking wakeups and possible
 *	races. This replaces identical code in packet,raw and udp, as well as the yet to
 *	be released IPX support. It also finally fixes the long standing peek and read
 *	race for datagram sockets. If you alter this routine remember it must be
 *	re-entrant.
 */
 //从接收队列中获取数据包
 //需要注意的是,这些函数(非udp.c文件下)或没有明确指明只与udp协议相关的函数则都是通用的
 //在tcp和udp协议下都可被调用
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err)
{
	struct sk_buff *skb;
	unsigned long intflags;

	/* Socket is inuse - so the timer doesn't attack it */
	save_flags(intflags);
restart:
	sk->inuse = 1;//加锁
	//检查套接字接收队列中是否有数据包
	//如果没有,则睡眠等待,在睡眠等待之前必须检查等待的必要性
	while(skb_peek(&sk->receive_queue) == NULL)	/* No data */
	{
		/* If we are shutdown then no more data is going to appear. We are done */
		//检查套接字是否已经被关闭接收通道,已经关闭通道了就没必要盲目等待了
		if (sk->shutdown & RCV_SHUTDOWN)
		{
			release_sock(sk);//对于udp无用,因为udp没有采用back_log暂存队列
			*err=0;
			return NULL;
		}
        //发生错误,则需要首先处理错误,返回
		if(sk->err)
		{
			release_sock(sk);
			*err=-sk->err;
			sk->err=0;
			return NULL;
		}

		/* Sequenced packets can come disconnected. If so we report the problem */
		//状态检查,如果不符合则置错误标志并返回
		if(sk->type==SOCK_SEQPACKET && sk->state!=TCP_ESTABLISHED)
		{
			release_sock(sk);
			*err=-ENOTCONN;
			return NULL;
		}

		/* User doesn't want to wait */
		//不阻塞,即调用者要求不进行睡眠等待,则直接返回
		if (noblock)
		{
			release_sock(sk);
			*err=-EAGAIN;
			return NULL;
		}

		//系列篇前面介绍过该函数的一个主要功能是重新接收back_log缓存队列中的数据包
	//由于udp协议不会使用back_log队列(用于tcp超时重发),所以该函数不会对套接字接收队列造成影响
		release_sock(sk);

		/* Interrupts off so that no packet arrives before we begin sleeping.
		   Otherwise we might miss our wake up */
		cli();
		//经过前面的一系列检查,这里再次判断是否队列中没有数据包
		//因为很有可能在上面检查过程中,有数据包到达
		if (skb_peek(&sk->receive_queue) == NULL)
		{
			interruptible_sleep_on(sk->sleep);//进入睡眠等待
			/* Signals may need a restart of the syscall */
			if (current->signal & ~current->blocked)
			{
				restore_flags(intflags);;
				*err=-ERESTARTSYS;
				return(NULL);
			}
			if(sk->err != 0)	/* Error while waiting for packet
						   eg an icmp sent earlier by the
						   peer has finally turned up now */
			{
				*err = -sk->err;
				sk->err=0;
				restore_flags(intflags);
				return NULL;
			}
		}
		sk->inuse = 1;//该套接字目前正在被本进程使用,不能被其余场所使用
		restore_flags(intflags);//恢复现场
	  }//end while
	  /* Again only user level code calls this function, so nothing interrupt level
	     will suddenly eat the receive_queue */

	 //如果接收队列中存在数据包
	 //处理正常读取的情况
	  if (!(flags & MSG_PEEK))
	  {
		skb=skb_dequeue(&sk->receive_queue);//从队列中获取数据包
		if(skb!=NULL)
			skb->users++;//使用该数据包的模块数+1
		else
			goto restart;	/* Avoid race if someone beats us to the data */
	  }
	  //如果设置了MSG_PEEK标志,允许查看已可读取的数据
	  //处理预先读取的情况
	  else
	  {
		cli();
		skb=skb_peek(&sk->receive_queue);
		if(skb!=NULL)
			skb->users++;
		restore_flags(intflags);
		if(skb==NULL)	/* shouldn't happen but .. */
			*err=-EAGAIN;
	  }
	  return skb;//返回该数据包
}

skb_copy_datagram()

  //将内核缓冲区中数据复制到用户缓冲区
 //拷贝size大小skb数据包中的数据负载(由offset偏移定位)到to缓冲区中
void skb_copy_datagram(struct sk_buff *skb, int offset, char *to, int size)
{
	/* We will know all about the fraglist options to allow >4K receives
	   but not this release */
	//函数原型:memcpy_tofs(to,from,n) :功能一目了然
	memcpy_tofs(to,skb->h.raw+offset,size);
}

skb_free_datagram()

 //释放一个数据包
 //先判断该数据包是否还有其余模块使用,再判断该数据包是否还处于系统的某个队列中,
 //换句话说,这两个判断的目的就是看该数据包是否还有用,没有用了就释放
void skb_free_datagram(struct sk_buff *skb)
{
	unsigned long flags;

	save_flags(flags);//保存现场
	cli();
	skb->users--;//使用该数据包的模块数-1
	if(skb->users>0)//如果还有模块使用该数据包,则直接返回
	{
		restore_flags(flags);
		return;
	}
	/* See if it needs destroying */
	//如果没有其余模块使用该数据包,表示这是一个游离的数据包
	//下面检查数据包是否仍处于系统某个队列中,如果还处于某个队列中则不可进行释放
	if(!skb->next && !skb->prev)	/* Been dequeued by someone - ie it's read */
		kfree_skb(skb,FREE_READ);//否则释放该数据包所占用的内存空间
	restore_flags(flags);//恢复现场
}

对比数据包的发送与接收,发送过程就是把数据从缓冲区拷贝到数据包的数据部分,由于需要经过协议栈,所以对于数据部分区域还需要进行数据封装,添加各层的协议头。对于数据包的接收,由于本来已经处于传输层了,不需要进行数据包的解封装,直接获取套接字接收队列中的数据包(如果有),然后再将数据包中的数据部分拷贝到缓冲区。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/163441.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 【JS】JS数组添加元素的三种方法「建议收藏」

    【JS】JS数组添加元素的三种方法「建议收藏」1、push()方法可向数组的末尾添加一个或多个元素,并返回新的长度。  1)、语法:arrayObject.push(newelement1,newelement2,….,newelementX)参数 描述 newelement1 必需。要添加到数组的第一个元素。 newelement2 可选。要添加到数组的第二个元素。 newelement…

  • zencart模板列表下载地址

    zencart模板列表下载地址下载index.html文件后用浏览器打开,里面有一百多个zencart模板示例下载地址:zencart模板示例下载地址或者复制下面网址,用浏览器打开即可下载:http://bcs.duapp.com

  • 越狱软件源

    越狱软件源全部内容来源于网络。不对其内容的安全性负责,本人不承担任何责任。使用任何内容即表示同意此内容。软件源地址ios6五百年源apt.so/ios6vxios6分享源apt.feng/eq2wowMK越狱源apt.so/mksscc盗版暗影apt.so/anyinkeji杀手源apt.so/tanyao正版暗影apt.so/anyinkj…

  • 上海java培训哪家教育机构比较好_上海it培训机构排名前十

    上海java培训哪家教育机构比较好_上海it培训机构排名前十学习Java编程不在是校内学生们的专利了,随着职场上竞争越来越激烈,毫无基础的或想要转行的求职者对于学习Java的积极性也是越来越高,因此,市面上涌现了大批的成人Java培训机构,为了不让大家纠结,小编花费了一周时间给大家专门整理了上海Java培训机构排名榜单,主要从口碑,师资,专一性,就业率等多方面得出的上海Java培训机构排名,仅供正在选择Java培训机构的小伙们参考。1.上海动力节点评价:动力节点大家学Java的话肯定也都听说过,单科教学,一直是秉承着从学员角度,全心全意保障学员利益.

  • jdbctemplate查询为空报错_java空指针异常举例

    jdbctemplate查询为空报错_java空指针异常举例问题是在:Dao类是不能直接new出来的必须是通过ApplicationContextapplicationContext=newClassPathXMLApplicationContext(“springmvc.xml”)erDaodao=(UserDao)ac.getBean(“UserDaoId得到类UserDao的实例化从而JdbcTemplate的值才能获…

    2022年10月30日
  • 【阅读笔记】数据分析思维:分析方法和业务知识

    【阅读笔记】数据分析思维:分析方法和业务知识这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与导入导出导入欢迎使用Markdown编辑器你好!这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Mar

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号