目录
1. UDP简介
2. UDP通信流程
3、UDP的函数接口说明
4、UDP通讯测试代码
UDP全称 User Datagram Protocol,即:用户数据报协议。是面向无连接的协议。通常,UDP 通信还会被冠以不可靠的头衔。这里的不可靠指的是:无法可靠地得知对方是否收到数据。
UDP有如下特征:
简单来讲,UDP 类似于寄信,如果两个人除了信件之外没有任何别的通信方式,那么信件寄出去了之后,寄件人是无法得知收件人是否收到信件或者是否已经读取内容的。UDP 的特点是无需连接、无需确认、无需缓冲区和分包序列号,因此 UDP 的效率是比较高的。
UDP适用情况
广播、组播模式
发送方:
- 创建 UDP 套接字:
int fd = socket();
- 准备好接收方的地址:
struct sockaddr_in peerAddr;
- 给对方发送 UDP 数据报:
sendto(fd, peerAddr);
接收方:
- 创建 UDP 套接字:
int fd = socket();
- 准备好自己的地址:
struct sockaddr_in addr;
- 绑定套接字和地址:
bind(fd, addr);
- 坐等各方发来的 UDP 数据报:
recvfrom(fd);
1、建立套接字(socket)
#include
#include
int socket(int domain,int type,int protocol);
函数作用:建立套接字,返回套接字文件描述符
函数参数:domain:你要选择哪一种地址族
PF_INET/AF_INET IPV4网络协议 PF ---> Protocol Family
PF_INET6/AF_INET6 IPV6网络协议 AF ---> Address Family
type:你要选择哪一种协议
SOCK_STREAM选择TCP -- 流式套接字 --->Stream Sockets
SOCK_DGRAM选择UDP -- 数据报套接字 --->Datagram Sockets
protocol:传0表示使用默认协议
返回值:成功:套接字文件描述符sockfd
失败:-1
2、绑定主机的IP地址和端口号(bind)
#include
#include
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
函数作用:绑定主机的IP地址和端口号
参数:sockfd:套接字文件描述符
addr:自己的IP地址和端口号
addelen:地址的大小长度
返回:成功
#include
ssize_t sendto(int socket,const void* message,size_t length,const struct sockaddr* dest_addr,socklen_t dest_len);
函数作用:用于UDP中发送数据,注意是UDP
参数:socket:套接字文件描述符
message:你要发送的数据
length:你要发送的数据大小,注意有多少写多少strlen
flags:一般设置成0
dest_addr:对方的IP地址和端口号
dest_len:结构体的大小
返回值:成功:发送出去的字节
失败:-1
需要新创建一个接收的结构体存储数据
struct sockaddr_in recv_addr;
int len = sizeof(struct sockaddr_in);
#include
ssize_t recvform(int socket,void* buffer,size_t length,int flags,struct sockaddr* dest_addr,socklen_t* address_len);
函数作用:用于UDP中接收数据
参数:socket:套接字文件描述符
buffer:接受的数据存储在这里
length:接受的数据的大小,以最大的来接受(sizeof)
flags:一般设置成0
address:存储客户端的IP地址和端口号,可以获取到是谁给你发送的
address_len:结构体的大小
返回值:成功:接收到的字节数
失败:-1
#include
uint16_t htons(uint16_t hostshort);//将主机端口号转成网络端口号
uint16_t ntohs(uint16_t netshort);//将网络端口号转成主机端口号
说明:h代表主机(host) n代表网络(network) s代表端口号(short)
返回值:成功:要转换的字节序
失败:-1
从什么(h,n)端口号到(to)什么(n,h)端口号(s)
#include
#include
#include
in_addr_t inet_addr(const char *cp); //将主机IP转成网络IP
char* inet_ntoa(struct in_addr in); //将网络IP转成主机IP
主机转网络addr(address) 网络转主机ntoa(network to address)
//udp_client.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "192.168.5.184" //-->服务器IP
#define SERVER_PORT 60000
/*
UDP客户端代码实现步骤
1、建立套接字
2、填充结构体,绑定IP和地址(绑定可有可无)
3、发送数据(sendto)
4、关闭
*/
int main(int argc,char** argv)
{
//手动传参的错误提示条件
// if(argc != 3)
// {
// perror("./a.out IP PORT");
// return -1;
// }
int ret = 0;
char buf[1024] = { 0 };
//1、建立套接字文件描述符
//参数: 地址族 流式套接字 默认协议
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket fail");
return -1;
}
//2、填充服务端的结构体,绑定IP和地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//server_addr.sin_port = htons(atoi(argv[2])); //传参方式
server_addr.sin_port = htons(SERVER_PORT); //宏定义方式
//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式
printf("连接服务器成功[%s][%d]\n",SERVER_IP,SERVER_PORT);
//printf("连接服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));
//3、发送数据
while(1)
{
//缓存区清零
bzero(buf,sizeof(buf));
//发送数据,计算返回值(buf真实数据大小)
scanf("%s",buf);
//参数 套接字文件描述符 缓存区 缓存区真实大小 默认为0 (旧结构体指针强转取地址)新结构体的大小
ret = sendto(socketfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
printf("发送数据 ret:%d\n",ret);
//做主动退出的判断条件
if(!strcmp(buf,"exit"))
break;
}
//4、关闭套接字
close(socketfd);
return 0;
}
//udp_server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "192.168.5.184" //-->虚拟机或者主机IP
#define SERVER_PORT 60000
/*
UDP服务端(接收端)代码实现步骤
1、建立套接字
2、填充结构体,绑定IP和地址
3、定义接收端的一个结构体用来存放,接收数据(recvfrom)
4、关闭
*/
int main(int argc,char** argv)
{
//手动传参的错误提示条件
// if(argc != 3)
// {
// perror("./a.out IP PORT");
// return -1;
// }
int ret = 0;
char buf[1024] = { 0 };
//1、建立套接字文件描述符
//参数: 地址族 数据包套接字 默认协议
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(socketfd == -1)
{
perror("socket fail");
return -1;
}
//设置端口复用
int optval = 1;
//参数 套接字文件描述符 网络层 端口复用 设置值 设置值的大小
setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
//2、填充结构体,绑定IP和地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
//server_addr.sin_port = htons(atoi(argv[2])); //传参方式
server_addr.sin_port = htons(SERVER_PORT); //宏定义方式
//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式
//参数 套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小
ret = bind(socketfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror("bind fail");
return -1;
}
printf("绑定服务器IP:%s 端口号PORT:%hu\n",SERVER_IP,SERVER_PORT);
//printf("绑定服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));
//定义一个接收端的地址,用来存放发送端的地址,另一端的端口号确定
struct sockaddr_in recv_addr;
int address_len = sizeof(struct sockaddr_in);
//3、接收数据
while(1)
{
//缓存区清零
bzero(buf,sizeof(buf));
//接收数据,计算返回值(buf真实数据大小)
//参数 套接字文件描述符 缓存区 缓存区大小 默认值 旧结构体指针强转取地址)长度取址
ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr*)&recv_addr,&address_len);
//解析接收到的地址和端口(难点)
char *ip= inet_ntoa(recv_addr.sin_addr) ;//将网络字节序转换成本机字节序
int port = ntohs(recv_addr.sin_port);//将网络端口转换为本机端口
printf("[%s][%d]收到数据 buf:%s ret:%d\n",ip,port,buf,ret);
//做主动退出的判断条件
if(!strcmp(buf,"exit"))
break;
}
//4、关闭套接字
close(socketfd);
return 0;
}