C语言 网络编程(七)UDP通信创建流程

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。

UDP 是⼀个传输层的⽆连接的协议,我们编写代码⼀般是分为两个端。⼀个我们称之为发送端,另⼀
个我们称之为接收端。正常⼀般是接收端先运⾏,然后等待结束发送端发送过来的数据。

img_87.png

创建套接字

首先,我们需要创建套接字,套接字是通信的基础。我们可以通过 socket() 函数来创建套接字。

int socket(int domain, int type, int protocol);
参数:
    @domain
            地址族
            AF_UNIX, AF_LOCAL  本地通信,数据不仅过网卡
            AF_INET         IPV4 ineter⽹通信  
            AF_INET6        IPV6 ineter⽹通信
            AF_PACKET       网卡上的数据包通信
            ....


    @ type
            使⽤协议类型
                SOCK_STREAM 流式套接字(TCP)
                SOCK_DGRAM 报⽂套接字(UDP)
                SOCK_RAW原始套接字: (IP,ICMP)
                ......

    @protocol
            协议编号
            0 : 让系统⾃动识别
            IPPROTO_TCP : TCP协议
            IPPROTO_UDP : UDP协议


返回值:
        成功返回得到的⽂件描述符。当前可使用的最小描述符
        失败返回 -1

发送消息 sendto()

发送端通过 sendto() 函数来发送消息。

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 操作⽅式    0 表示默认操作
        @dest_addr 向指定的地址发送数据
        @addrlen  发送的地址的⼤⼩

返回值:

        成功返回实际发送的字节数,失败返回-1



struct sockaddr {
   
   
    unsigned short sa_family; // 地址族 对应socket()中的domain
    char sa_data[14]; // 地址数据 ip地址端口信息
};

struct sockaddr_in {
   
    
    short int sin_family; // 地址族 AF_INET
    unsigned short int sin_port; // 端口号
    struct in_addr sin_addr;// IP地址
    unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
};

struct in_addr {
   
   
    uint32_t       s_addr; // IP地址
};

示例用法:

//填充信息
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(8080);
peer_addr.sin_addr.s_addr = inet_addr("192.168.0.88");
//发送信息
n = sendto(sockfd,buf,n,0,(struct sockaddr *)&peer_addr,sizeof(struct sockaddr_in));

示例 发送数据

// todo UDP发送端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

//?发送数据
//?@param fd 套接字描述符
//?@param addr 目标地址
//?@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen);

//命令行参数 ip port
int main(int argc, char *argv[] ){
   
   
//    if(argc!= 3){
   
   
//        printf("Usage: %s ip port\n", argv[0]);
//        exit(EXIT_FAILURE);
//    }

    //!通过socket函数创建套接字
    //!@param domain 协议族,AF_INET表示IPv4协议族
    //!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
    //!@param protocol 协议,一般为0 让系统⾃动识别
    //!@return 成功返回套接字描述符,失败返回-1
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1){
   
       //创建套接字失败
        perror("socket err");
        exit(EXIT_FAILURE);
    }

    //准备接收消息的地址
    /*      struct sockaddr_in {
                short int sin_family; // 地址族 AF_INET
                unsigned short int sin_port; // 端口号
                struct in_addr sin_addr;// IP地址
                unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
            };
    */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8080);//htons函数将主机字节序转换为网络字节序

    //addr.sin_addr.s_addr=inet_addr("192.168.74.1");//inet_addr()将点分十进制IP地址转换为网络字节序IP地址

    //inet_aton()将点分十进制IP地址转换为网络字节序IP地址
    //@param ip 字符串形式的IP地址
    //@param in_addr 结构体变量,用于存储IP地址
    int ret=inet_aton("172.17.128.1", &addr.sin_addr); // 成功返回⾮0,失败返回0
    if(ret == 0){
   
   
        perror("inet_aton err");
        exit(EXIT_FAILURE);
    }
    printf("ip == %d\n",addr.sin_addr.s_addr);


    //inet_ntoa()将网络字节序IP地址转换为点分十进制IP地址
    //char *ip=inet_ntoa(addr.sin_addr); // 成功返回⾮0,失败返回0
    //printf("ip == %s\n",ip);


    send_data(fd, &addr, sizeof(addr));

    return 0;
}

