网络.1 UDP
1.网络概述
网络使设备之间在不同区域完成数据交换。
网络传输过程中必须遵守协议。
协议:网络数据传递要求的协议内容。
eg:Socket协议、IP协议、TCP/UDP协议
起源:美国1958年成立APPA(the Advanced Research Projects Agency 美国高级研究计划署)。
1.1 网络结构:
1.2 TCP/IP模型
1.3 IP地址
IP地址是当前设备在网络中的唯一地址。
1.3.1 IPv4
IPv4下32bit位组成,常用方式有两种:
- 4字节IP地址存储方式:
- 点分十进制IP进制字符串形式:
192.168.16.126
1.4 端口号
- 数据在发送到目标设备时,需要明确到底教给哪一个进程。
- 计算机为主机中每一个联网的设备分配了唯一的端口号。
- 端口号类型为short,范围0~65535。推荐使用1000以上的端口号,避免与系统特定的端口号冲突。
完成数据在网络端的传输,必须提供目标主机的IP地址和端口号。
1.5 其他
- MAC地址:物理地址,设备出场自带,无法修改。可利用MAC地址过滤,实现白名单和黑名单功能。
- 子网掩码NetMask:判断两个IP是否在同一个网段。
- 回环地址/本地地址:利用网络方式完成本机不同进程之间的数据交换。可利用本机地址+端口号完成。IPv4下本机地址是127.0.0.1,IPv6是::1。
2. 网络字节序/大端字节序
小端字节序:计算机存储数据的方式
大端字节序:网络传递过程中存储数据的方式
本地到网络【小转大】,网络到本地【大转小】
eg:
3.网络处理数据API
- hton ==> Host to NetWork 本地转网络
- ntoh ==> NetWork to Host 网络转本地
htonl 和 ntohl 处理 4 字节 IP 地址数据本地和网络数据转换
htons 和 ntohs 处理 2 字节 port 端口号数据本地和网络数据转换
3.1 htonl()
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
(uint32_t :无符号整数类型,只能表示非负整数,明确规定位数为 32 个二进制位,取值范围0 到 2³² - 1(即 0 到 4294967295)
函数功能:
- 将本地 uint32_t 无符号 int 类型转换,从小端字节序转换到网络传递要求所需的大端字节序数据形式。
参数:
- uint32_t hostlong : 用户提供的本地无符号 int 类型数据。
返回值:
- 转换为大端字节序网络所需数据内容。
3.2 htons()
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
函数功能:
- 将本地 uint16_t 无符号 short 类型转换,从小端字节序转换到网络传递要求所需的【大端字节序】数据形式。
参数:
- uint16_t hostshort :用户提供的本地无符号 short 类型数据。
返回值:
- 转换为大端字节序网络所需数据内容。
3.3 ntohl()
#include <arpa/inet.h>
uint32_t ntohl(uint32_t netlong);
函数功能:
- 将网络大端字节序 无符号 int 类型数据转换为本地小端字节序数据
参数:
- uint32_t netlong: 用户提供的网络传递无符号 int 类型数据。
返回值:
- 转换为小端字节序本地所需数据内容。
3.4
#include <arpa/inet.h>
uint16_t ntohs(uint16_t netshort);
函数功能:
- 将网络大端字节序无符号 short 类型数据转换为本地小端字节序数据
参数:
- uint16_t netshort:用户提供的网络传递无符号 short 类型数据。
返回值:
- 转换为小端字节序本地所需数据内容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <arpa/inet.h>u_int32_t my_htonl(uint32_t hostlong);
u_int16_t my_htons(uint16_t hostshort);u_int32_t my_ntohl(uint32_t netlong);
u_int16_t my_ntohs(uint16_t netshort);
int main(int argc, char const *argv[])
{//IPv4 ==> 192.168.16.125 对应大端字节序 4 字节 IP 地址数据 ==> 2098243776//端口号 8848 对应大端字节序 2 字节数据 ==> 36898 uint32_t local_ip_addr =3232239741;uint16_t local_post =8848;uint32_t net_ip_addr =my_htonl(local_ip_addr);printf("my_htonl:%d\n",net_ip_addr);net_ip_addr=htonl(local_ip_addr);printf("htonl:%d\n",net_ip_addr);uint16_t net_post =my_htons(local_post);printf("my_htons:%d\n",net_post);net_post=htons(local_post);printf("htons:%d\n",net_post);uint32_t host_ip_addr = my_ntohl(net_ip_addr);printf("host_ip_addr : %u\n", host_ip_addr);uint16_t host_port = my_ntohs(36898);printf("host_port : %d\n", net_post);return 0;
}u_int32_t my_htonl(uint32_t hostlong)
{uint8_t buff[4]={0};for (size_t i = 0; i < 4; i++){//将 hostlong 的地址转换为 uint8_t* 类型,即把它当作一个字节数组来访问memcpy(&buff[i], ((uint8_t *)&hostlong) + 3 - i, 1);}return *((uint32_t* )buff);}
u_int16_t my_htons(uint16_t hostshort)
{uint8_t buff[2]={0};for (size_t i = 0; i < 2; i++){//将 hostshort 的地址转换为 uint8_t* 类型,即把它当作一个字节数组来访问memcpy(&buff[i], ((uint8_t *)&hostshort) + 1 - i, 1);}return *((uint32_t* )buff);
}
u_int32_t my_ntohl(uint32_t netlong)
{return my_htonl(netlong);
}
u_int16_t my_ntohs(uint16_t netshort)
{return my_htons(netshort);
}
4.IP地址转换API
IPv4 地址有两种模式方式:
- 4 字节无符号 int 类型方式描述
- 点分十进制字符串 IPv4 地址描述,一般会采用 char 类型数组形式存储,数组容量为 16
网络传递中,IPv4 地址数据采用 4 字节无符号 int 数据形式,
本地采用点分十进制字符串 IPv4 地址
4.1 inet_pton()
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
函数功能:
- 本地IP转网络地址。将本地点分十进制 IP 地址字符串,根据 IP 协议转换为目标网络传递所需 IP 地址方式。通常情况下都是 IPv4 协议。
参数:
- int af :IP PROTOCOL IP 地址协议,可以选择 IPv4 或者 IPv6
- const char *src : 字符串形式的 IP 地址数据,例如 点分十进制 IPv4 协议地址
- void *dst : 用于存储网络传递所需的大端字节序目标变量地址
返回值:
- 标准转换正常: 1
- 提供的字符串数据不满足 IP 地址协议要求: 0
- 提供 AF 协议族不合法: -1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <arpa/inet.h>int main(int argc, char const *argv[])
{char ip_addr_str[16] = "192.168.16.125";int net_ip_addr = 0;int ret = inet_pton(AF_INET, ip_addr_str, &net_ip_addr);if (1 == ret){printf("net_ip_addr : %u\n", net_ip_addr);}else{printf("What are you弄啥嘞!\n");}return 0;
}
4.2 inet_ntop()
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
函数功能:
- 网络地址转本地IP。将网络传递使用的 IP 地址数据以及数据字节长度,根据当前协议要求,转换为本地字符串形式的 IP 地址数据,支持 IPv4 和 IPv6。
参数:
- int af : IP PROTOCOL IP 地址协议,可以选择 IPv4 或者 IPv6
- const void *src : 网络传递字节序/大端字节序 IP 地址数据地址
- char *dst : 用于存储字符串形式 IP 地址数据的 char 类型缓冲区空间首地址
- socklen_t size : 对应 char 类型缓冲区字节个数
返回值:
- 函数转换成功,返回 dst 对应字符数组空间首地址
- 转换失败,返回 NULL,同时设置 errno
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <arpa/inet.h>#define IP_ADDR_STR_LEN (16)int main(int argc, char const *argv[])
{int net_ip_addr = 2098243776;char ip_addr_str[IP_ADDR_STR_LEN] = "";socklen_t socklen = IP_ADDR_STR_LEN;const char *ret = inet_ntop(AF_INET, &net_ip_addr, ip_addr_str, socklen);printf("ip_addr_str : %s\n", ip_addr_str);return 0;
}
5. UDP网络传输
5.1 UDP特征
- 面向无连接,非完成可靠数据传递方式
- 数据发送速度快
- 没有客户端和服务器,只有发送端和接收端
- 利用数据包形式完成数据传递
5.2 UDP流程概述
通过 socket 函数创建/申请 socket 套接字,socket 支持不同版本,不同协议。
- 接收端
需要通过 bind 函数,明确当前 UDP 接收端进程绑定的端口号是哪一个。
利用 recvfrom 接收目标数据。
- 发送端
利用 sendto 将【打包】的 UDP 数据包发送给目标接收端
5.3 UDP相关API
5.3.1 socket()
socket 是网络编程中用于进行进程间通信的基础,所有的数据都是通过 Socket 文件描述符对应的网络通道进行数据发送和接收。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
函数功能:
- 根据协议限制,Socket类型要求和支持的协议要求,创建对应的 Socket 套接字,提供给进程使用的是文件描述符 FD
参数:
- int domain : 当前 IP 地址协议要求,提供给当前参数是 AF_INET
- int type : 指定 Socket 类型,可以选择 SOCK_STREAM TCP Socket 类型 ,SOCK_DGRAM UDP Socket 类型, SOCK_RAW 原始 Socket 类型
SOCK_STREAM :流式套接字,对应 TCP 协议(可靠、面向连接)
SOCK_DGRAM :数据报套接字,对应 UDP 协议(不可靠、无连接)
SOCK_RAW:原始套接字,用于直接访问底层协议(如 ICMP)
- int protocol : 协议支持,提供给函数是 IP_PROTOCOL
返回值:
- 申请创建 Socket 成功,返回值是当前 Socket 对应的文件描述符
- 申请创建失败,返回 -1,同时设置 errno
5.3.2 bind()
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
函数功能:
- 根据 IP 版本协议和指定的网络地址结构体信息,当前进程绑定指定端口进行数据监控和接收。
参数:
- int sockfd : Socket 对应的文件描述符
- const struct sockaddr *addr : IP 地址结构体数据,需要提供的参数有 IP 协议,IP 地址 4 字节网络字节序数据和端口号网络字节序。同时要求 IP 地址必须是本机地址
- socklen_t addrlen : 当前 struct sockaddr IP 地址结构体字节个数
返回值:
- bind 操作成功, 返回 0
- bind 操作失败返回 -1,同时设置对应的 errno
5.3.3 sendto()
#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);
函数功能:
- 发送数据到 UDP 接收端,需要的数据【发送数据内容】【目标地址信息】
参数:
- int sockfd : UDP 协议要求的 Socket
- const void *buf : 发送数据缓冲区地址
- size_t len : 发送数据字节数
- int flags : 一般都是 0
- const struct sockaddr *dest_addr : 目标接收端 IP 地址结构体数据,包括 IP 地址协议,IP 地址 4 字节网络字节序数据和端口号网络字节序数据。
- socklen_t addrlen : 对应 struct sockaddr 结构体字节数
返回值:
- 发送成功,返回发送数据字节数
- 发送失败,返回 -1,并且设置对应的 errno
5.3.4 recvfrom()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd,
void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t addrlen);
函数功能:
- 接收UDP 发送端数据
参数:
- int sockfd : UDP 协议要求的 Socket
- void *buf : 接收数据缓冲区地址
- size_t len : 接收数据最大字节数,一般对应缓冲区大小
- int flags : 一般都是 0
- struct sockaddr *dest_addr : 当前参数可以提供用于存储发送端 IP 地址数据的结构体地址,也可以提供 NULL ,表示不接收发送端 IP 地址数据
- socklen_t addrlen : 提供 socklen_t 类型变量地址,用于存储接收到的发送端 IP 地址结构体字节个数,如果 src_addr 提供 NULL,当前参数也提供 NULL
返回值:
返回值 > 0 表示接收到的有效字节个数
返回值 == 0 表示当前数据已接收完毕,或者 EOF (End Of File)
返回值 -1 表示接受失败,同时设置对应的 errno
发送端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>// 接收端 IPv4 点分十进制地址
#define DEST_IP_ADDR "192.168.16.125"
// 接收端端口号
#define DEST_PORT (9527)/*
UDP 发送端1. 申请 UDP 支持的 Socket2. 明确发送数据内容3. 准备接收端目标 IP 地址相关数据4. sendto 发送5. close 关闭资源
*/
int main(int argc, char const *argv[])
{// 1. 申请 UDP 支持的 Socket/*AF_INET 对应 IPv4 协议SOCK_DGRAM 满足 Socket Datagram 数据包套接字需求IPPROTO_IP IPPROTO_IP 支持 IP 协议*/int sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (-1 == sock_fd){perror("socket failed!");exit(1);}// 2. 明确发送数据内容char *str = "Hello UDP Receiver!";// 3. 准备目标接收端 IP 地址结构体数据,使用结构体 struct sockaddr_instruct sockaddr_in dest_addr;socklen_t socklen = sizeof(struct sockaddr_in);memset(&dest_addr, 0, socklen);// 3.1 明确当前使用的 IP 协议为 IPv4dest_addr.sin_family = AF_INET;// 3.2 端口号明确为 8848,提供大端字节序/网络字节序数据dest_addr.sin_port = htons(DEST_PORT);// 3.3 本地点分十进制 IP 地址字符串,转换为网络字节序数据 4 字节 IP 地址数据inet_pton(AF_INET, DEST_IP_ADDR, &(dest_addr.sin_addr.s_addr));// 4. sendto 发送ssize_t ret = sendto(sock_fd, // UDP Socket 对应文件描述符str, // 发送数据缓冲区,对应字符串strlen(str), // 发送数据字节数0, // 标志位 == 0(const struct sockaddr *)&dest_addr, // 接收端 IP 地址数据结构体地址。 socklen); // 接收端 IP 地址结构体字节数if (-1 == ret){perror("sendto failed!");/*【资源管理】发送数据出现问题,后续进程是执行退出操作,需要在退出之前,将使用的相关资源进行关闭,在 sendto 之前使用的资源,有且只有 socket 对应的文件描述符,利用 SystemCall close 函数进行关闭操作*/close(sock_fd);exit(1);}// 5. close 关闭资源close(sock_fd);return 0;
}
接收端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>// 本机 IPv4 点分十进制地址
#define HOST_IP_ADDR "192.168.16.125"
// 本机用于接收数据的端口号
#define HOST_PORT (9527)#define BUFFER_SIZE (256)/*
UDP 接收端1. 申请 UDP 支持的 Socket2. 【准备本机用于接收数据 IP 和 端口号 结构体】3. 【bind 操作】4. 准备接受数据缓冲区空间5. recvfrom 接收数据6. close 关闭资源
*/int main(int argc, char const *argv[])
{// 1. 申请 UDP 支持的 Socketint sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);// 2.【准备本机用于接收数据 IP 和 端口号 结构体】struct sockaddr_in host_addr;socklen_t socklen = sizeof(struct sockaddr_in);memset(&host_addr, 0, socklen);// 2.1 本地 IPv4 协议host_addr.sin_family = AF_INET;// 2.2 本地用于接收数据的端口号,需要网络字节序host_addr.sin_port = htons(HOST_PORT);// 2.3 本地 IPv4 点分十进制地址转换为网络字节序 IP 地址inet_pton(AF_INET, HOST_IP_ADDR, &(host_addr.sin_addr.s_addr));// 3. 【bind 操作】int ret = bind(sock_fd, (const struct sockaddr *)&host_addr, socklen);if (ret){perror("bind failed!");close(sock_fd);exit(1);}// 4. 准备接受数据缓冲区空间// 4.1 char 类型缓冲区空间,采用数组形式char buffer[BUFFER_SIZE] = "";// 4.2 本机用于存储发送端 IP 地址数据结构体struct sockaddr_in src_addr;memset(&src_addr, 0, socklen);// 5. recvfrom 接收数据ssize_t count = recvfrom(sock_fd, // UDP 协议支持的 Socket 套接字buffer, // 数据缓冲区地址BUFFER_SIZE, // 当前数据缓冲区字节数,同时也是最大接收数据字节数0, // 标志位 0(struct sockaddr *)&src_addr, // 用于存储发送端 IP 地址相关数据&socklen); // 接收到的 struct sockaddr_in 字节数// 用于临时存储数据发送端 IP 地址字符串char src_ip_addr[16] = "";if (count > 0){printf("Data from %s:%u, Data : %s\n",// 网络 4 字节 IP 地址数据,转换为 IPv4 本地点分十进制 IP 地址字符串inet_ntop(AF_INET, &(src_addr.sin_addr.s_addr), src_ip_addr, 16),// 网络 2 字节 Port 端口号数据转本地ntohs(src_addr.sin_port),// 收到的数据内容buffer);}// 6. close 资源close(sock_fd);return 0;
}