UDP协议
本文分析基于Linux Kernel 1.2.13
原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512
更多请看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html
作者:闫明
注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。
这里看看数据包从IP层是如何交给传输层来处理的,为了方便,这里传输层以UDP协议为例来分析。
从ip_rcv()函数中可以看到
- /*
- * Pass on the datagram to each protocol that wants it,
- * based on the datagram protocol. We should really
- * check the protocol handler's return values here...
- */
- ipprot->handler(skb2, dev, opts_p ? &opt : 0, iph->daddr,
- (ntohs(iph->tot_len) - (iph->ihl * 4)),
- iph->saddr, 0, ipprot);
这里调用指定协议的handler函数,如果是UDP协议,该函数的定义 udp_protocol如下
- static struct inet_protocol udp_protocol = {
- udp_rcv, /* UDP handler */
- NULL, /* Will be UDP fraglist handler */
- udp_err, /* UDP error control */
- &tcp_protocol, /* next */
- IPPROTO_UDP, /* protocol ID */
- 0, /* copy */
- NULL, /* data */
- "UDP" /* name */
- };
先看UDP协议数据报的报头定义如下:比较简单

- struct udphdr {
- unsigned short source;//源端口
- unsigned short dest;//目的端口
- unsigned short len;//数据包长度
- unsigned short check;//检验和
- };
下面就分析下udp_rcv()函数,流程图:

