一、UDP概述
1、特点
面向无连接的用户数据报协议 ----> 在传输数据前不需要建立连接!!!传输后也不需要确认!!!
UDP 是无连接、不可靠的传输层协议!!!
支持 广播和多播(组播) ----> 支持0字节报文!!
特点:
1、相对TCP速度稍 快!
2、简单的请求/应答 可以使用UDP
3、对于海量数据的传输不应该使用 UDP
4、广播/多播 必须使用UDP
2、UDP的编程框架
3、网络字节序
大端: 低字节序 存在 高地址
小端: 低字节序 存在 低地址
LSB: 低位有效 在一个字节(8位)中,LSB 是第 0 位。
MSB: 高位有效 在一个字节(8位)中,LSB 是第 7 位。
htonl() 32位
ntohl()
htons() 16位
ntohs()
主机是 小端 ----> 以上功能 可以将数据转为大端(换序)
主机是 大端 ----> 以上不能不做任何操作
htonl 和 ntohl 功能没有区别 ----> 名字不一样增加可读性
本质是宏
10.7.164.45 ---> 都是字符串!!!
IPV4 是32位无符号整型!!!
int inet_pton(int family,const char *strptr,void * addr);
family: AF_INET AF_INET6
strptr: 需要转换的ip 字符串首地址
addr: 转换后的IP整型 存储在这个地址指向的空间中!
返回值: 成功 1 失败 其他
const char *inet_ntop(int family,const void *addr,char *strptr,size_t len);
family: AF_INET AF_INET6
addr: 需转换的IP地址--->整型
strptr: 转换后的ip 字符串首地址
len: strptr缓冲区长度!!!
返回值: 成功就是转换后的Ip字符串首地址
二、UDP编程
1、socket套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int family,int type,int protocol)
功能:
创建一个用于网络通信的socket套接字(描述符)
参数:
family:协议族(AF_INET、AF_INET6、PF_PACKET 等)
协议簇 地址族
AF_INET // internet 协议
AF_UNIX // unix internal协议
type:套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW 等)
1、流式套接字(SOCK_STREAM) -----> TCP
提供一个面向连接、可靠的数据传输服务,
数据无差错、无重复的发送且按发送顺序接收。
内设置流量控制,避免数据流淹没慢的接收方。
数据被看作是字节流,无长度限制。
2、数据报套接字(SOCK_DGRAM) -----> UDP
提供无连接服务。数据包以独立数据包的形式
被发送,不提供无差错保证,数据可能丢失或重复,
顺序发送,可能乱序接收。
3、原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP 等
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。
通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;
但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
参数通常置为0
2、地址类型
// 通用地址结构体 ----> 地址 主要IP地址 IPV4 IPV6
struct sockaddr
{
unsigned short sa_family; // 地址族 2字节 AF_INET ipv4 AFINET6 ipv6 AF_LOCAL unix AF_LINK 链路地址
char buf[14]; // 14字节 ---> 装的 ip地址和port
}
IPV4的结构体
struct sockaddr_in
{
short sa_family; // 地址族 2字节 AF_INET ipv4 AFINET6 ipv6 AF_LOCAL unix AF_LINK 链路地址
unsigned short sin_port; //2 字节 端口 ---> 转为网络字节序
struct in_addr sin_addr; // 4个字节 ---> IP地址
unsigned char sin_zero[8]; //8个字节 填充0 保持和 struct sockaddr 同样大小!!!
}
struct in_addr
{
in_addr_t s_addr; // ipv4 的地址结构
}
// 注意
ip 和端口,要转换为 网络字节序(大小端转换)
// 用于端口转换 --->字符串转整型 ---> 不考虑大小端
char *p = "8000";
unsigned short num = 0;
sscanf(p, "%hu", &num);
int num1 = atoi(p);
printf("%d\n", num1);
atoi函数是将字符串转换成整数。
int atoi (const char * str);
该函数的返回值为int类型的整数,转换后的值不可超出int可表示的范围。
htons
IP地址转换 ---> 转为网络字节序
inet_pton()
inet_aton()
将strptr所指的字符串转换成32位的网络字节序二进制值
#include <arpa/inet.h>
int inet_aton(const char *strptr,struct in_addr *addrptr);
inet_addr() /用这个函数进行IP转换/ ----> 被抛弃
功能同上,返回转换后的地址。
in_addr_t inet_addr(const char *strptr);
inet_ntoa()
将32位网络字节序二进制地址转换成点分十进制的字符串。
char *inet_ntoa(stuct in_addr inaddr);
配置地址信息
struct sockaddr_in dest_addr = {0}; // memset(&dest_addr,0,sizeof(dest_addr)); bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8000); // dest_addr.sin_port = htons(atoi("8000"));
inet_pton(AF_INET,"10.7.164.44",&dest_addr.sin_addr.s_addr);
//inet_aton("10.7.164.44",&dest_addr.sin_addr);
///dest_addr.sin_addr.s_addr = inet_addr("10.7.164.44"); ---- 被抛弃
不支持 IPv6: inet_addr 仅支持 IPv4 地址,对于 IPv6 地址无法处理。
无法检测错误: 函数返回的结果不能指示是否有错误发生,因此在实际使用中,我们很难判断是否解析成功。
如果获取自己主机IP
my_addr.sin_addr.s_addr = INADDR_ANY;
//my_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
// INADDR_ANY就是inet_addr("0.0.0.0")
//INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
//一般来说,在各个系统中均定义成为0值。
3、sendto发送
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
功能:
向to结构体指针中指定的ip,发送UDP数据
参数:
sockfd:套接字
buf:发送数据缓冲区
nbytes: 发送数据缓冲区的大小
flags:一般为0
dest_addr:指向目的主机地址结构体的指针
addrlen:dest_addr所指向内容的长度
注意:
通过to和addrlen确定目的地址
可以发送0长度的UDP数据包
返回值:
成功:发送数据的字符数
失败: -1
// 发送 ---> 需要确认发送的目标地址
//定义目标地址 变量 并赋值
struct sockaddr_in des_addr = {0};
des_addr.sin_family = AF_INET; // ipv4
des_addr.sin_port = htons(8000); // 端口 转为网络字节序
inet_pton(AF_INET,"10.7.164.45",&des_addr.sin_addr.s_addr); // IPV4
// 发送了个hello 长度为6 目标des_addr---> 强转为通用地址格式 目标地址大小 sizeof(des_addr)
sendto(sockfd,"hello",6,0,(struct sockaddr *)&des_addr,sizeof(des_addr))
demo udp发送数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
// 创建socket 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error:");
return -1;
}
// 发送数据
char buf[128] = {0};
fgets(buf, 128, stdin);
// 发送前 配置目标地址
struct sockaddr_in des_addr = {0};
des_addr.sin_family = AF_INET;
des_addr.sin_port = htons(8000);
inet_pton(AF_INET, "10.7.164.45", &des_addr.sin_addr.s_addr);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));
close(sockfd);
return 0;
}
4、接收recvfrom
ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:
接收UDP数据,并将源地址信息保存在from指向的结构中
参数:
sockfd: 套接字
buf: 接收数据缓冲区
nbytes: 接收数据缓冲区的大小
flags: 套接字标志(常为0)
src_addr: 源地址结构体指针,用来保存数据的来源
addrlen: src_addr所指内容的长度
注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
返回值:
成功:接收到的字符数
失败: -1
// 接收 ---> 可以创建地址变量接受 收到的数据 来自于谁
//定义 接收的是谁的消息,获取对方地址 变量
struct sockaddr_in rcv_addr = {0};
// 发送了个hello 长度为6 目标des_addr---> 强转为通用地址格式 目标地址大小 sizeof(des_addr)
char rcvbuf[128] = {0};
int len = recvfrom(sockfd,rcvbuf,128,0,(struct sockaddr *)&rcv_addr,sizeof(rcv_addr));
if(len >= 0)
{
//消息
printf("%s\n",rcvbuf);
//对方地址
char rcvip[16]="";
inet_ntop(AF_INET,&rcv_add.sin_addr.s_addr,rcvip,16);
unsigned short rcvport=ntohs(rcv_add.sin_port);
printf("rcv: %s: %u\n",rcvip,rcvport);
}
demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
// 创建socket 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error:");
return -1;
}
// 发送数据
char buf[128] = {0};
fgets(buf, 128, stdin);
// 发送前 配置目标地址
struct sockaddr_in des_addr = {0};
des_addr.sin_family = AF_INET;
des_addr.sin_port = htons(8000);
inet_pton(AF_INET, "10.7.164.45", &des_addr.sin_addr.s_addr);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));
// 想要获取发送对象的地址 就需要定义一个变量接收
struct sockaddr_in rcv_addr = {0};
// 阻塞
char rcv_buf[128] = {0};
// 接受对方地址 的长度 不能为 0!!
socklen_t rcv_addr_len = sizeof(rcv_addr);
int len = recvfrom(sockfd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
printf("len = %d\n", len);
printf("rcv_buf: %s\n", rcv_buf);
printf("rcv_port:%d\n", ntohs(rcv_addr.sin_port));
char rcv_ip[16] = {0};
inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
printf("rcv_ip:%s\n", rcv_ip);
close(sockfd);
return 0;
}
5、bind函数:绑定
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:
将本地协议地址与sockfd绑定
参数:
sockfd: socket套接字
myaddr: 指向特定协议的地址结构指针
addrlen:该地址结构的长度
返回值:
成功:返回0
失败:其他
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
// 创建socket 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error:");
return -1;
}
// 给当前网络进程 绑定固定的地址 ---> 当前程序自己的网络地址
struct sockaddr_in my_addr = {0};
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(9000);
my_addr.sin_addr.s_addr = INADDR_ANY;
// inet_pton(AF_INET, "0.0.0.0", &my_addr.sin_addr.s_addr);
// 绑定
int bind1 = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (bind1 < 0)
{
perror("bind my_addr:");
return -1;
}
// 想要获取发送对象的地址 就需要定义一个变量接收
struct sockaddr_in rcv_addr = {0};
// 阻塞
char rcv_buf[128] = {0};
// 接受对方地址 的长度 不能为 0!!
socklen_t rcv_addr_len = sizeof(rcv_addr);
int len = recvfrom(sockfd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
printf("len = %d\n", len);
printf("rcv_buf: %s\n", rcv_buf);
printf("rcv_port:%d\n", ntohs(rcv_addr.sin_port));
char rcv_ip[16] = {0};
inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
printf("rcv_ip:%s\n", rcv_ip);
close(sockfd);
return 0;
}
三、UDP完整收发案例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define MY_PORT 9000
#define DEST_IP "10.7.164.45"
#define DEST_PORT 8989
void *send_func(void *arg);
void *rcv_func(void *arg);
int main(int argc, char const *argv[])
{
// 创建socket 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error:");
return -1;
}
// 给当前网络进程 绑定固定的地址 ---> 当前程序自己的网络地址
struct sockaddr_in my_addr = {0};
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MY_PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
// inet_pton(AF_INET, "0.0.0.0", &my_addr.sin_addr.s_addr);
// 绑定
int bind1 = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (bind1 < 0)
{
perror("bind my_addr:");
return -1;
}
// 线程
pthread_t send_lwp, rcv_lwp;
pthread_create(&send_lwp, NULL, send_func, (void *)&sockfd);
pthread_create(&rcv_lwp, NULL, rcv_func, (void *)&sockfd);
pthread_join(send_lwp, NULL);
pthread_join(rcv_lwp, NULL);
close(sockfd);
return 0;
}
void *send_func(void *arg)
{
// 接受参数 获取描述符
int fd = *(int *)arg;
// 创建目的地址
struct sockaddr_in des_addr = {0};
des_addr.sin_family = AF_INET;
des_addr.sin_port = htons(DEST_PORT);
inet_pton(AF_INET, DEST_IP, &des_addr.sin_addr.s_addr);
while (1)
{
char buf[128] = {0};
fgets(buf, 128, stdin);
buf[strlen(buf) - 1] = 0; // 去掉\n
// 加规则 sendto:10.7.164.100:9569
if (strncmp(buf, "sendto:", 7) == 0)
{
unsigned short pport = 0;
char iip[16] = {0};
sscanf(buf, "sendto:%s %hu", iip, &pport);
printf("%s--%hu\n", iip, pport);
des_addr.sin_port = htons(pport);
inet_pton(AF_INET, iip, &des_addr.sin_addr.s_addr);
continue;
}
sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));
if (strcmp(buf, "quit") == 0)
{
break;
}
}
}
void *rcv_func(void *arg)
{
// 接受参数 获取描述符
int fd = *(int *)arg;
while (1)
{
// 想要获取发送对象的地址 就需要定义一个变量接收
struct sockaddr_in rcv_addr = {0};
// 阻塞
char rcv_buf[128] = {0};
// 接受对方地址 的长度 不能为 0!!
socklen_t rcv_addr_len = sizeof(rcv_addr);
int len = recvfrom(fd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
// printf("len = %d\n", len);
char rcv_ip[16] = {0};
inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
unsigned short rcv_port = ntohs(rcv_addr.sin_port);
printf("rcve:%s:%hu: %s\n", rcv_ip, rcv_port, rcv_buf);
}
}