//!发送数据
//!@param fd 套接字描述符
//!@param addr 目标地址
//!@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen){
   
   

    while (1){
   
   
        int n = 0;//返回发送的字节数
        char buf[1024] = {
   
   0};
        printf("请输入要发送的消息:");
        fgets(buf, 1024, stdin);


        //!发送数据
        //!@param fd 套接字描述符
        //!@param buf 发送缓冲区
        //!@param len 发送缓冲区长度
        //!@param flags 发送标志  0 表示默认操作
        //!@param addr 目标地址
        //!@param addrlen 地址长度
        //!@return 成功返回发送的字节数,失败返回-1
        n= sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)addr, addrlen);
        if(n == -1){
   
   
            perror("sendto err");
            exit(EXIT_FAILURE);
        }

        if(strncmp(buf, "exit",4) == 0){
   
   
            break;
        }
    }
}

bind() 绑定地址

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
        绑定套接字到地址。


参数:
        @sockfd 套接字文件描述符
        @addr  绑定的地址结构体
        @addrlen 地址结构体的长度

返回值:
        成功返回0,失败返回-1

recvfrom() 接收消息

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
功能:
        从套接字接收数据。

参数:
        @sockfd 套接字文件描述符
        @buf  接收缓冲区
        @len  接收缓冲区的长度
        @flags 接收标志  0 表示默认操作 
        @src_addr 发送端地址结构体 ,如果不为NULL,会获取发送方的地址放入该结构体
        @addrlen  发送端地址结构体的长度 值结果参数,必须进⾏初始化, 表示表示对⽅实际地址的⼤⼩。


返回值:
        成功返回实际接收的字节数,失败返回-1

示例 服务端

// todo UDP服务器端程序
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

//接收数据
void recv_data(int sockfd);

int main(int argc, char *argv[]){
   
   

    //!通过socket函数创建套接字
    //!@param domain 协议族,AF_INET表示IPv4协议族
    //!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
    //!@param protocol 协议,一般为0 让系统⾃动识别
    //!@return 成功返回套接字描述符,失败返回-1
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1){
   
       //创建套接字失败
        perror("socket err");
        exit(EXIT_FAILURE);
    }

    //准备服务器地址
    /*      struct sockaddr_in {
                short int sin_family; // 地址族 AF_INET
                unsigned short int sin_port; // 端口号
                struct in_addr sin_addr;// IP地址
                unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
            };
    */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8081);//htons函数将主机字节序转换为网络字节序
    //inet_aton()将点分十进制IP地址转换为网络字节序IP地址
    //*@param ip 字符串形式的IP地址
    //*@param in_addr 结构体变量,用于存储IP地址
    int ret=inet_aton("172.17.140.183", &addr.sin_addr); // 成功返回⾮0,失败返回0
    if(ret == 0){
        perror("inet_aton err");
        exit(EXIT_FAILURE);
    }


    //!绑定套接字到服务器地址
    //!@param sockfd 套接字描述符
    //!@param addr 服务器地址
    //!@param addrlen 服务器地址长度
    //!@return 成功返回0,失败返回-1
    int ret2 = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret2 == -1){
        perror("bind err");
        exit(EXIT_FAILURE);
    }

    //接收数据
    recv_data(fd);

    //关闭套接字
    close(fd);
    return 0;
}

