简单的一个udp的服务程序主要有如下几步:
1:调用socket函数创建socket
2:调用bind函数指定在什么地址监听什么端口
3:调用recvfrom以及sendto进行首发包
看一个简单的示例代码:
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <string.h>
-
#include <netinet/in.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
#define MAXLINE 80
-
#define SERV_PORT 8888
-
-
void do_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen)
-
{
-
int n;
-
socklen_t len;
-
char mesg[MAXLINE];
-
-
for(;;)
-
{
-
len = clilen;
-
memset(mesg,0,MAXLINE);
-
/* waiting for receive data */
-
n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
-
/* sent data back to client */
-
sendto(sockfd, mesg, n, 0, pcliaddr, len);
-
}
-
}
-
-
int main(void)
-
{
-
int sockfd;
-
struct sockaddr_in servaddr, cliaddr;
-
-
sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* create a socket */
-
-
/* init servaddr */
-
bzero(&servaddr, sizeof(servaddr));
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
servaddr.sin_port = htons(SERV_PORT);
-
-
/* bind address and port to socket */
-
if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
-
{
-
perror("bind error");
-
exit(1);
-
}
-
-
do_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
-
-
return 0;
-
}
先看recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
其中sockfd为socket系统调用创建的fd,mesg为接收使用的缓冲区,pcliaddr是客户端的地址,如果只是接收的话这个参数可以不设置,但是后面要回应的话,就需要在接收的时候保存客户端的地址。
先看一下系统调用的入口:
-
SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
-
unsigned, flags, struct sockaddr __user *, addr,
-
int __user *, addr_len)
-
{
-
struct socket *sock;
-
struct iovec iov;
-
struct msghdr msg;
-
struct sockaddr_storage address;
-
int err, err2;
-
int fput_needed;
-
-
if (size > INT_MAX)
-
size = INT_MAX;
-
sock = sockfd_lookup_light(fd, &err, &fput_needed); //根据fd找到对应的socket数据结构
-
if (!sock)
-
goto out;
-
-
msg.msg_control = NULL; //根据传入参数赋值msghdr数据结构,接收缓冲区的地址和大小赋给msg_iov分量
-
msg.msg_controllen = 0;
-
msg.msg_iovlen = 1;
-
msg.msg_iov = &iov;
-
iov.iov_len = size;
-
iov.iov_base = ubuf;
-
msg.msg_name = (struct sockaddr *)&address; //定义局部变量address,用于保存客户端的地址
-
msg.msg_namelen = sizeof(address);
-
if (sock->file->f_flags & O_NONBLOCK)
-
flags |= MSG_DONTWAIT;
-
err = sock_recvmsg(sock, &msg, size, flags);//接收数据,sock->ops->recvmsg(iocb, sock, msg, size, flags);
-
-
if (err >= 0 && addr != NULL) {
-
err2 = move_addr_to_user((struct sockaddr *)&address, //把客户端的地址赋值给传入的参数
-
msg.msg_namelen, addr, addr_len);
-
if (err2 < 0)
-
err = err2;
-
}
-
-
fput_light(sock->file, fput_needed);
-
out:
-
return err;
-
}
sock->ops->recvmsg会调用inet_recvmsg:
-
int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
-
size_t size, int flags)
-
{
-
struct sock *sk = sock->sk;
-
int addr_len = 0;
-
int err;
-
-
sock_rps_record_flow(sk);
-
-
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT, //udp_recvmsg
-
flags & ~MSG_DONTWAIT, &addr_len);
-
if (err >= 0)
-
msg->msg_namelen = addr_len;
-
return err;
-
}
udp_recvmsg逻辑比较简单,没有数据就睡眠(没有制定nonblock),有数据就拷贝数据到缓冲区
-
int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
-
size_t len, int noblock, int flags, int *addr_len)
-
{
-
struct inet_sock *inet = inet_sk(sk);
-
struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
-
struct sk_buff *skb;
-
unsigned int ulen;
-
int peeked;
-
int err;
-
int is_udplite = IS_UDPLITE(sk);
-
bool slow;
-
-
/*
-
* Check any passed addresses
-
*/
-
if (addr_len)
-
*addr_len = sizeof(*sin);
-
-
if (flags & MSG_ERRQUEUE)
-
return ip_recv_error(sk, msg, len);
-
-
try_again:
-
skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0), //从sk_receive_queue队列中取skb,没有的话睡眠
-
&peeked, &err);
-
if (!skb)
-
goto out;
-
-
ulen = skb->len - sizeof(struct udphdr);
-
if (len > ulen) //可以看到一次接收调用只会取一个skb
-
len = ulen;
-
else if (len < ulen)
-
msg->msg_flags |= MSG_TRUNC; //比较skb中数据和接收buffer的大小
-
-
/*
-
* If checksum is needed at all, try to do it while copying the
-
* data. If the data is truncated, or if we only want a partial
-
* coverage checksum (UDP-Lite), do it before the copy.
-
*/
-
-
if (len < ulen || UDP_SKB_CB(skb)->partial_cov) { //如果只是拷贝skb的一部分数据的话,需要提前校验全部的数据,因为没法边拷贝边校验
-
if (udp_lib_checksum_complete(skb)) //如果硬件已经校验过的话,就不用了
-
goto csum_copy_err;
-
}
-
-
if (skb_csum_unnecessary(skb))
-
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),//把skb的数据拷贝到接收buffer中,包含线性区,frags以及frag_list
-
msg->msg_iov, len);
-
else {
-
err = skb_copy_and_csum_datagram_iovec(skb,
-
sizeof(struct udphdr),
-
msg->msg_iov);
-
-
if (err == -EINVAL)
-
goto csum_copy_err;
-
}
-
-
if (err)
-
goto out_free;
-
-
if (!peeked)
-
UDP_INC_STATS_USER(sock_net(sk),
-
UDP_MIB_INDATAGRAMS, is_udplite);
-
-
sock_recv_ts_and_drops(msg, sk, skb);
-
-
/* Copy the address. */
-
if (sin) {
-
sin->sin_family = AF_INET;
-
sin->sin_port = udp_hdr(skb)->source; //客户端的端口号
-
sin->sin_addr.s_addr = ip_hdr(skb)->saddr;//客户端的ip地址,后续回应的时候,sendto系统调用使用
-
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
-
}
-
if (inet->cmsg_flags)
-
ip_cmsg_recv(msg, skb);
-
-
err = len;
-
if (flags & MSG_TRUNC)
-
err = ulen;
-
-
out_free:
-
skb_free_datagram_locked(sk, skb);
-
out:
-
return err;
-
-
csum_copy_err:
-
slow = lock_sock_fast(sk);
-
if (!skb_kill_datagram(sk, skb, flags))
-
UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
-
unlock_sock_fast(sk, slow);
-
if (noblock)
-
return -EAGAIN;
-
-
/* starting over for a new packet */
-
msg->msg_flags &= ~MSG_TRUNC;
-
goto try_again;
-
}
该函数逻辑比较简单,其中取skb调用__skb_recv_datagram函数:
-
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
-
int *peeked, int *err)
-
{
-
struct sk_buff *skb;
-
long timeo;
-
/*
-
* Caller is allowed not to check sk->sk_err before skb_recv_datagram()
-
*/
-
int error = sock_error(sk);
-
-
if (error)
-
goto no_packet;
-
-
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); //noblock ? 0 : sk->sk_rcvtimeo;初始化的时候sk_rcvtimeo设置为死等
-
-
do {
-
/* Again only user level code calls this function, so nothing
-
* interrupt level will suddenly eat the receive_queue.
-
*
-
* Look at current nfs client by the way...
-
* However, this function was correct in any case. 8)
-
*/
-
unsigned long cpu_flags;
-
-
spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
-
skb = skb_peek(&sk->sk_receive_queue);
-
if (skb) {
-
*peeked = skb->peeked;
-
if (flags & MSG_PEEK) {
-
skb->peeked = 1;
-
atomic_inc(&skb->users);
-
} else
-
__skb_unlink(skb, &sk->sk_receive_queue);
-
}
-
spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
-
-
if (skb) //有数据就返回,没有数据就等待
-
return skb;
-
-
error = -EAGAIN;
-
if (!timeo)
-
goto no_packet;
-
} while (!wait_for_packet(sk, err, &timeo)); //把自己放到sk_sleep对应的等待链表中,然后调用schedule_timeout
-
return NULL;
-
no_packet:
-
*err = error;
-
return NULL;
-
}
udp的接收系统调用比较简单,不涉及各种状态的转换,有数据的时候就拷贝,没有的话等待,当底层收到包的时候,会唤醒当前进程
下面分析bind流程:bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)
该系统调用会调inet_bind:
-
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
-
{
-
struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
-
struct sock *sk = sock->sk;
-
struct inet_sock *inet = inet_sk(sk);
-
unsigned short snum;
-
int chk_addr_ret;
-
int err;
-
-
/* If the socket has its own bind function then use it. (RAW) */
-
if (sk->sk_prot->bind) { //udp没有自己的bind函数
-
err = sk->sk_prot->bind(sk, uaddr, addr_len);
-
goto out;
-
}
-
err = -EINVAL;
-
if (addr_len < sizeof(struct sockaddr_in))
-
goto out;
-
-
if (addr->sin_family != AF_INET) {
-
/* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
-
* only if s_addr is INADDR_ANY.
-
*/
-
err = -EAFNOSUPPORT;
-
if (addr->sin_family != AF_UNSPEC ||
-
addr->sin_addr.s_addr != htonl(INADDR_ANY))
-
goto out;
-
}
-
-
chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
-
-
/* Not specified by any standard per-se, however it breaks too
-
* many applications when removed. It is unfortunate since
-
* allowing applications to make a non-local bind solves
-
* several problems with systems using dynamic addressing.
-
* (ie. your servers still start up even if your ISDN link
-
* is temporarily down)
-
*/
-
err = -EADDRNOTAVAIL;
-
if (!sysctl_ip_nonlocal_bind && //bind绑定的地址一般为INADDR_ANY或者一个本地的ip值
-
!(inet->freebind || inet->transparent) &&
-
addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
-
chk_addr_ret != RTN_LOCAL &&
-
chk_addr_ret != RTN_MULTICAST &&
-
chk_addr_ret != RTN_BROADCAST)
-
goto out;
-
-
snum = ntohs(addr->sin_port); //bind对应的端口号
-
err = -EACCES;
-
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE)) //0-1023只有root用户可以设置
-
goto out;
-
-
/* We keep a pair of addresses. rcv_saddr is the one
-
* used by hash lookups, and saddr is used for transmit.
-
*
-
* In the BSD API these are the same except where it
-
* would be illegal to use them (multicast/broadcast) in
-
* which case the sending device address is used.
-
*/
-
lock_sock(sk);
-
-
/* Check these errors (active socket, double bind). */
-
err = -EINVAL;
-
if (sk->sk_state != TCP_CLOSE || inet->inet_num)
-
goto out_release_sock;
-
-
inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;//inet_rcv_saddr用于hash查找,参考__udp4_lib_lookup_skb
-
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
-
inet->inet_saddr = 0; /* Use device */
-
-
/* Make sure we are allowed to bind here. */
-
if (sk->sk_prot->get_port(sk, snum)) { //udp_v4_get_port
-
inet->inet_saddr = inet->inet_rcv_saddr = 0;
-
err = -EADDRINUSE;
-
goto out_release_sock;
-
}
-
-
if (inet->inet_rcv_saddr)
-
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
-
if (snum)
-
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
-
inet->inet_sport = htons(inet->inet_num); //用于hash查找
-
inet->inet_daddr = 0;
-
inet->inet_dport = 0;
-
sk_dst_reset(sk);
-
err = 0;
-
out_release_sock:
-
release_sock(sk);
-
out:
-
return err;
-
}
阅读(2678) | 评论(0) | 转发(0) |