UDP发送端

1、创建套接字

socket函数:

是用于创建网络套接字(socket)的系统调用,它是进行网络通信的基础。通过 socket() 函数,程序可以创建一个网络套接字,用于后续的数据发送和接收操作。这个函数通常用于客户端和服务器程序中,在数据交换前需要先创建一个套接字。

int socket(int domain, int type, int protocol);

参数说明:

  1. domain(协议族)

    • 用于指定套接字使用的协议族(Address Family),它决定了套接字支持哪种网络通信协议。常用的协议族有:

      • AF_INET:IPv4 地址族(用于 Internet 网络)。

      • AF_INET6:IPv6 地址族(用于 Internet 网络,支持更大的地址空间)。

      • AF_UNIXAF_LOCAL:用于本地通信(同一台机器上的进程之间通信)。

      • AF_PACKET:直接操作链路层数据(通常用于原始套接字)。

  2. type(套接字类型)

    • 套接字的类型决定了数据传输的方式和协议。常见的套接字类型有:

      • SOCK_STREAM:流式套接字,基于 TCP 协议,提供面向连接、可靠、双向的字节流通信。

      • SOCK_DGRAM:数据报套接字,基于 UDP 协议,提供无连接、不可靠、面向消息的通信。

      • SOCK_RAW:原始套接字,允许直接访问网络协议栈的低层数据,通常用于自定义协议或者网络分析。

  3. protocol(协议类型)

    • 用于指定协议。在大多数情况下,如果选择了 SOCK_STREAMSOCK_DGRAMprotocol 可以设置为 0,系统会自动选择合适的协议(如 TCP 或 UDP)。常用的协议有:

      • IPPROTO_TCP:TCP 协议(用于 SOCK_STREAM)。

      • IPPROTO_UDP:UDP 协议(用于 SOCK_DGRAM)。

      • IPPROTO_IP:IP 协议(用于原始套接字)。

返回值:
  • 如果成功,socket() 返回一个 非负整数,这个整数值代表新创建的套接字的文件描述符。

  • 如果失败,返回 -1,并且设置 errno 变量来说明错误的原因。

2.定义和初始化目标地址结构体

  • 使用 struct sockaddr_in 来定义接收端的 IP 地址和端口号。

  • 通过 inet_pton() 函数将接收端的 IPv4 地址字符串(点分十进制)转换为二进制形式并存储在地址结构体中。

  • 使用 htons() 函数将端口号转换为网络字节序(大端字节序)。

struct sockaddr_in 结构体

struct sockaddr_in dest_addr;
socklen_t socklen = sizeof(struct sockaddr_in);
memset(&host_addr, 0, socklen);  // 清零结构体
dest_addr.sin_family = AF_INET;  // 设置协议族为 IPv4
dest_addr.sin_port = htons(DEST_PORT);  // 转换端口号为网络字节序
inet_pton(AF_INET, DEST_IP_ADDR, &(dest_addr.sin_addr.s_addr));  // 转换 IP 地址为网络字节序

inet_pton()函数

int inet_pton(int af, const char *src, void *dst);
函数功能:

inet_pton() 是一个用于将文本格式的 IP 地址(如 "192.168.1.1")转换为二进制网络地址的函数。它是 网络编程 中非常常用的函数,特别是在使用 socket 编程时,通常需要将 IP 地址字符串转换为可以在网络上传输的二进制格式。

参数解释:
  1. af (Address Family 地址族)

    • 该参数指定了 IP 地址的格式,也就是使用哪种地址族。

      • AF_INET:表示 IPv4 地址族。对应的 IP 地址应该是标准的点分十进制格式(如 192.168.1.1)。

      • AF_INET6:表示 IPv6 地址族。对应的 IP 地址应该是标准的 IPv6 地址格式(如 2001:0db8:85a3:0000:0000:8a2e:0370:7334)。

    • 该参数用于告诉函数要转换的 IP 地址是 IPv4 还是 IPv6。

  2. src (源 IP 地址字符串)

    • 这是一个指向字符串的指针,字符串表示要转换的 IP 地址。字符串的格式根据 af 参数的值决定:

      • 对于 AF_INET,该字符串应该是一个 IPv4 地址,形式如 "192.168.1.1"

      • 对于 AF_INET6,该字符串应该是一个 IPv6 地址,形式如 "2001:0db8::ff00:42:8329"

  3. dst (目标二进制地址缓冲区)

    • 该参数是一个指向内存的指针,用于存储转换后的二进制地址。

      • 对于 AF_INET,该地址应该是一个大小为 4 字节的缓冲区,用于存储转换后的 IPv4 地址。

      • 对于 AF_INET6,该地址应该是一个大小为 16 字节的缓冲区,用于存储转换后的 IPv6 地址。

