UDP介绍

UDP是一个简单的面向数据报的传输层协议,RFC768是UDP的正式规范。应用程序的每个输出操作(也即send操作)都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。这与面向流字符的协议不同,如TCP,应用程序产生的数据与真正发送的IP数据报的数量没有什么联系。

面向字节流和面向数据报是两种不同的传输方式,它们在数据的传输和处理方式上有一些区别。

面向字节流传输是指数据的传输是连续的字节流,没有明确的消息边界。数据在发送端被分割成一系列的字节,然后按照顺序发送给接收端。接收端根据接收到的字节流进行解析和处理。TCP(传输控制协议)是一个典型的面向字节流的协议。面向字节流传输的特点包括:

  1. 无消息边界:数据被分割成字节流进行传输,接收端需要自行解析消息边界。

  2. 可靠性:TCP提供可靠的传输,确保数据的有序传输和不丢失。

  3. 流量控制:TCP使用滑动窗口协议来控制发送和接收数据的速率,以防止数据的过载。

  4. 有序性:TCP保证数据按照发送的顺序进行接收和处理。

相比之下,面向数据报传输是指数据被分割成一个个独立的数据报进行传输。每个数据报都有自己的消息边界,包含了完整的数据和相关的控制信息。UDP(用户数据报协议)是一个典型的面向数据报的协议。面向数据报传输的特点包括:

  1. 有消息边界:数据被分割成独立的数据报进行传输,每个数据报都有自己的消息边界。

  2. 不可靠性:UDP提供不可靠的传输,数据可能丢失、重复或乱序。

  3. 无流量控制:UDP没有流量控制机制,发送方可以按照自己的速率发送数据。

  4. 无序性:UDP不能保证数据按照发送的顺序进行接收和处理。

总结来说,面向字节流传输适合需要可靠性和有序性的应用,如文件传输和网页浏览。而面向数据报传输适合实时性要求高、数据量较小且对可靠性要求不高的应用,如音频和视频传输。

UDP报文封装

img点击并拖拽以移动编辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 0                       7 8     15 16    23 24    31  
                 +--------+--------+--------+--------+ 
                 |     Source      |   Destination   | 
                 |      Port       |      Port       | 
                 +--------+--------+--------+--------+ 
                 |                 |                 | 
                 |     Length      |    Checksum     | 
                 +--------+--------+--------+--------+ 
                 |                                     
                 |          data octets ...            
                 +---------------- ...                 

                      User Datagram Header Format

端口号表示发送进程和接收进程,UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为8字节(发送一份0字节的UDP数据报是OK的)。这个UDP长度是有冗余的。IP数据报长度指的是数据报全长,因此UDP数据报长度是全长减去IP首部的长度。

特点:不可靠、易分片

UDP不提供可靠性:它把应用程序传给IP层的数据发送出去,但是并不保证它们能到达目的地。

分片:应用程序必须关心IP数据报的长度。如果它超过网络的MTU,那么就要对IP数据报进行分片。如果需要,源端到目的端之间的每个网络都要进行分片,并不只是发送端主机连接第一个网络才这样做。

IP分片

物理网络层一般要限制每次发送数据帧的最大长度。任何时候IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(选路),并查询该接口获得其MTU。IP把MTU与数据报长度进行比较,如果需要则进行分片。分片可以发生在原始发送端主机上,也可以发生在中间路由器上。把一份IP数据报分片以后,只有到达目的地才进行重新组装。

IP首部中部分字段用于报文分片组装,具体参见tcp/ip协议(3)Internet Protocol(IP)

对于发送端发送的每份IP数据报来说,其标识字段都包含一个唯一值。该值在数据报分片时被复制到每个片中。标志字段用其中一个比特来表示“更多的片”。除了最后一片外,其他每个组成数据报的片都要把该比特置1,下面简单抓一个分片ping包看一下

第一片报文

img

第二片报文

img

在分片时,除最后一片外,其他每一片中的数据部分(除IP首部外的其余部分)必须是8字节的整数倍。

