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

前面介绍的函数基本上都是TCP协议的,如listen,connect,accept 等函数,这都是为可靠传输协议TCP定制的。对于另一个不可靠udp协议(通信系统其可靠性交由上层应用层负责),则主要由两个函数完成,sendto 和 recvfrom 函数。这里先介绍 sendto 函数。

说明:sendto 和 recvfrom 函数不限于udp协议,这里只是udp协议当中是采用这两个函数实现的,所以就放在udp协议中介绍。

对于 udp 协议的介绍和编程实现请参考下文:UDP 客户/服务器简单 Socket 程序

简要介绍下UDP数据报格式,相比TCP数据报格式,实在是简洁不少。

                                  

上面的各个字段含义一目了然(上面是16是表示该字段占16bit,udp头部占8字节),其中长度指的是此 UDP 数据报的长度(包括 UDP 数据报头部和 “数据” 部分)。

一、应用层——sendto 函数

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
	           const struct sockaddr *to, socklen_t *addrlen);
//若成功则返回写的字节数,出错则返回-1
/*参数解析
前面三个参数分别表示:套接字描述符,指向写出缓冲区的指针和写字节数。
to:指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen参数指定
*/
该函数的作用是:向指定端口发送给定地址中的指定大小数据(如客户端sockfd,向 to 指定的远端套接字发送buff 缓冲区内nbytes 个字节数据)

二、BSD Socket层——sock_sendto 函数

/*
 *	Send a datagram to a given address. We move the address into kernel
 *	space and check the user space data area is readable before invoking
 *	the protocol.
 */
//发送数据给指定的远端地址,主要用于UDP协议
//前面三个参数分别表示套接口描述字、指向缓冲区的指针和读写字节数
//addr指向一个含有数据包接收者的协议地址(含ip地址和端口号)的套接口地址结构
//其大小由addr_len参数指定
//该函数的作用就是向指定地址的远端发送数据包:将buff缓冲区中len大小的数据发送给addr指定的远端套接字
static int sock_sendto(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;
	//参数有效性检查
	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;
	//检查权限,buff中len个字节区域是否可读
	err=verify_area(VERIFY_READ,buff,len);
	if(err)
	  	return err;
  	//从addr拷贝addr_len大小的数据到address
	if((err=move_addr_to_kernel(addr,addr_len,address))<0)
	  	return err;
    //调用下层函数sendto,inet域为inet_sendto函数
	return(sock->ops->sendto(sock, buff, len, (file->f_flags & O_NONBLOCK),
		flags, (struct sockaddr *)address, addr_len));
}
三、INET Socket层——inet_sendto 函数
 //INET socket层
static int inet_sendto(struct socket *sock, void *ubuf, int size, int noblock, 
	    unsigned flags, struct sockaddr *sin, int addr_len)
{
    //得到socket对应的sock结构
	struct sock *sk = (struct sock *) sock->data;
	//判断该套接字的有效性,是否处于关闭状态(半关闭)
	if (sk->shutdown & SEND_SHUTDOWN) 
	{
		send_sig(SIGPIPE, current, 1);
		return(-EPIPE);
	}
	if (sk->prot->sendto == 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_sendto函数
	return(sk->prot->sendto(sk, (unsigned char *) ubuf, size, noblock, flags, 
			   (struct sockaddr_in *)sin, addr_len));
}
四、传输层

udp_sento 函数

static int udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock,
	   unsigned flags, struct sockaddr_in *usin, int addr_len)
{
	struct sockaddr_in sin;
	int tmp;

	/* 
	 *	Check the flags. We support no flags for UDP sending
	 */
	 //udp除了MSG_DONTROUTE外,不支持任何其他标志位
	if (flags&~MSG_DONTROUTE) 
	  	return(-EINVAL);
	/*
	 *	Get and verify the address. 
	 */
	//对远端地址的合法性检查,由于不涉及网络数据传送,所以无法验证这个地址存在性
	
	if (usin) 
	{
	//如果明确指定远端地址,就直接检查该地址的有效性
		if (addr_len < sizeof(sin)) //大小
			return(-EINVAL);
		memcpy(&sin,usin,sizeof(sin));
		if (sin.sin_family && sin.sin_family != AF_INET) //本地地址有效性
			return(-EINVAL);
		if (sin.sin_port == 0) //端口号有效性
			return(-EINVAL);
	} 
	else 
	{
	//如果没有明确指定远端地址,则检查之前是否调用了connect函数进行了地址绑定
		if (sk->state != TCP_ESTABLISHED) 
			return(-EINVAL);
		//如果进行了绑定,则将远端地址设置为这个绑定的地址
		sin.sin_family = AF_INET;
		sin.sin_port = sk->dummy_th.dest;
		sin.sin_addr.s_addr = sk->daddr;
  	}
  
  	/*
  	 *	BSD socket semantics. You must set SO_BROADCAST to permit
  	 *	broadcasting of data.
  	 */
  	//处理尚未指定本地地址的情况
  	if(sin.sin_addr.s_addr==INADDR_ANY)
  		sin.sin_addr.s_addr=ip_my_addr();

	//处理广播的情况
  	if(!sk->broadcast && ip_chk_addr(sin.sin_addr.s_addr)==IS_BROADCAST)
	    	return -EACCES;			/* Must turn broadcast on first */

	sk->inuse = 1;//加锁

	/* Send the packet. */
	//转调用udp_send函数
	tmp = udp_send(sk, &sin, from, len, flags);

	/* The datagram has been sent off.  Release the socket. */
	//数据包以发送,释放该套接字,前面介绍到这个函数的两个功能
	//取决于sk_dead字段是否设置
	release_sock(sk);
	return(tmp);
}
udp_send 函数
 //根据被调用出清楚参数情况
