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 更多请看专栏, ...
随机推荐
- Android路径之Javascript基础-笔记
一.Javascript概述(知道) a.一种基于对象和事件驱动的脚本语言 b.作用: 给页面添加动态效果 c.历史: 原名叫做livescript.W3c组织开发的标准叫ECMAscipt. d.特 ...
- bzoj 2761: [JLOI2011]不重复数字 (map||Treap)
链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2761 思路: map标记 实现代码: #include<bits/stdc++.h&g ...
- 洛谷P3953 逛公园(NOIP2017)(最短/长路,拓扑排序,动态规划)
洛谷题目传送门 又是一年联赛季.NOIP2017至此收官了. 这个其实是比较套路的图论DP了,但是细节有点恶心. 先求出\(1\)到所有点的最短路\(d1\),和所有点到\(n\)的最短路\(dn\) ...
- LA3490 Generator(KMP + 高斯消元)
题意 一开始给你一个长为 \(S\) 的字符串. 从空串开始,不断在后面添加一个 \([A, A + n]\) 的一个字符. 第一次包含 \(S\) 的时候会停止添加.问期望的添加次数. 有 \(T\ ...
- Hdoj 2045.不容易系列之(3)—— LELE的RPG难题 题解
Problem Description 人称"AC女之杀手"的超级偶像LELE最近忽然玩起了深沉,这可急坏了众多"Cole"(LELE的粉丝,即"可乐 ...
- 自学Linux命令行与Shell脚本之路
自学Linux命令行与Shell脚本之路[第一回]:初识Linux 1.1 自学Linux Shell1.1-Linux初识 1.2 自学Linux Shell1.2-Linux目录结构 1.3 ...
- ZOJ 4062 Plants vs. Zombies(二分答案)
题目链接:Plants vs. Zombies 题意:从1到n每个位置一棵植物,植物每浇水一次,增加ai高度.人的初始位置为0,人每次能往左或往右走一步,走到哪个位置就浇水一次.求m步走完后最低高度的 ...
- LCOW —— 单一Docker引擎下可同时运行Linux和Windows容器啦!
https://blog.csdn.net/m2l0zgssvc7r69efdtj/article/details/79251059 就在上周,Docker官方的master分支上新增了LCOW(Li ...
- Nginx宣布正式支持gRPC,附示例代码
原创 2018-03-20 薛命灯 聊聊架构 作者|Owen Garrett编辑|薛命灯 NGINX 官方博客正式宣布 NGINX 支持原生的 gPRC,现在就可以从代码仓库拉取快照版本.该特性将会被 ...
- CentOS6.5安装JDK1.8
1.查看Linux自带的JDK是否已安装(如果安装则卸载CentOS已安装的低版本) 安装好的CentOS会自带OpenJdk,用命令 java -version,会有下面的信息: java -ver ...