【Linux内核⽹络协议栈源码剖析】sendto函数剖析 前⾯介绍的函数基本上都是TCP协议的,如listen,connect,accept 等函数,这都是为可靠传输协议TCP定制的。对于另⼀个不可靠udp 协议(通信系统其可靠性交由上层应⽤层负责),则主要由两个函数完成,sendto 和 recvfrom 函数。这⾥先介绍 sendto 函数。
说明:sendto 和 recvfrom 函数不限于udp协议,这⾥只是udp协议当中是采⽤这两个函数实现的,所以就放在udp协议中介绍。
对于 udp 协议的介绍和编程实现请参考下⽂:
简要介绍下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;//⽹络接⼝设备