- /*
- * All we need to do is get the socket, and then do a checksum.
- */
- int udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
- unsigned long daddr, unsigned short len,
- unsigned long saddr, int redo, struct inet_protocol *protocol)
- {
- struct sock *sk;
- struct udphdr *uh;
- unsigned short ulen;
- int addr_type = IS_MYADDR;
- if(!dev || dev->pa_addr!=daddr)//检查这个数据包是不是发送给本地的数据包
- addr_type=ip_chk_addr(daddr);//该函数定义在devinet.c中,用于检查ip地址是否是本地或多播、广播地址
- /*
- * Get the header.
- */
- uh = (struct udphdr *) skb->h.uh;//获得UDP数据报的报头
- ip_statistics.IpInDelivers++;
- /*
- * Validate the packet and the UDP length.
- */
- ulen = ntohs(uh->len);
- //参数len表示ip负载长度(IP数据报的数据部分长度)= UDP数据包头+UDP数据包的数据部分+填充部分长度
- //ulen表示的是UDP数据报首部和负载部分的长度,所以正常情况下len>=ulen
- if (ulen > len || len < sizeof(*uh) || ulen < sizeof(*uh))
- {
- printk("UDP: short packet: %d/%d\n", ulen, len);
- udp_statistics.UdpInErrors++;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- if (uh->check && udp_check(uh, len, saddr, daddr)) //进行UDP数据包校验
- {
- /* <mea@utu.fi> wants to know, who sent it, to
- go and stomp on the garbage sender... */
- printk("UDP: bad checksum. From %08lX:%d to %08lX:%d ulen %d\n",
- ntohl(saddr),ntohs(uh->source),
- ntohl(daddr),ntohs(uh->dest),
- ulen);
- udp_statistics.UdpInErrors++;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- len=ulen;//对len赋值为实际的UDP数据报长度
- #ifdef CONFIG_IP_MULTICAST//对多播情况进行处理
- if (addr_type!=IS_MYADDR)
- {
- /*
- * Multicasts and broadcasts go to each listener.
- */
- struct sock *sknext=NULL;//next指针
- /*get_sock_mcast 获取在对应端口的多播套接字队列
- *下面函数的参数依次表示:sock结构指针,本地端口,远端地址,远端端口,本地地址
- */
- sk=get_sock_mcast(udp_prot.sock_array[ntohs(uh->dest)&(SOCK_ARRAY_SIZE-1)], uh->dest,
- saddr, uh->source, daddr);
- if(sk)
- {
- do
- {
- struct sk_buff *skb1;
- sknext=get_sock_mcast(sk->next, uh->dest, saddr, uh->source, daddr);//下一个满足条件的套接字
- if(sknext)
- skb1=skb_clone(skb,GFP_ATOMIC);
- else
- skb1=skb;
- if(skb1)
- udp_deliver(sk, uh, skb1, dev,saddr,daddr,len);//对满足条件的套接字调用发送函数发送
- sk=sknext;
- }
- while(sknext!=NULL);
- }
- else
- kfree_skb(skb, FREE_READ);
- return 0;
- }
- #endif
- sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr);
- if (sk == NULL) //没有找到本地对应的套接字,则进行出错处理
- {
- udp_statistics.UdpNoPorts++;
- if (addr_type == IS_MYADDR)
- {
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev);//回复ICMP出错报文,目的主机不可达
- }
- /*
- * Hmm. We got an UDP broadcast to a port to which we
- * don't wanna listen. Ignore it.
- */
- skb->sk = NULL;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- return udp_deliver(sk,uh,skb,dev, saddr, daddr, len);//调用函数发送套接字
- }
上面函数中调用了get_sock_mcast()函数,下面具体分析一下该函数的功能,该函数定义的位置在文件af_inet.c中
- /*
- * Deliver a datagram to broadcast/multicast sockets.
- */
- struct sock *get_sock_mcast(struct sock *sk, //套接字指针
- unsigned short num,//本地端口
- unsigned long raddr,//远端地址
- unsigned short rnum,//远端端口
- unsigned long laddr)//本地地址
- {
- struct sock *s;
- unsigned short hnum;
- hnum = ntohs(num);
- /*
- * SOCK_ARRAY_SIZE must be a power of two. This will work better
- * than a prime unless 3 or more sockets end up using the same
- * array entry. This should not be a problem because most
- * well known sockets don't overlap that much, and for
- * the other ones, we can just be careful about picking our
- * socket number when we choose an arbitrary one.
- */
- s=sk;
- for(; s != NULL; s = s->next)
- {
- if (s->num != hnum) //本地端口不符合,跳过
- continue;
- if(s->dead && (s->state == TCP_CLOSE))//dead=1表示该sock结构已经处于释放状态
- continue;
- if(s->daddr && s->daddr!=raddr)//sock的远端地址不等于条件中的远端地址
- continue;
- if (s->dummy_th.dest != rnum && s->dummy_th.dest != 0)
- continue;
- if(s->saddr && s->saddr!=laddr)//sock的本地地址不等于条件的本地地址
- continue;
- return(s);
- }
- return(NULL);
- }
下面是udp_rcv调用的udp_deliver()函数
- static int udp_deliver(struct sock *sk,//sock结构指针
- struct udphdr *uh,//UDP头指针
- struct sk_buff *skb,//sk_buff
- struct device *dev,//接收的网络设备
- long saddr,//本地地址
- long daddr,//远端地址
- int len)//数据包的长度
- {
- //对skb结构相应字段赋值
- skb->sk = sk;
- skb->dev = dev;
- //skb->len = len;
- /*
- * These are supposed to be switched.
- */
- skb->daddr = saddr;//设置目的地址为本地地址
- skb->saddr = daddr;//设置源地址为远端地址
- /*
- * Charge it to the socket, dropping if the queue is full.
- */
- skb->len = len - sizeof(*uh);
- if (sock_queue_rcv_skb(sk,skb)<0) //调用sock_queu_rcv_skb()函数,将skb挂到sk接构中的接收队列中
- {
- udp_statistics.UdpInErrors++;
- ip_statistics.IpInDiscards++;
- ip_statistics.IpInDelivers--;
- skb->sk = NULL;
- kfree_skb(skb, FREE_WRITE);
- release_sock(sk);
- return(0);
- }
- udp_statistics.UdpInDatagrams++;
- release_sock(sk);
- return(0);
- }
sock_queu_rcv_skb()函数的实现如下:
- /*
- * Queue a received datagram if it will fit. Stream and sequenced protocols
- * can't normally use this as they need to fit buffers in and play with them.
- */
- int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
- {
- unsigned long flags;
- if(sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)
- return -ENOMEM;
- save_flags(flags);
- cli();
- sk->rmem_alloc+=skb->mem_len;
- skb->sk=sk;
- restore_flags(flags);
- skb_queue_tail(&sk->receive_queue,skb);
- if(!sk->dead)
- sk->data_ready(sk,skb->len);
- return 0;
- }
UDP协议的更多相关文章
- TODO:Golang语言TCP/UDP协议重用地址端口
TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...
- 闲来无事,写个基于UDP协议的Socket通讯Demo
项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...
- UDP协议开发
UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...
- 基于UDP协议模拟的一个TCP协议传输系统
TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...
- TCP协议与UDP协议的区别
TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...
- 采用UDP协议的PIC32MZ ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...
- 采用UDP协议实现PIC18F97J60 ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...
随机推荐
- Semi-prime H-numbers POJ - 3292 打表(算复杂度)
题意:参考https://blog.csdn.net/lyy289065406/article/details/6648537 一个H-number是所有的模四余一的数. 如果一个H-number是H ...
- 洛谷P3870开关题解
我们先看题面,一看是一个区间操作,再看一下数据范围,就可以很轻松地想到是用一个数据结构来加快区间查询和修改的速度,所以我们很自然的就想到了线段树. 但是这个题还跟普通的线段树不一样,这个题可以说要思考 ...
- genymotion ddms查看data等文件目录
使用ADB shell 命令: 打开 Cmd ,输入 ADB shell 命令后,回车(前提是你已经配置好了adb 的环境变量,跟配置Java的环境变量一样); 输入su回车,获取超级管理员权限 ...
- 【Vijos】lxhgww的奇思妙想(长链剖分)
题面 给定一棵树,每次询问一个点的\(k\)次祖先,强制在线. Vijos 题解 长链剖分. 链接暂时咕咕咕了. 现在可以戳链接看题解了 #include<iostream> #inclu ...
- 【BZOJ1188】分裂游戏(博弈论)
[BZOJ1188]分裂游戏(博弈论) 题面 BZOJ 洛谷 题解 这道题目比较神仙. 首先观察结束状态,即\(P\)状态,此时必定是所有的豆子都在最后一个瓶子中. 发现每次的转移一定是拿出一棵豆子, ...
- 【BZOJ5197】Gambling Guide (最短路,期望)
[BZOJ5197]Gambling Guide (最短路,期望) 题面 BZOJ权限题 洛谷 题解 假设我们求出了每个点的期望,那么对于一个点,只有向期望更小的点移动的时候才会更新答案. 即转移是: ...
- python中,print函数的sep和end参数
print函数是我们经常使用的,但是它的sep和end参数或许对很多python使用者相对陌生,他们可以让我们的打印更具有个性化. 先来看下官方解释, sep:分割值与值,默认是一个空格 end:附件 ...
- angular2路由与express路由冲突的问题
angular2的路由定义了一个/a,如果走angular的路由没问题,如果直接访问/a就会出现cannot GET /a的错误,原因就是express的路由问题. 所以路由走angular2,那ex ...
- Educational Codeforces Round 46 C - Covered Points Count
C - Covered Points Count emmm 好像是先离散化一下 注意 R需要+1 这样可以确定端点 emmm 扫描线?瞎搞一下? #include<bits/stdc++.h&g ...
- Home School Books美国家庭学校教育小学初中高中全套美语教材
加州的资料总共买过三次: ①优妈妈儿童教育,买过美国加州小学一.二年级的语文及相应的练习册,并买了纸版资料. (这是自己学习用的) ②美国加州原版小学教材Reading Wonders 2014新版语 ...