返回值:
  • 如果转换成功,返回 1

  • 如果遇到错误(例如,输入的 IP 地址格式无效),返回 0

  • 如果输入的地址族不匹配(如给定的地址字符串不是该地址族支持的格式),则返回 -1,并设置 errno 以提供错误信息。

htons()函数

unsigned short htons(unsigned short hostshort);
功能:将 主机字节序(Host Byte Order)转换成 网络字节序

why?在计算机系统中,数据的存储顺序有不同的字节序方式(字节顺序)。大端字节序(Big-endian)和小端字节序(Little-endian)是常见的两种字节序。网络字节序采用的是 大端字节序,也就是说,数据的高位字节存储在低地址位置,低位字节存储在高地址位置。为了保证不同计算机系统之间的数据能够正确传输和解析,我们需要在不同字节序的系统间进行转换。

参数解释:
  • hostshort:这是一个 16 位的短整型数,通常用于表示端口号等数据。在调用 htons() 函数时,传入的 hostshort 参数是主机字节序下的端口号。

    • 主机字节序:指的是本地计算机的字节序。

    • 网络字节序:指的是网络上传输数据时所使用的字节序,通常是大端字节序。

返回值:
  • htons() 函数会返回一个 16 位无符号短整型数,该数值已经被转换成网络字节序。

    • 如果主机字节序是大端字节序,htons() 将直接返回原值。

    • 如果主机字节序是小端字节序,htons() 会将字节顺序进行转换,确保结果是大端字节序。

3.准备发送的数据内容

定义一个字符串(如 "Hello UDP Receiver!")作为发送的数据。你可以根据需要修改发送内容。

4.使用 sendto() 函数发送数据

函数原型:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

参数解释:

1.sockfd(套接字文件描述符):

  • 这是通过 socket() 函数创建的套接字文件描述符,标识当前用于通信的 UDP 套接字。这个套接字用于指定发送的数据流。

  • 类型int

2.buf(发送缓冲区):

  • 这是一个指向要发送的数据的指针,数据的内容将通过这个缓冲区发送到目标地址。

  • 类型const void *

3.len(发送数据的长度):

  • 这是要发送的字节数。通常,长度是 buf 中数据的长度,单位是字节。

  • 类型size_t

4.flags(控制标志):

  • 这是控制发送的行为的标志位。通常在 UDP 中,设置为 0 即可,但也可以设置一些特殊标志,常见的有:

    • MSG_CONFIRM:请求确认。

    • MSG_DONTROUTE:不通过路由表发送数据。

    • MSG_NOSIGNAL:发送时不会产生 SIGPIPE 信号(当发送到已关闭的套接字时)。

  • 类型int

5.dest_addr(目标地址):

  • 这是指向 struct sockaddr 类型的指针,包含目标地址(即接收方的 IP 地址和端口号)。UDP 协议需要明确目标地址,以便发送数据包到正确的位置。

  • 类型const struct sockaddr *

6.addrlen(目标地址结构的长度):

  • 这是目标地址结构体的长度(单位:字节)。对于 struct sockaddr_in,长度通常是 sizeof(struct sockaddr_in)

  • 类型socklen_t

返回值:
  • 成功sendto() 返回成功发送的字节数。

  • 失败:返回 -1,并设置 errno 来表示错误。

UDP接收端

接收端同发送端一样第一步申请UDP支持socket

第二步 准备本机用于接收数据的地址结构,和发送端一样使用struct sockaddr_in 结构体来设置本地的地址和端口号。由于 UDP 是无连接的协议,我们只需要绑定本地的地址即可。

