一、初识UDP
1.UDP 协议
- UDP 是
无连接的
,即发送数据之前不需要建立连接(发送数据结束时也没有连接可释放),减少了开销和发送数据之前的时延 - UDP 使用
尽最大努力交付
,即不保证可靠交付,主机不需要维持复杂的连接状态表(如发短信,你无法确定对方是否收到) - UDP 是
面向报文
的,发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界
- UDP
没有拥塞控制
,网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的 - UDP 支持一对一、一对多、多对一和多对多的交互通信
- UDP 的
首部开销小
,只有8个字节,比 TCP 的20个字节的首部要短
2.UDP头
源端口:在需要对方回信时1,不需要时可用全0
目标端口:这在终点交付报文时必须使用
长度:UDP 用户数据报的长度,其最小值是8(仅有首部)
校验和: 检测 UDP 用户数据报在传输中是否有错。有错就丢弃
3.UDP通信过程
二、UDP通信——scoket编程
UDP网络通信(类似发送/接收短信):
类比 | 作用 | 功能 | 函数 |
有手机 | 有网络通信协议UDP | 创建套接字文件 | socket() |
有手机卡(号码) | 通信协议中需要有自己的网络信息 | 绑定(指定)本地当前进程的网络信息 | bind() |
填写对方手机号,发送短信 | 使用协议发送数据 | 协议中添加对方信息发送数据(ip端口) | sendto() |
接收短信 | 使用协议接收数据 | 根据解析的信息接收数据 | recvfrom() |
关闭 | 不再进行通信 | 关闭通信 | close() |
1. 创建套接字
#include <sys/types.h>
#include <sys/socket.h>
//创建套接字文件,为进程添加一个对应的网络通信协议(文件),返回值就是创建号的文件描述符(socket描述符就代表一套协议---套接字)
int socket(int domain, int type, int protocol);
参数1:
int domain:地址族,选用那种网络层协议地址
AF_INET------IPV4
AF_INET6------IPV6
参数2:
int type:套接字类型
SOCK_STREAM--------TCP
SOCK_DGRAM---------UDP
SOCK_RAW:原始套接字(没有传输层协议)
参数3:
int protocol:套接字协议
0:套接字默认协议
返回值:
int:整数---------文件描述符(套接字文件)
成功:返回套接字描述符 >= 0
失败:返回-1
2.绑定套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//绑定本地网络信息到套接字中
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数1:
int sockfd:绑定本地网络信息到哪个套接字上(绑定到哪一套协议上)
参数2:
const struct sockaddr *addr:结构体地址,这个结构体中存储的本地网络信息(要绑定的网络信息ip、port)
struct sockaddr {//通用结构体,表示一个网络信息内容
sa_family_t sa_family;
char sa_data[14];
}
//IPV4 网络信息结构体
struct sockaddr_in {
sa_family_t sin_family;//地址族 AF_INET
in_port_t sin_port;//端口
struct in_addr sin_addr;//结构体变量--ip地址
};
/* Internet address. */
struct in_addr {
uint32_t s_addr;//ipv4地址
};
参数3:
socklen_t addrlen:整数,结构体大小(确定信息结构体的大小)
返回值:
成功:返回0
失败:返回-1
3.发送数据
#include <sys/types.h>
#include <sys/socket.h>
//使用UDP协议发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数1:
int sockfd:套接字对象,使用哪个套接字发送数据
参数2:
const void *buf:要发送的数据的内存首地址
参数3:
size_t len:发送的数据的大小
参数4:
int flags:标志
0:阻塞发送
参数5:
const struct sockaddr *dest_addr:发送的目标网络信息
参数6:
socklen_t addrlen:结构体大小
返回值:
成功:返回发送的字节数
失败:返回-1
4.接收数据
#include <sys/types.h>
#include <sys/socket.h>
//使用UDP协议接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数1:
int sockfd:套接字,指定使用哪个套接字来接收(ip,port来进行检测)
参数2:
void *buf:接收的数据的存放的地址
参数3:
size_t len:指定能够接收存储的最大大小
参数4:
int flags:标志
0:阻塞接收
参数5:
struct sockaddr *src_addr:在接收数据时,存储发送方的ip和port,如果不需要写NULL
参数6:
socklen_t *addrlen:存储信息结构体的大小,不需要写NULL
返回值:
成功:返回收到的大小
失败:返回-1
5.关闭套接字
int close(int fd)
三、实现代码:
//通过UDP协议,发送数据,接收数据
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
//1、创建套接字,选择对应的网络通信协议组
int socketfd = socket(AF_INET,SOCK_DGRAM,0);//当前进程使用UDP IP 协议
/* if(socketfd < 0)
{
return -1;
}
*/
//2、绑定套接字,对套接字添加自己当前进行需要到本地网络信息
struct sockaddr_in addr;//有一个IPV4网络信息结构体
addr.sin_family = AF_INET;//地址族-----IPV4
addr.sin_port = htons(10000);//为当前进程添加的端口号为10000-------主机10000端字节序转换为网络字节序
addr.sin_addr.s_addr = inet_addr("192.168.124.63");
bind(socketfd,(struct sockaddr *)&addr,sizeof(addr));
char buf[20];
memset(buf,0,20);//清空buf
struct sockaddr_in dest_addr;//接收方网络信息结构体
dest_addr.sin_family = AF_INET;//地址族-----IPV4
dest_addr.sin_port = htons(2000);
dest_addr.sin_addr.s_addr = inet_addr("192.168.124.27");
while(1)
{
fgets(buf,20,stdin);
sendto(socketfd,buf,20,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));//发送
memset(buf,0,20);//清空buf
int num=recvfrom(socketfd,buf,20,0,NULL,NULL);
printf("recv size is %d\n",num);
printf("data is %s\n",buf);
}
return 0;
}
通信结果如下图所示: