目录
一、UDP简介
UDP全称"用户数据报协议"(User Datagram Protocol),即报文。是面向无连接的协议。是不可靠协议,不可靠指的是无法可靠的得知对方是否收到数据。
UDP的特征:
- 无连接:通信双方不需要事先连接
- 无确认:收到数据不给对方发回执确认
- 不保证有序、丢失不重发
- 采用帧同步的数据报通信方式(即通信双方每次的收发数据量相等)
UDP类似于寄信,UDP的特点是无需连接、无需确认、无需缓冲区、无需分包序列号。因此UDP的效率比较高。
UDP的使用场景:广播、组播模式
二、UDP协议的通信流程
1、发送方(客户端)
(1)创建UDP套接字 int fd=socket();
(2)准备好接收方的地址 struct sockaddr_in peerAddr;
(3)给对方发送UDP数据报 sendto(fd,peerAddr)
(4)接收对方得消息 recvfrom(fd,peerAddr)
2、接收方(服务器)
(1)创建UDP套接字 int fd=socket();
(2)准备好自己的地址 struct sockaddr_in addr;
(3)绑定套接字地址 bind(fd,addr);
(4)坐等各方发来UDP数据报 recvfrom(fd,buf,buf_len,addr,addr_len); (
5)发送消息 sendto(fd,buf,buf_len,addr,addr_len);
UDP协议需要先接收recvfrom之后才能发送sendto。是没有面向连接的协议。 UDP很少用于C/S(服务器/客户端)模型,显得服务器很被动,除非模拟TCP面向连接的流程。
三、UDP相关API接口
(1)创建套接字-socket()
cpp
1、创建套接字,申请一个应用于网络通信的接口
//头文件
#include <sys/types.h> #include <sys/socket.h>
//函数原型
int socket(int domain,int type,int protocol);
//参数
@domain:选择需要的协议簇(网络协议) --域 AF_UNIX,AF_LOCAL 本地通信协议(UNIX本地域AF_LOCAL) AF_INET IPv4 AF_INET6 IPv6
@type:协议类型 SOCK_STREAM 流式套接字TCP SOCK_DGRAM 数据报套接字UDP @protocol:传输层协议的支持,如UDP协议IPPROTO_UDP、TCP协议IPPROTO_TCP等, 只要type正确即可,默认为0
//返回值
成功 返回大于0的套接字文件描述符
失败 返回-1
//备注
- 常用的网络协议
- AF_UNIX,AF_LOCAL:本地通信协议
- AF_INET:IPv4地址协议
- AF_INET6 :IPv6地址协议
- 常用的协议类型
- (1)流式套接字(TCP)------------SOCK_STREAM
- (2)数据报套接字(UDP)------------ SOCK_DGRAM
- 协议的支持 一般情况下某一个type会有一个特定的协议来支持它
- 因此一般情况下只要确保type是正确的,该参数可以默认为0
(2)地址信息结构体sockaddr_in{}
cpp
struct sockaddr_in{
sa_family_t sin_family; //选定地址协议:AF_INET
in_port_t sin_port; //端口号:明确哪一个进程处理该数据
struct in_addr sin_addr; //地址结构体internet address
};
/*Internet address*/
struct in_addr{
uint32_t s_addr;
//32位整型的地址信息
}
(3)地址转换接口
cpp
如何把点分十进制的字符串转换为32位的无符号整型 "192.168.172.135"---->uint2_t
//头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//函数原型
in_addr_t inet_addr(const cahr *cp);
//把字符串ip转换为in_addr_t
#include <arpa/inet.h>
(1)主机地址转换为网络地址:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint32_t hostshort);
(2)网络地址转换为主机地址:
uint32_t ntonl(uint32_t netlong);
uint16_t ntons(uint32_t netshort);
(4)发送消息sendto()
对于UDP而言,由于没有连接,因此每次发送数据都必须携带对端的地址,就像写信,不管信封里面的内容长短,也不管是第几次寄信,每次寄信都必须写清楚对方的地址方可寄出。
cpp
//头文件
#include <sys/types.h>
#include <sys/socket.h>
//函数原型
ssize_t sendto(int sockfd,const void *buf,size_t len,int flags, const struct sockaddr *dest_addr,socklen_t addrlen);
//参数
@sockfd:通信套接字
@buf:需要发送的消息的首地址
@len:具体要发送的消息的长度(字节)
@flags:发送时的特殊标记,比如带外数据MSG_OOB,一般设置为0即可,
@dest_addr:目标地址,包括IP地址和端口号
@addrlen:目标地址长度
//返回值
成功 返回已发送的数据的字节数
失败 返回-1
(5)绑定地址bind()
对于服务端来说,由于其IP和端口必须固定(否则客户端无法找到),因此服务端一般都需要将套接字绑定到某个IP和端口上,IP和端口一般统称网络地址或地址。当然,有些特定的场合客户端的套接字也可以绑定地址。
cpp
//头文件
#include <sys/types.h>
#include <sys/socket.h>
//函数原型
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//参数
@sockfd:套接字文件描述符
@addr:具体的地址信息(IP地址于端口号、需要转换成标准地址结构体)
@addrlen:地址信息的长度
//返回值
成功 返回0
失败 返回-1
(6)接收消息recvfrom()
cpp
ssize_t recvfrom(
int sockfd, //套接字文件描述符
void *buf, //接收数据缓冲区
size_t len, //接收数据缓冲区大小
int flags, //接收标记,比如MSG_OOB,一般设置为0
struct sockaddr *src_addr, //源端地址,不保存源端地址时可设置为NULL
socklen_t *addrlen //源端地址长度,不保存源端地址时可以设置为NULL
);
//参数
@sockfd:套接字
@buf:接收到的消息的用户缓冲区
@len:缓冲区的最大长度,避免越界
@flags:特殊选项,一般设置为0
@src_addr:消息来自哪里(用于存储对方的地址结构体)
@addrlen:明确上一个参数中的地址尺寸以及用于记录实际收到的对方地址尺寸
//返回值
成功 返回实际收到的字节数
失败 返回-1
服务器只能让其他人知道自己的网络信息,但是服务器不知道其他人的网络信息 所以需要自己先调用recvfrom()去接收别人的消息才能保存其他人的网络信息
四、UDP单向通信案例
(1)发送方
cpp
/*发送端(客户端)*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h> /* superset of previous */
int main(int argc, char const *argv[])
{
// 1. 创建一个udp套接字
int socketfd = socket(AF_INET, // 选择使用IPV4地址协议
SOCK_DGRAM, // 选择使用UDP 数据报套接字
0); // 传输协议0 表示自动根据SOCK_DGRAM 匹配
if (socketfd < 0)
{
perror("socket error");
exit(0);
}
// 2. 设置好接收方的地址信息
struct sockaddr_in toAddr = {
.sin_family = AF_INET, // 设置为IPV4地址协议
.sin_port = htons(60000), // 使用htons把本地字节序的端口号6000转换成网络字节序
.sin_addr.s_addr = inet_addr("192.168.172.122") // 把点分十进制的字符串IP地址转换为32位的网络地址
};
char SendMsg[128] = {0};
while (1)
{
// 清空发送消息缓冲区
bzero(SendMsg, sizeof(SendMsg));
// 3. 从键盘中获取消息
fgets(SendMsg, 128, stdin);
// 4. 发送消息
ssize_t ret_val = sendto(socketfd, // 通信套接字
SendMsg, // 需要发送的消息内容地址
strlen(SendMsg), // 具体发送的消息长度
0, // 特殊选项 0 表示默认
(struct sockaddr *)&toAddr, // 目标地址(接收者地址)
sizeof(toAddr)); // 地址大小
}
return 0;
}
(2)接收方
cpp
/*接收端(服务器)*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h> /* superset of previous */
int main(int argc, char const *argv[])
{
// 1. 创建一个udp套接字
int socketfd = socket(AF_INET, // 选择使用IPV4地址协议
SOCK_DGRAM, // 选择使用UDP 数据报套接字
0); // 传输协议0 表示自动根据SOCK_DGRAM 匹配
if (socketfd < 0)
{
perror("socket error");
exit(0);
}
// 2. 设置好自己的地址信息
struct sockaddr_in MyAddr = {
.sin_family = AF_INET, // 设置为IPV4地址协议
.sin_port = htons(60000), // 使用htons把本地字节序的端口号6000转换成网络字节序
.sin_addr.s_addr = inet_addr("192.168.172.135") // 把点分十进制的字符串IP地址转换为32位的网络地址
};
// 3. 绑定地址
if (bind(socketfd, (struct sockaddr *)&MyAddr, sizeof(MyAddr)))
{
perror("bind error");
exit(0);
}
char RecvMsg[128] = {0};
while (1)
{
bzero(RecvMsg, sizeof(RecvMsg));
// 接收消息
ssize_t ret_val = recvfrom(socketfd, RecvMsg, sizeof(RecvMsg), 0,
NULL, NULL);
if (ret_val > 0)
{
printf("成功收到%ld字节数据:%s\n", ret_val, RecvMsg);
}
}
return 0;
}