第三步:使用bind()函数绑定本地地址到套接字

通过调用 bind() 函数,将本地地址(host_addr)与套接字 sock_fd 进行绑定。绑定操作是确保接收端能够正确地接收到目标端口的数据。

bind()函数原型:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind() 是一个用于将本地的 IP 地址端口号 绑定到一个 套接字(socket)上的函数,通常在 UDP 和 TCP 网络编程中使用。它是 UDP 套接字、TCP 套接字以及 原始套接字 必须进行的操作之一。绑定操作使得一个套接字与本地的某个地址(IP 地址和端口号)关联起来,使得程序能够接收该地址上的数据。

参数解释:

  1. sockfd(套接字文件描述符):

    • 这是之前通过 socket() 函数创建的套接字文件描述符,标识你要绑定的套接字。

    • 类型int

    • 该参数用于指定要与本地地址绑定的套接字。

  2. addr(本地地址):

    • 这是指向一个 struct sockaddr 结构体的指针,结构体中包含了要绑定的本地地址(包括本地 IP 地址和端口号)。

    • 类型const struct sockaddr *

    • 由于 bind() 是一个通用函数,struct sockaddr 结构体需要具体化为协议相关的结构体。对于 IPv4 地址,通常使用 struct sockaddr_in(用于存储 IPv4 地址和端口),对于 IPv6 地址,使用 struct sockaddr_in6(用于存储 IPv6 地址和端口)。

  3. addrlen(地址结构的长度):这是结构体 addr 的大小,通常是使用 sizeof() 操作符来获取地址结构体的大小。对于 struct sockaddr_in,它的大小通常是 sizeof(struct sockaddr_in),对于 IPv6 地址,它通常是 sizeof(struct sockaddr_in6)。               类型socklen_t它告诉 bind() 函数该结构体有多大,这样函数才知道应该读取多大内存来解析地址信息。

        成功返回0,失败返回-1。

第四步:准备接收数据的缓冲区和本机用于存储发送端 IP 地址数据结构体

通常定义一个256位的buffer

char buffer[BUFFER_SIZE] = "";
struct sockaddr_in src_addr;
memset(&src_addr, 0, socklen);

第五步:接收数据

使用 recvfrom() 函数来接收数据。recvfrom() 会从套接字中读取数据,并将数据存储到指定的缓冲区,同时也会提供发送端的地址信息。

recvfrom() 是一个用于 接收数据 的函数,常用于 UDP原始套接字 的编程中,允许接收数据并获取 发送端的地址信息。与 recv() 函数不同,recvfrom() 不仅接收数据,还返回发送端的地址(如 IP 地址和端口号)。这对于 UDP 或其他无连接协议尤其重要,因为数据的发送者和接收者在通信前没有建立连接。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

参数解释:

  1. sockfd(套接字文件描述符):

    • 这是由 socket() 函数返回的套接字文件描述符,表示接收数据的套接字。

    • 类型int

    • 在调用 recvfrom() 时,指定要接收数据的套接字。

  2. buf(数据缓冲区):

    • 这是一个指向内存的指针,接收到的数据将被存储在该缓冲区中。

    • 类型void *

    • 需要确保这个缓冲区足够大,能够容纳接收到的数据。通常,它是一个字符数组。

  3. len(缓冲区大小):

    • 这是缓冲区 buf 的大小,即最大可以接收的字节数。这个大小应该与 buf 的实际大小匹配。

    • 类型size_t

    • 传递的 len 参数应小于或等于缓冲区的实际大小。

  4. flags(标志位):

    • 这是一个整数,用于控制接收行为。通常设置为 0,表示默认行为,但也可以使用一些标志来调整数据接收方式:

      • MSG_OOB:接收带外数据(Out-of-Band Data)。

      • MSG_PEEK:窥视数据,不将数据从缓冲区中移除。

      • MSG_WAITALL:接收所有数据,直到数据完整。

      • MSG_DONTWAIT:非阻塞模式,接收操作不会阻塞。

    • 类型int

  5. src_addr(发送端地址):

    • 这是一个指向 struct sockaddr 结构体的指针,用于存储发送端的地址信息(例如 IP 地址和端口号)。

    • 类型struct sockaddr *   通常需要强转 (struct sockaddr *)&src_addr,  

    • 在接收到数据后,该结构体将包含发送端的 IP 地址、端口号等信息。

  6. addrlen(地址结构的长度):

    • 这是 src_addr 结构体的大小,通常是 sizeof(struct sockaddr_in),用于告诉函数该地址结构的大小。调用时,recvfrom() 会修改该参数来表示实际地址的长度。

    • 类型socklen_t *