static int udp_send(struct sock *sk, struct sockaddr_in *sin,
	 unsigned char *from, int len, int rt)
{
	struct sk_buff *skb;
	struct device *dev;
	struct udphdr *uh;
	unsigned char *buff;
	unsigned long saddr;
	int size, tmp;
	int ttl;
  
	/* 
	 *	Allocate an sk_buff copy of the packet.
	 */
	//计算所需要分配的封装数据的缓冲区大小 
	size = sk->prot->max_header + len;
	//分配指定大小的sk_buff 结构用于封装数据
	skb = sock_alloc_send_skb(sk, size, 0, &tmp);


	if (skb == NULL) 
		return tmp;

	skb->sk       = NULL;	/* to avoid changing sk->saddr */
	skb->free     = 1;//发送完后数据包立即释放,udp不提供超时重传
	skb->localroute = sk->localroute|(rt&MSG_DONTROUTE);//指定路由类型

	/*
	 *	Now build the IP and MAC header. 
	 */
	 
	buff = skb->data;//udp首部和有效负载
	saddr = sk->saddr;//本地地址
	dev = NULL;
	ttl = sk->ip_ttl;
#ifdef CONFIG_IP_MULTICAST
	//如果目的地址是多播,则设置TTL值为1,表示局限于本地网络,不可跨越路由器

	if (MULTICAST(sin->sin_addr.s_addr))
		ttl = sk->ip_mc_ttl;
#endif
	//创建MAC首部和IP首部
	tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr,
			&dev, IPPROTO_UDP, sk->opt, skb->mem_len,sk->ip_tos,ttl);

	skb->sk=sk;//关联	/* So memory is freed correctly */
	
	/*
	 *	Unable to put a header on the packet.
	 */
	 		    
	if (tmp < 0 ) //创建失败
	{
		sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);
		return(tmp);
  	}
  	
	buff += tmp;//定位到udp首部位置
	saddr = skb->saddr; /*dev->pa_addr;*/
	//数据报sk_buff中挂载的数据部分长度:下面注释,len是有效数据负载长度
	skb->len = tmp + sizeof(struct udphdr) + len;	/* len + UDP + IP + MAC */
	skb->dev = dev;//网络接口设备
	
	/*
	 *	Fill in the UDP header. 
	 */
	//udp首部字段的初始化
	uh = (struct udphdr *) buff;
	uh->len = htons(len + sizeof(struct udphdr));//长度字段
	uh->source = sk->dummy_th.source;//源端端口,sk中tcp首部字段
	uh->dest = sin->sin_port;//目的端口
	buff = (unsigned char *) (uh + 1);//定位到数据部分
	//MAC header | IP Header | UDP Header | Data
	//uh本身已经指向了udp首地址,uh+1,表示后移一个udp首部大小位置,定位到了数据负载

	/*
	 *	Copy the user data. 
	 */
	//从from拷贝len大小的数据到buff,即把应用层中待发送的缓冲区的数据拷贝到数据包的数据负载中
	//然后通过数据包整体打包发送出去。
	//就好比货物搭上了货轮开往目的地,为啥不是火车呢,因为火车线路已经固定好了,只能这么走。
	memcpy_fromfs(buff, from, len);

  	/*
  	 *	Set up the UDP checksum. 
  	 */
  	//同tcp,这里进行udp校验和检查 
	udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);

	/* 
	 *	Send the datagram to the interface. 
	 */
	 
	udp_statistics.UdpOutDatagrams++;
	//调用ip_queue_xmit函数将数据包发往网络层模块处理。以下处理就和TCP协议一样了,二者的差异只在于传输层
	//该函数以及更下层数据传送前面已经介绍,
	sk->prot->queue_xmit(sk, dev, skb, 1);
	return(len);
}
关于ip_queue_xmit 函数的介绍以及更下层的数据传送,参见博文: 【Linux 内核网络协议栈源码剖析】数据包发送

可以看出,udp是一种无连接传输层协议,不像tcp那样需要服务器监听,也不必等待客户端与服务器建立连接后才能通信,效率优于tcp协议,但udp则不能保证数据传输的可靠性。
udp 的数据传输,实现并不像tcp那样要建立一条数据传输通道,而是直接创建套接字后,直接传送数据到给定的远端(提供远端地址),数据传送过程无超时重传和序列号校验工作,适用于数据传输的连续性比数据的完整性更重要的场合,允许数据在传输过程中有部分丢失,如IP电话、流媒体通信等。


                                                                                           




评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值