标志字段中有一个比特称作“不分片”位。如果将这一比特置1,IP将不对数据报进行分片。相反把数据报丢弃并发送一个ICMP差错报文给起始端

尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重传整个数据报文。为什么会发生这种情况呢?因为IP层本身没有超时重传的机制——由更高层来负责超时和重传(TCP有超时和重传机制,但UDP没有)

IP数据报:是指IP层端到端的传输单元(在分片之前和重新组装之后)

分组是指在IP层和链路层之间传送的数据单元。一个分组可以是一个完整的IP数据报,也可以是IP数据报的一个分片。

img

UDP数据报最大长度

理论上,IP数据报的最大长度是65535字节,这是由IP首部16比特总长度字段所限制的。去除20字节的IP首部和8个字节的UDP首部, UDP数据报中用户数据的最长长度为65507字节。但是,大多数实现所提供的长度比这个最大值小。其中两个限制因素:

  • 第一,应用程序可能会受到其程序接口的限制。socket API提供了一个可供应用程序调用的函数,以设置接收和发送缓存的长度。对于UDP socket,这个长度与应用程序可以读写的最大U D P数据报的长度直接相关。现在的大部分系统都默认提供了可读写大于8192字节的UDP数据报

  • 第二个限制来自于TCP/IP的内核实现。可能存在一些实现特性(或差错),使IP数据报长度小于65535字节

UDP服务器的设计与实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*********************************************************************************
  *Author     :  wph
  *Version    :  1.0
  *Date       :  2014/03/01  
  *Description:  udp server 
  *Others     : 
  *History    :   
**********************************************************************************/
#include<stdio.h>  
#include<string.h>  
#include<unistd.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<stdlib.h>  
#include<netinet/in.h>  
#include<arpa/inet.h>
#include<event2/event.h>

#include "errocode.h"
#include "basetype.h"

#define INVALID_FD		-1
#define PORT 			1234  
#define MAXDATASIZE 	512 

STATIC INT g_iudpFd = INVALID_FD;

VOID udp_callback(evutil_socket_t fd, short what, void *arg)
{
	struct sockaddr_in client;  
	socklen_t addrlen;  
	int num;  
	char buf[MAXDATASIZE]; 
	
	memset(buf, 0, MAXDATASIZE);
	num = recvfrom(fd, buf, MAXDATASIZE, 0, (struct sockaddr*)&client, &addrlen);                                     
	if (num < 0)  
	{  
		perror("recvfrom() error\n");  
		exit(1);  
	} 
	
	printf("You got a message (%s) from client.\nIt's ip is%s, port is %d.\n", 
		    buf, inet_ntoa(client.sin_addr), htons(client.sin_port)); 
	
    sendto(fd, buf, num, 0, (struct sockaddr *)&client, addrlen);  
}

ULONG udp_init(VOID)
{
	int sockfd;  
	struct sockaddr_in server;  
	
	/* The caller has already set up fd1, fd2 somehow, and make them
	   nonblocking. */
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(INVALID_FD == sockfd)   
	{  
		perror("Creatingsocket failed.");  
		exit(1);  
	} 
	
	bzero(&server, sizeof(server));  
	server.sin_family = AF_INET;  
	server.sin_port= htons(PORT);  
	server.sin_addr.s_addr= htonl(INADDR_ANY);  
	if(-1 == bind(sockfd, (struct sockaddr *)&server, sizeof(server)))  
	{  
		perror("Bind()error.");  
		exit(1);  
	}
	
	g_iudpFd = sockfd;
	
	return EROOR_SUCCESS;
}

VOID udp_fini(VOID)
{
	int sockfd = g_iudpFd;  

	if (INVALID_FD != sockfd)
	{	
		close(sockfd);
	}
}
VOID main_loop(VOID)
{
	INT ifd = g_iudpFd;
        struct event *ev1;
	struct timeval five_seconds = {5,0};
	struct event_base *base = event_base_new();

	/* upd 服务器采用libevet进行处理 */
	ev1 = event_new(base, ifd, EV_TIMEOUT|EV_READ|EV_PERSIST, udp_callback, NULL);

	event_add(ev1, &five_seconds);
	event_base_dispatch(base);
	
    return ;
}
INT main()
{
	if(EROOR_SUCCESS != udp_init())
	{
		return -1;
	}
	
	main_loop();

	udp_fini();
	
	return 0;
}