成功返回接收的数据字节数,失败返回-1

第六步,处理数据

一旦 recvfrom() 成功接收数据,接收端通常会对数据进行处理。接收的数据包含发送端的 IP 地址和端口号,这可以通过 src_addr 结构体访问。同时,还可以将接收到的 IP 地址转换为字符串格式并打印。

代码:
// 用于临时存储数据发送端 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);
}
  • inet_ntop():将接收到的发送端的 IP 地址(网络字节序)转换为点分十进制的字符串格式。

  • ntohs():将接收到的端口号(网络字节序)转换为主机字节序。

  • buffer:打印接收到的数据。

无论发送端还是接收端,最后都要关闭资源    close(sock_id);

代码案例:sender:

#include
#include
#include
#include
#include
#include
#include
//接收端ipv4点分十进制地址
#define DEST_IP_ADDR  "192.168.16.58"
//接收端端口号
#define DEST_PORT (8848)
int main(int argc, char const *argv[])
{
//sock 套接字
int sock_id = socket(AF_INET,SOCK_DGRAM,IPPROTO_IP);
if (-1 == sock_id)
{
perror("socket failed!");
exit(1);
}
//明确发送内容
char *str = "你好,我是嵌入式高手!";
//准备目标接收端IP地址结构体数据,使用结构体 struct sockaddr_in
struct sockaddr_in dest_addr;
socklen_t socklen = sizeof(struct sockaddr_in);
memset(&dest_addr,0,socklen);
//sin_family  明确当前使用的IP协议
dest_addr.sin_family = AF_INET;
//端口号明确为8848,提供大端字节序
dest_addr.sin_port = htons(DEST_PORT);
//本地点分十进制IP地址字符串,转换为网络地址
inet_pton(AF_INET,DEST_IP_ADDR,&(dest_addr.sin_addr));
//发送
ssize_t ret = sendto(sock_id,str,strlen(str),0,(struct sockaddr *)&dest_addr,socklen);
if (-1 == ret)
{
perror("sendto failed!");
close(sock_id);
exit(1);
}
close(sock_id);
return 0;
}

reveive:

#include
#include
#include
#include
#include
#include
#include
//本机ipv4点分十进制地址
#define HOST_IP_ADDR  "192.168.16.58"
//接收端端口号
#define HOST_PORT (8848)
//buff大小
#define BUFFER_SIZE (256)
int main(int argc, char const *argv[])
{
//申请socket
int sock_id = 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);
host_addr.sin_family = AF_INET;
host_addr.sin_port = htons(HOST_PORT);
inet_pton(AF_INET,HOST_IP_ADDR,&(host_addr.sin_addr));
int ret = bind(sock_id,(const struct sockaddr *)&host_addr,socklen);
if (ret)
{
perror("bind failed!");
close(sock_id);
exit(1);
}
char buffer[BUFFER_SIZE] = " ";
//本机用于存储发送端IP地址数据结构体
struct sockaddr_in src_addr;
memset(&src_addr,0,socklen);
//接收数据
ssize_t count = recvfrom(
sock_id,
buffer,
BUFFER_SIZE,
0,
(struct sockaddr *)&src_addr,
&socklen);
//临时存放数据发送端IP 地址字符串
char src_ip_addr[16] = "";
if (count > 0)
{
printf("数据发送端:%s:%u,内容:%s\n",inet_ntop(AF_INET,&(src_addr.sin_addr.s_addr),src_ip_addr,16),
ntohs(src_addr.sin_port),
buffer);
}
close(sock_id);
return 0;
}

posted on 2025-09-17 21:43  ycfenxi  阅读(18)  评论(0)    收藏  举报