//接收数据
void recv_data(int sockfd) {
    struct sockaddr_in client_addr;//客户端的地址
    int client_addr_len = sizeof(client_addr);

    while(1) {
        char recv_buf[1024]={0};

        //接收数据
        //*@param sockfd 套接字描述符
        //*@param buf 接收缓冲区
        //*@param len 接收缓冲区长度
        //*@param flags 接收标志
        //*@param src_addr 发送方地址
        //*@param addrlen 发送方地址长度
        //*@return 成功返回接收到的字节数,失败返回-1
        int ret = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &client_addr, &client_addr_len);
        if (ret == -1) {
            perror("recvfrom err");
            exit(EXIT_FAILURE);
        }
        //打印接收到的信息
        char *ip_str = inet_ntoa(client_addr.sin_addr);//将网络字节序IP地址转换为点分十进制IP地址
        int port = ntohs(client_addr.sin_port); //将网络字节序端口号转换为主机字节序端口号
        printf("接收到来自%s:%d的数据:%s\n", ip_str, port, recv_buf);

    }
}
相关实践学习
基于ACK Serverless轻松部署企业级Stable Diffusion
本实验指导您在容器服务Serverless版(以下简称 ACK Serverless )中,通过Knative部署满足企业级弹性需求的Stable Diffusion服务。同时通过对该服务进行压测实验,体验ACK Serverless 弹性能力。
相关文章
|
5月前
|
canal 编解码 运维
飞天洛神云网络再度入选通信顶会 SIGCOMM'24
飞天洛神云网络再度入选通信顶会 SIGCOMM'24
145 12
|
5月前
|
人工智能 自然语言处理 决策智能
智能体竟能自行组建通信网络,还能自创协议提升通信效率
《一种适用于大型语言模型网络的可扩展通信协议》提出创新协议Agora,解决多智能体系统中的“通信三难困境”,即异构性、通用性和成本问题。Agora通过标准协议、结构化数据和自然语言三种通信格式,实现高效协作,支持复杂任务自动化。演示场景显示其在预订服务和天气预报等应用中的优越性能。论文地址:https://arxiv.org/pdf/2410.11905。
131 6
|
5月前
|
网络协议 算法 安全
Go语言的网络编程与TCP_UDP
Go语言由Google开发,旨在简单、高效和可扩展。本文深入探讨Go语言的网络编程,涵盖TCP/UDP的基本概念、核心算法(如滑动窗口、流量控制等)、最佳实践及应用场景。通过代码示例展示了TCP和UDP的实现,并讨论了其在HTTP、DNS等协议中的应用。最后,总结了Go语言网络编程的未来发展趋势与挑战,推荐了相关工具和资源。
132 5
|
5月前
|
缓存 网络协议 安全
即时通讯初学者必知必会的20个网络编程和通信安全知识点
即时通讯IM应用开发的初学者很容易迷失在网络编程的复杂性以及通信安全的各种概念里,本文不涉及深度理论知识,尽量通过一句话或几句话让你快速了解20个相关的网络编程和通信安全知识点,希望能助你愉快地开始即时通讯应用开发。
98 0
|
7月前
|
负载均衡 网络协议 算法
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
这网络层就像搭积木一样,上层协议都是基于下层协议搭出来的。不管是ping(用了ICMP协议)还是tcp本质上都是基于网络层IP协议的数据包,而到了物理层,都是二进制01串,都走网卡发出去了。 如果网络环境没发生变化,目的地又一样,那按道理说他们走的网络路径应该是一样的,什么情况下会不同呢? 我们就从路由这个话题聊起吧。
172 4
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
|
8月前
|
网络协议
计算机网络与通信
计算机网络基本概念:了解计算机网络的定义、功能、分类和拓扑结构(如总线型、星型、环型、树形、网状等)。 网络通信原理:了解网络通信的基本原理、协议和技术,如TCP/IP协议、网络通信设备等。
150 3
|
8月前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
219 3
|
6月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
309 23
|
5月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
109 1
一文彻底搞清楚C语言的函数
|
6月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
267 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】