2. UDP编程
2.1 字节序概述
字节序是指多字节数据的存储顺序
分类:
- 小端格式:将低位字节数据存储在低地址(LSB)
- 大端格式:将高位字节数据存储在低地址
2.1.1 如何判断当前系统的字节序
#include <stdio.h>
union un
{
/* data */
int a;
char b;
};
int main(int argc, char const *argv[])
{
union un myun;
myun.a = 0x12345678;
printf("a = %#x\n", myun.a);
printf("b = %#x\n", myun.b);
if (myun.b == 0x78)
{
printf("小端对齐\n");
}
else
{
printf("大端对齐\n");
}
return 0;
}
输出结果
a = 0x12345678
b = 0x78
小端对齐
2.1.2 字节序转换函数
特点
- 网络协议指定了通讯字节序大端
- 只有在多字节数据处理时才需要考虑字节序
- 运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
- 异构计算机之间通讯,需要转换自己的字节序为网络字节序
在需要字节序转换的时候一般调用特定字节序转换函数
host -> network
1. htonl
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32)
功能:将32位主机字节序数据转换成网络字节序数据
参数:
hostint32: 待转换的32位主机字节序数据;
返回值:
成功:返回网络字节序的值
2. htons
#include <arpa/inet.h>
uint16_t htons(uint16_t hostint16);
功能:
将16位主机字节序数据转换成网络字节序数据;
参数:
uint16_t: unsigned short int
hostint16: 待转换的16位主机字节序数据;
返回值:
成功:
返回网络字节序的值
networl -> host
3. ntohl
#include <arpa/inet.h>
uint32_t ntohl(uint32_t netint32);
功能:
将32位网络字节序数据转换成主机字节序数据
参数:
uint32_t: unsigned int
netint32: 待转换的32位网络字节序数据;
返回值:
成功:
返回主机字节序的值
4. ntohs
uint16_t ntohs(uint16_t netint16);
功能:
将16位网络字节序数据转换成主机字节序数据;
参数:
uint16_t: unsigned short int
netint16:待转换的16位网络字节序数据
返回值:
成功:返回主机字节序的值
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
int a = 0x12345678;
short b = 0x1234;
// ubuntu中以小端存储,转换后变成大端存储,以字节进行操作
printf("%#x\n", htonl(a));
printf("%#x\n", htons(b));
return 0;
}
输出结果
0x78563412
0x3412
2.1.3 地址转换函数 - inet_pton、inet_ntop
人为识别的ip地址是点分十进制的字符串形式,但是计算机网络中识别的ip地址是整型数据。
// 字符串ip地址转换型数据
#include <arpa/inet.p>
int inet_pton(int family,const char *strptr,void *addrptr)
功能:将点分十进制数串转换成 32 位无符号整数
参数:
family: 协议族
strptr: 点分十进制数串
addrptr: 32 位无符号整数的地址;
返回值:
成功: 1;
失败: 非1
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
char ip_str[] = "192.168.3.103";
unsigned int ip_int = 0;
unsigned char *ip_p = NULL;
// 将点分十进制ip地址转化为32位无符号整形数据
inet_pton(AF_INET, ip_str, &ip_int);
printf("ip int = %d\n", ip_int);
ip_p = (char *)&ip_int;
printf("in_uint = %d,%d,%d,%d\n", *ip_p, *(ip_p + 1), *(ip_p + 2), *(ip_p + 3));
return 0;
}
输出结果
ip int = 1728293056
in_uint = 192,168,3,103
// 整型数据转字符串格式ip 地址
const char *inet_ntop(int family, const void *addrptr,char *strptr,size_t len)
功能: 将 32 位无符号整数转换成点分十进制数串;
参数:
family:协议族 :AF_INET // ipv4
addrptr:32 位无符号整数
strptr:点分十进制数串
len :strptr 缓存区长度
len的宏定义 :
#define INET_ADDRSTRLEN 16 // ipv4
#define INET_ADDRSTRLEN 46 // ipv6
返回值:
成功:返回字符串首地址
失败:返回NULL
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
unsigned char ip_int[] = {192, 168, 3, 103};
char ip_str[16] = "";
inet_ntop(AF_INET, &ip_int, ip_str, 16);
printf("ip_s =%s\n", ip_str);
return 0;
}
输出结果
ip_s =192.168.3.103
inet_addr()和inet_ntoa()
这两个函数只能用在ipv4地址的转换,且用的比较多
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
功能:将点分十进制ip地址转化为整形数据;
参数:
cp:点分十进制的IP地址
返回值:
成功:整形数据;
char *inet_ntoa(struct in_addr in);
功能:将整形数据转化为点分十进制的ip地址;
参数:
in:保存ip地址的结构体;
返回值:
成功: 点分十进制的IP地址
2.2 UDP介绍、编程流程
2.2.1 UDP介绍
UDP 协议
面向无连接的用户数据报协议,在传输数据前不需要先建立连接:目地主机的运输层收到UDP 报文后,不需要给出任何确认
UDP 特点
- 相比 TCP 速度稍快些
- 简单的请求/应答应用程序可以使用 UDP
- 对于海量数据传输不应该使用 UDP
- 广播和多播应用必须使用 UDP
UDP 应用
DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等
2.2.2 网络编程接口 socket
网络通信要解决的是不同主机进程间的通信
- 首要问题是网络间进程标识问题
- 多重协议的识别问题
20 世纪 80 年代初,加州大学 Berkeley 分校在 BSD(一个 UNIX OS 版本)系统内实现了 TCP/IP 协议,其网络程序编程开发接口为 socket。
随着 UNIX 以及类 UNIX 操作系统的广泛应用, socket 成为最流行的网络程序开发接口。
socket作用:提供不同主机之间的通信
socket特点:
- socket 也称“套接字”
- 是一种文件描述符,代表了一个通信管道的一个端点
- 类似对文件的操作一样,可以使用 read、write、close 等函数对 socket套接字进行网络数据的收取和发送等操作
- 得到 socket 套接字(描述符)的方法调用 socket()
socket分类:
- SOCK_STREAM,流式套接字,用于TCP
- SOCK_DGRAM,数据报套接字,用于UDP
- SOCK_RAW,原始套接字,对于其他层次的协议操作时需要使用这个类型
- ....
2.2.3 UDP编程C/S架构
服务器:
1. 创建套接字 socket();
2. 将服务器的ip地址、端口号与套接字进行绑定 bind();
3. 接受数据 recvfrom();
4. 发送数据 sendto()
客户端
1. 创建套接字 socket();
2. 发送数据 sendto();
3. 接收数据 recvfrom();
4. 关闭套接字 close();
2.3 UDP编程-创建套接字
2.3.1 创建套接字socket
#include <sys/socket.h>
int socket(int family int type,int protocol);
功能:创建一个用于网络通信的socket套接字;
参数:
family: 协议族(AF_INET,AF_INET6,PF_PACKET等);
type: 套接字类(SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等);
protocol:协议类别(0,IPPROTO_TCP,IPPROTO_UDP等)
返回值:
套接字
特点:
创建套接字时,系统不会分配端口;
创建的套接字默认属性是主动的,即主动发起服务的请求;
当作为服务器时,往往需要修改为被动的
2.3.2 案例
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("FAIL TO SOCKET");
exit(1);
}
printf("socketfd = %d\n", sockfd);
return 0;
}
输出结果
socketfd = 3
2.4 UDP编程-发送、绑定、接收数据
2.4.1 IPV4套接字地址结构
在网络编程中经常使用的结构体sockaddr_in
#include <netinet/in.h>
struct in_addr{
in_addr_t s_addr;//4字节
}
struct sockaddr_in{
sa_family_t sin_family;//协议族,2字节
in_port_t sin_port;//端口号,2字节
struct in_addr sin_addr;//ip地址,4字节
char sin_zero[8]//填充,8字节
}
通用结构体sockaddr
struct sockaddr{
sa_family_t sa_family; // 2字节
char sa_data[14]; // 14字节
}
为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样。但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可。
2.4.2 发送数据 - sendto函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);.
功能: 向 to 结构体指针中指定的 ip,发送 UDP 数据;
参数:
sockfd: 套接字
buf: 发送数据缓冲区
nbytes: 发送数据缓冲区的大小
flags: 一般为 0阻塞,MSG_DONT_WAIT非阻塞
to: 指向目的主机地址结构体的指针;
addrlen: dest_addr的长度;
注意:
通过 to 和 addren 确定目的地址;
可以发送0长度的 UDP 数据包;
返回值:
成功:返回实际发送的字节数
失败:返回-1
2.4.3 绑定函数 - bind函数
UDP网络程序想要收取数据需什么条件?
确定的ip地址,确定的port
怎样完成上面的条件呢?
接收端使用bind
函数,来完成地址结构与socket
套接字的绑定,这样ip、port
就固定了发送端在sendto
函数中指定接收端的ip、port
,就可以发送数据了 。
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
功能:将套接字与网络信息结构体绑定;
参数:
sockfd:文件描述符,socket的返回值;
addr:网络信息结构体
通用结构体(一般不用)struct sockaddr
网络信息结构体 sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
addrlen: addr的长度
返回值:
成功:0
失败:-1
2.4.4 接收数据
#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);
功能:接收数据
参数:
sockfd:文件描述符,socket的返回值
buf: 保存接收的数据
len: buf的长度
flags: 标志位
0 阻塞
MSG_DONTWAIT 非阻塞
src_addr: 源的网络信息结构体(自动填充,定义变量传参即可)
addrlen: src_addr的长度
返回值:
成功:0
失败:-1
2.5 UDP编程-Client、Server
2.5.1 UDP客户端注意点
1、本地IP、本地端口(我是谁)
2、目的IP、目的端口(发给谁)
3、在客户端的代码中,只设置了目的IP、目的端口
客户端的本地ip、本地port是我们调用sendto
的时候linux系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的port
不一样
2.5.2 UDP服务器注意点
- 服务器之所以要 bind 是因为它的本地 port 需要是固定,而不是随机的
- 服务器也可以主动地给客户端发送数据
- 客户端也可以用 bind,这样客户端的本地端口就是固定的了,但一般不这样做
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 12345
int main() {
int sockfd;
struct sockaddr_in server_addr;
socklen_t server_addr_len;
char buffer[BUF_SIZE];
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(SERVER_PORT);
server_addr_len = sizeof(server_addr);
while (1) {
printf("Enter a message to send (or 'q' to quit): ");
fgets(buffer, BUF_SIZE, stdin);
// 发送数据
if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&server_addr, server_addr_len) == -1) {
perror("sendto");
exit(1);
}
// 退出条件
if (buffer[0] == 'q' && (buffer[1] == '\n' || buffer[1] == '\0')) {
break;
}
// 接收回复
int recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0, NULL, NULL);
if (recv_len == -1) {
perror("recvfrom");
exit(1);
}
// 打印回复
buffer[recv_len] = '\0';
printf("Received reply from server: %s\n", buffer);
}
// 关闭套接字
close(sockfd);
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024
#define PORT 12345
int main() {
int sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len;
char buffer[BUF_SIZE];
// 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// 绑定套接字到服务器地址
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(1);
}
printf("UDP server is running and listening on port %d...\n", PORT);
while (1) {
// 接收数据
client_addr_len = sizeof(client_addr);
int recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
if (recv_len == -1) {
perror("recvfrom");
exit(1);
}
// 打印接收到的数据
buffer[recv_len] = '\0';
printf("Received message from client: %s\n", buffer);
// 发送回复
if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, client_addr_len) == -1) {
perror("sendto");
exit(1);
}
printf("Sent reply to client: %s\n", buffer);
}
// 关闭套接字
close(sockfd);
return 0;
}
标签:02,UDP,addr,int,ip,编程,接字,include,字节
From: https://www.cnblogs.com/hasaki-yasuo/p/18042960