UDP协议
UDP协议
在网络传输层中,UDP和TCP协议是最为重要的。接下来先介绍UDP协议
-
UDP
(User Datagram Protocol)协议(用户数据报协议):不连接、不可靠、不能保证数据包的交付(不会管对方有没有接收到)。 -
报首的结构
注意一个UDP数据最小要有8个字节的大小也就是没有数据

- 端口
端口是用来区分一台主机上的程序的,每一个程序都有端口,比如ssh使用的是22端口,http使用的是80端口,使用数据传输必须要知道他们的端口号,传输到哪一个程序要说清楚。
端口一共两个字节,一共只有65535个端口,其中前面的1024个端口都被很多大公司使用,所以一选用端口一般都需要向后面选用。
发送机的端口不一定要和接收机的端口相同。
- 校验和
校验和一般不使用。
- 数据长度
数据长度最小是8个字节,在没有数据的情况下。一般而言使用不同的网络会有不同的传输最大值MTU(Maximum transmission Unit),最大传输单元。一般使用以太网最大传输单元是1500个字节,如果你不使用以太网,UDP可以传输65536个字节,但是由于他这个数据包需要在每一层加上标签,IP的数据头需要20个字节,所以总共可以发送65507个字节
-
UDP的接口
- 创建套接字文件
一般网络传输是使用套接字文件,所以UDP套接字也需要创建一个套接字文件
- 创建套接字文件
int socket(int domain, int type, int protocol);
//第一个参数是域,使用这个宏AF_INET就是IPV4的
//第二个参数是类型UDP选择SOCK_DGRAM,TCP选择SOCK_STREAM
//第三个参数选择0,系统会自动帮你匹配所用的协议
//返回值,成功返回套接字文件的描述符,失败返回-1
- 绑定IP和端口
只有需要接受数据的时候才需要绑定端口和ip如果只需要发送数据那么不需要绑定端口
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//第一个参数是套接字文件的描述符
//第二个参数是一个结构体类型的指针,需要的是他需要这个结构体但是一般都使用下面这个结构体然后强转为这个结构体类型,需要自己填
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
//上面这个结构体第一个成员是地址家族,需要填写的值是AF_INET
//第二个成员是端口字节序,绑定自己程序的端口,以便于对方传输数据查找到这个程序
//第三个参数是结构体,这个结构体只有一个成员,也就是在下面,下面这个结构体也是需要一个地址字节序,绑定自己主机的成员,以便于自己的端口绑定自己IP。
/* Internet address. */
struct in_addr {
uint32_t s_addr;/* address in network byte order */
};
//第三个参数是这个结构体的长度
//返回值,成功返回0,失败返回-1
- 发送函数
注意:如果只发送,不接受不需要绑定
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
//第一个参数是套接字文件描述符
//第二个参数是发送的缓冲区(注意不要超过1500个字节)
//第三个参数是发送字节的长度
//第四个参数是标志位,一般都填0
//第五个参数是一个结构体类型的指针,需要的是他需要这个结构体但是一般都使用下面这个结构体然后强转为这个结构体类型,需要自己填
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
//上面这个结构体第一个成员是地址家族,需要填写的值是AF_INET
//第二个成员是端口字节序,绑定自己程序的端口(自己定),以便于对方传输数据查找到这个程序
//第三个参数是结构体,这个结构体只有一个成员,也就是在下面,下面这个结构体也是需要一个地址字节序,绑定自己主机的成员,以便于自己的端口绑定自己IP。
/* Internet address. */
struct in_addr {
uint32_t s_addr;/* address in network byte order */
};
//第六个参数是这个结构体的长度
//返回值是发送字节的个数,如果是失败返回-1
- 接收函数(如果接受不到信息会阻塞)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
//第一个参数是套接字文件描述符
//第二个参数是接收的缓冲区
//第三个参数是接收字节的长度
//第四个参数是标志位,一般都填0
//第五个参数是一个结构体类型的指针,需要的是他需要这个结构体但是一般都使用下面这个结构体然后强转为这个结构体类型,不需要自己填,可以获取是谁发送的
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
//上面这个结构体第一个成员是地址家族,需要填写的值是AF_INET
//第二个成员是端口字节序,绑定自己程序的端口,以便于对方传输数据查找到这个程序
//第三个参数是结构体,这个结构体只有一个成员,也就是在下面,下面这个结构体也是需要一个地址字节序,绑定自己主机的成员,以便于自己的端口绑定自己IP。
/* Internet address. */
struct in_addr {
uint32_t s_addr;/* address in network byte order */
};
//第六个参数是这个结构体的长度
//返回值是接收字节的个数,如果是失败返回-1
- 需要注意的是由于网络协议基本都是大端存储,我们使用的平台(x86)一般是小端,arm一般是大端,无论是大端还是小端都转换成大端
//将任意端转换成大端函数
h:host
to
n:net
s:short
l:long
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
//将字符串,转换为十进制数字
int atoi(const char *nptr);
//将点分十进制的字符串转换为,地址字节序
int inet_aton(const char *cp, struct in_addr *inp);
//将点分十进制的字符串转换为,地址字节序
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
- Example(基本用法)
//程序一,客户端
int main()
{
//创建套接字文件
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
//直接可以发送信息
char buf[128] = "hello";
struct sockaddr_in dest_addr;
dest_addr.sa_family_t = AF_INET;//地址家族
dest_addr.sin_port = htons(atoi("60000")); //端口字节序,接收机的程序端口,需要注意的是,
dest_addr.sin_addr.s_addr = inet_addr("192.168.136.128");
ssize_t sendto(udp_socket, buf,strlen(buf), 0, (struct sockaddr *)dest_addr, sizeof(dest_addr));
}
//程序二,服务器
int main()
{
//创建套接字文件
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
//需要绑定信息
struct sockaddr_in addr;
addr.sa_family_t = AF_INET;//地址家族
addr.sin_port = htons(atoi("60000")); //端口字节序,接收机的程序端口,需要注意的是,
addr.sin_addr.s_addr = inet_addr("192.168.136.128"); //地址字节序,如果填写这个INADDR_ANY,那么无论谁发送的信息都可以接受到,注意这个已经是地址字节序
bind(udp_socket, (struct sockaddr *)addr,strlen(addr));
//直接可以接受信息
char buf[128] = {0};
struct sockaddr_in dest_addr;
//最后两个参数可以查看是谁发送的信息,如果不想知道是谁发送的信息,那么可以填NULL
recvfrom(udp_socket, buf, sizeof(buf), 0,(struct sockaddr *)dest_addr,strlen(dest_addr));
}
-
组播(多播)和广播
-
- 使用UDP可以使用广播或者组播(不连接),也就是可以发送给所有的用户信息,或者可以给同组的用户发送信息
- 函数接口,下面的可以设置套接字文件的属性
//获得套接字文件的属性 int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen); //第一个参数是套接字文件描述符 //第二个参数是等级广播(SOL_SOCKET).组播(IPPROTO_IP) //第三个参数是广播(SO_BROADCAST).组播(IP_ADD_MEMBERSHIP) //第四个参数是广播打开是1(整形),关闭是0. //组播是一个结构体(需要入组才可以接受到),组播的地址是D类的,随意可选,如下 struct ip_mreqn { struct in_addr imr_multiaddr; /* IP multicast group address */ //组播地址 struct in_addr imr_address; /* IP address of local interface */ //入组地址 int imr_ifindex; /* interface index */ //一般为0 }; //第五个参数是,第四个参数的字节大小 //返回值成功返回0,失败返回-1 //设置套接字文件的属性 int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen); //第一个参数是套接字文件描述符 //第二个参数是等级广播(SOL_SOCKET). //第三个参数是广播(SO_BROADCAST). //第四个参数是广播打开是1(整形),关闭是0. //组播是一个结构体(需要入组才可以接受到),组播的地址是D类的,随意可选,如下 struct ip_mreqn { struct in_addr imr_multiaddr; /* IP multicast group address */ //组播地址 struct in_addr imr_address; /* IP address of local interface */ //入组地址 int imr_ifindex; /* interface index */ //一般为0 }; //第五个参数是,第四个参数的字节大小 //返回值成功返回0,失败返回-1 -
Example(组播)
// 多播端口号
#define MULTI_PORT "17575"
// 多播地址
#define MULTI_ADDRESS "224.168.64.242"
// 本地地址
#define LOCAL_ADDRESS "192.168.64.242"
int main(int argc, char *argv[])
{
// 1.创建UDP套接字
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1)
{
fprintf(stderr, "udp socket error,errno:%d,%s\n", errno, strerror(errno));
exit(1);
}
// 打开多播模式
struct ip_mreqn ip_mreqn1;
socklen_t optlen = sizeof(ip_mreqn1);
ip_mreqn1.imr_address.s_addr = inet_addr(LOCAL_ADDRESS);
ip_mreqn1.imr_ifindex = 0;
ip_mreqn1.imr_multiaddr.s_addr = inet_addr(MULTI_ADDRESS);
setsockopt(udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&ip_mreqn1, optlen);
// 2.如果自己也想收信息那么需要绑定端口号
struct sockaddr_in host_addr;
host_addr.sin_family = AF_INET; // 协议族,是固定的
host_addr.sin_port = htons(atoi(MULTI_PORT)); // 目标端口,必须转换为网络字节序
host_addr.sin_addr.s_addr = INADDR_ANY; // 目标地址 "192.168.64.xxx" 已经转换为网络字节序 INADDR_ANY(任意的地址都可以一个主机发消息,端口号相同,已经入组也可以写自己的端口号)
bind(udp_socket, (struct sockaddr *)&host_addr, sizeof(host_addr));
char buf[256] = "hello";
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(atoi(MULTI_PORT)); // 服务器端口,必须转换为网络字节序
dest_addr.sin_addr.s_addr = inet_addr(MULTI_ADDRESS); // 服务器地址 "192.168.64.xxx"
while (1)
{
// 3.发送到组播
sendto(udp_socket, buf, strlen(buf), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
sleep(2);
}
return 0;
}
- Example(广播)
int main(int argc, char const *argv[])
{
//1.创建UDP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1)
{
fprintf(stderr, "udp socket error,errno:%d,%s\n",errno,strerror(errno));
exit(1);
}
//3.设置UDP套接字的广播属性
int optval = 1;
setsockopt(udp_socket,SOL_SOCKET,SO_BROADCAST,&optval,sizeof(optval));
//3.利用循环每隔5s想广播地址发送数据包
char buf[128] = "I am teacher,this is test packet";
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET; //协议族,是固定的
dest_addr.sin_port = htons(atoi(argv[1])); //服务器端口,必须转换为网络字节序,想要接受的主机端口号必须一样
dest_addr.sin_addr.s_addr = inet_addr(argv[2]); //服务器地址 "192.168.64.255",这个网段下面全是1(255),那就是全部发送 ,一定要设置广播模式之后,才可以接受到
while(1)
{
sendto(udp_socket,buf,strlen(buf),0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
sleep(5);
}
return 0;
}

浙公网安备 33010602011771号