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);

    }
}
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
28天前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
53 10
|
1月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
37 0
Linux C/C++之TCP / UDP通信
|
1月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
54 2
|
2月前
|
网络协议 C语言
C语言 网络编程(十一)TCP通信创建流程---服务端
在服务器流程中,新增了绑定IP地址与端口号、建立监听队列及接受连接并创建新文件描述符等步骤。`bind`函数用于绑定IP地址与端口,`listen`函数建立监听队列并设置监听状态,`accept`函数则接受连接请求并创建新的文件描述符用于数据传输。套接字状态包括关闭(CLOSED)、同步发送(SYN-SENT)、同步接收(SYN-RECEIVE)和已建立连接(ESTABLISHED)。示例代码展示了TCP服务端程序如何初始化socket、绑定地址、监听连接请求以及接收和发送数据。
|
2月前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
2月前
|
网络协议 C语言
C语言 网络编程(十二)TCP通信创建-粘包
TCP通信中的“粘包”现象指的是由于协议特性,发送方的数据包被拆分并在接收方按序组装,导致多个数据包粘连或单个数据包分割。为避免粘包,可采用定长数据包或先传送数据长度再传送数据的方式。示例代码展示了通过在发送前添加数据长度信息,并在接收时先读取长度后读取数据的具体实现方法。此方案适用于长度不固定的数据传输场景。
|
2月前
|
网络协议 C语言
C语言 网络编程(十)TCP通信创建流程---客户端
在TCP通信中,客户端需通过一系列步骤与服务器建立连接并进行数据传输。首先使用 `socket()` 函数创建一个流式套接字,然后通过 `connect()` 函数连接服务器。连接成功后,可以使用 `send()` 和 `recv()` 函数进行数据发送和接收。最后展示了一个完整的客户端示例代码,实现了与服务器的通信过程。
|
2月前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
存储 C语言
《C语言及程序设计提高》网络课程主页
  在CSDN学院开出的网络系列课程《C语言及程序设计初步》已经完成(以后还将扩充)。   系列中的第二季《C语言及程序设计提高》的资源建设全部完成(视频资源链接)!   【公告】本课学员名单及QQ群公告,请到套餐主页查看。有兄弟,不孤单,一起营造学习氛围。   【重要通知1】自测题由于问卷星服务规则变化,不能看到答题情况,现转到“蓝墨云班课”中。加入方法:到mosoteach.cn免费注册账
1672 0

热门文章

最新文章