客户端实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/*********************************************************************************
  *Copyright(C),2010-2011,
  *Author     :  wph
  *Version    :  1.0
  *Date       :  2014/03/01  
  *Description:  udp client 
  *Others     : 
  *History    :   
**********************************************************************************/
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <netdb.h>  

#include "errocode.h"
#include "basetype.h"

#define INVALID_FD		-1
#define PORT 			1234  
#define MAXDATASIZE 	512 
 
INT main(INT argc, CHAR *argv[])  
{  
	INT  isockfd = INVALID_FD;
    UINT uinum = 0;  
	char buf[MAXDATASIZE];
	socklen_t  addrlen;  
	struct hostent *he;  
	struct sockaddr_in server;
	struct sockaddr_in peer;  

	if (3 != argc)  
	{  
		printf("Usage: %s <IP Address><message>\n", argv[0]);  
		exit(1);  
	}  

	if (NULL == (he=gethostbyname(argv[1])))  
	{  
		printf("gethostbyname()error\n");  
		exit(1);  
	}  

	if (INVALID_FD == (isockfd = socket(AF_INET, SOCK_DGRAM,0)))  
	{  
		printf("socket() error\n");  
		exit(1);  
	}  

	bzero(&server,sizeof(server));  
	server.sin_family = AF_INET;  
	server.sin_port = htons(PORT);  
	server.sin_addr= *((struct in_addr *)he->h_addr);  
	sendto(isockfd, argv[2],strlen(argv[2]),0,(struct sockaddr *)&server,sizeof(server));  
	addrlen=sizeof(server);  
	while (1)  
	{  
		if((uinum=recvfrom(isockfd, buf, MAXDATASIZE, 0, (struct sockaddr *)&peer, &addrlen))== -1)  
		{  
			printf("recvfrom() error\n");  
			exit(1);  
		}  
		if (addrlen != sizeof(server) || memcmp((const void *)&server, (const void *)&peer, addrlen) != 0)  
		{  
			printf("Receive message from otherserver.\n");  
			continue;  
		}  

		buf[uinum]='\0';  
		printf("Server Message:%s\n", buf);  
		break;  
   }  

 close(isockfd);  
}  

演示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@localhost server]# ./udpserver &
 [1] 1389
[root@localhost server]# ./udpclient 127.0.0.1 "i like you"
 You got a message (i like you) from client.
 It's ip is127.0.0.1, port is 34340.
 Server Message:i like you
 [root@localhost server]# netstat -aun
 Active Internet connections (servers and established)
 Proto Recv-Q Send-Q Local Address      Foreign Address     State
 udp     0    0 0.0.0.0:68        0.0.0.0:*
 udp     0    0 0.0.0.0:111       0.0.0.0:*
 udp     0    0 0.0.0.0:631       0.0.0.0:*
 udp     0    0 0.0.0.0:123       0.0.0.0:*
 udp     0    0 192.168.1.255:137    0.0.0.0:*
 udp     0    0 192.168.1.105:137    0.0.0.0:*
 udp     0    0 0.0.0.0:137       0.0.0.0:*
 udp     0    0 192.168.1.255:138    0.0.0.0:*
 udp     0    0 192.168.1.105:138    0.0.0.0:*
 udp     0    0 0.0.0.0:138       0.0.0.0:*
 udp     0    0 0.0.0.0:17585      0.0.0.0:*
 udp     0    0 0.0.0.0:1234       0.0.0.0:*
 udp     0    0 0.0.0.0:816       0.0.0.0:*
 udp     0    0 0.0.0.0:323       0.0.0.0:*
 udp6    0    0 :::111          :::*
 udp6    0    0 :::123          :::*
 udp6    0    0 :::816          :::*
 udp6    0    0 :::323          :::*
 udp6    0    0 :::45117         :::*