关于c语言的udp通讯详细讲解

目录

1. UDP简介

2. UDP通信流程

3、UDP的函数接口说明

4、UDP通讯测试代码

1. UDP简介

UDP全称 User Datagram Protocol,即:用户数据报协议。是面向无连接的协议。通常,UDP 通信还会被冠以不可靠的头衔。这里的不可靠指的是:无法可靠地得知对方是否收到数据。

UDP有如下特征:

  • 无连接:通信双方不需要事先连接
  • 无确认:收到数据不给对方发回执确认
  • 不保证有序、丢失不重发
  • 采用帧同步的数据报通信方式(即通信双方每次的收发数据量相等)

简单来讲,UDP 类似于寄信,如果两个人除了信件之外没有任何别的通信方式,那么信件寄出去了之后,寄件人是无法得知收件人是否收到信件或者是否已经读取内容的。UDP 的特点是无需连接、无需确认、无需缓冲区和分包序列号,因此 UDP 的效率是比较高的。

  • UDP适用情况

    • 发送小尺寸数据(如对DNS服务器进行IP地址查询时)
    • 在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
  • 广播、组播模式

    • 即时通讯软件的点对点文本通讯以及音视频通讯
    • 流媒体、VOD、VoIP、IPTV等网络多媒体服务中的实时数据传输

2. UDP通信流程

  • 发送方:

    1. 创建 UDP 套接字: int fd = socket();
    2. 准备好接收方的地址:struct sockaddr_in peerAddr;
    3. 给对方发送 UDP 数据报:sendto(fd, peerAddr);
  • 接收方:

    1. 创建 UDP 套接字:int fd = socket();
    2. 准备好自己的地址:struct sockaddr_in addr;
    3. 绑定套接字和地址:bind(fd, addr);
    4. 坐等各方发来的 UDP 数据报:recvfrom(fd);

3、UDP的函数接口说明

1、建立套接字(socket)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
函数作用:建立套接字,返回套接字文件描述符
函数参数:domain:你要选择哪一种地址族
        PF_INET/AF_INET  IPV4网络协议  PF ---> Protocol Family
        PF_INET6/AF_INET6 IPV6网络协议    AF ---> Address Family
        type:你要选择哪一种协议
        SOCK_STREAM选择TCP -- 流式套接字   --->Stream Sockets
        SOCK_DGRAM选择UDP -- 数据报套接字  --->Datagram Sockets
        protocol:传0表示使用默认协议
返回值:成功:套接字文件描述符sockfd
        失败:-1

2、绑定主机的IP地址和端口号(bind)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
函数作用:绑定主机的IP地址和端口号
参数:sockfd:套接字文件描述符
     addr:自己的IP地址和端口号
     addelen:地址的大小长度
返回:成功

#include <sys/socket.h>
ssize_t sendto(int socket,const void* message,size_t length,const struct sockaddr* dest_addr,socklen_t dest_len);
函数作用:用于UDP中发送数据,注意是UDP
参数:socket:套接字文件描述符
        message:你要发送的数据
        length:你要发送的数据大小,注意有多少写多少strlen
        flags:一般设置成0
        dest_addr:对方的IP地址和端口号
        dest_len:结构体的大小
返回值:成功:发送出去的字节
        失败:-1
        
需要新创建一个接收的结构体存储数据
struct sockaddr_in recv_addr;
int len = sizeof(struct sockaddr_in);
#include <sys/socket.h>
ssize_t recvform(int socket,void* buffer,size_t length,int flags,struct sockaddr* dest_addr,socklen_t* address_len);
函数作用:用于UDP中接收数据
参数:socket:套接字文件描述符
        buffer:接受的数据存储在这里
        length:接受的数据的大小,以最大的来接受(sizeof)
        flags:一般设置成0
        address:存储客户端的IP地址和端口号,可以获取到是谁给你发送的
        address_len:结构体的大小
返回值:成功:接收到的字节数
        失败:-1

#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);//将主机端口号转成网络端口号
uint16_t ntohs(uint16_t netshort);//将网络端口号转成主机端口号
说明:h代表主机(host) n代表网络(network) s代表端口号(short)
返回值:成功:要转换的字节序
        失败:-1
从什么(h,n)端口号到(to)什么(n,h)端口号(s)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);    //将主机IP转成网络IP
char* inet_ntoa(struct in_addr in);    //将网络IP转成主机IP
主机转网络addr(address) 网络转主机ntoa(network to address)

4、UDP通讯测试代码

//udp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define SERVER_IP "192.168.5.184" //-->服务器IP
#define SERVER_PORT 60000

/*
    UDP客户端代码实现步骤
    1、建立套接字
    2、填充结构体,绑定IP和地址(绑定可有可无)
    3、发送数据(sendto)
    4、关闭
*/

int main(int argc,char** argv)
{
    //手动传参的错误提示条件
    // if(argc != 3)
    // {
    //     perror("./a.out IP PORT");
    //     return -1;    
    // }

    int ret = 0;
    char buf[1024] = { 0 };

    //1、建立套接字文件描述符
    //参数:              地址族 流式套接字 默认协议
    int socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if(socketfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    //2、填充服务端的结构体,绑定IP和地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    //server_addr.sin_port = htons(atoi(argv[2]));                        //传参方式
    server_addr.sin_port = htons(SERVER_PORT);                        //宏定义方式
    //server_addr.sin_addr.s_addr = inet_addr(argv[1]);                 //传参方式
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);                 //宏定义方式

    printf("连接服务器成功[%s][%d]\n",SERVER_IP,SERVER_PORT);
    //printf("连接服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));

    //3、发送数据
    while(1)
    {
        //缓存区清零    
        bzero(buf,sizeof(buf));

        //发送数据,计算返回值(buf真实数据大小)
        scanf("%s",buf);

        //参数 套接字文件描述符 缓存区 缓存区真实大小 默认为0 (旧结构体指针强转取地址)新结构体的大小
        ret = sendto(socketfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));

        printf("发送数据 ret:%d\n",ret);

        //做主动退出的判断条件
        if(!strcmp(buf,"exit"))
            break;
    }

    //4、关闭套接字
    close(socketfd);

    return 0;
}

//udp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define SERVER_IP "192.168.5.184" //-->虚拟机或者主机IP
#define SERVER_PORT 60000

/*
    UDP服务端(接收端)代码实现步骤
    1、建立套接字
    2、填充结构体,绑定IP和地址
    3、定义接收端的一个结构体用来存放,接收数据(recvfrom)
    4、关闭
*/


int main(int argc,char** argv)
{
    //手动传参的错误提示条件
    // if(argc != 3)
    // {
    //     perror("./a.out IP PORT");
    //     return -1;    
    // }

    int ret = 0;
    char buf[1024] = { 0 };

    //1、建立套接字文件描述符
    //参数:              地址族 数据包套接字 默认协议
    int socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if(socketfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    //设置端口复用
    int optval = 1;
    //参数  套接字文件描述符    网络层   端口复用    设置值   设置值的大小
    setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));

    //2、填充结构体,绑定IP和地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    //server_addr.sin_port = htons(atoi(argv[2]));                        //传参方式
    server_addr.sin_port = htons(SERVER_PORT);                        //宏定义方式
    //server_addr.sin_addr.s_addr = inet_addr(argv[1]);                 //传参方式
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);                 //宏定义方式
    //参数  套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小
    ret = bind(socketfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
    if(ret == -1)
    {
        perror("bind fail");
        return -1;
    }
    printf("绑定服务器IP:%s 端口号PORT:%hu\n",SERVER_IP,SERVER_PORT);
    //printf("绑定服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));

    //定义一个接收端的地址,用来存放发送端的地址,另一端的端口号确定
    struct sockaddr_in recv_addr;
    int address_len = sizeof(struct sockaddr_in);

    //3、接收数据
    while(1)
    {
        //缓存区清零
        bzero(buf,sizeof(buf));

        //接收数据,计算返回值(buf真实数据大小)
        //参数   套接字文件描述符 缓存区 缓存区大小 默认值 旧结构体指针强转取地址)长度取址
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr*)&recv_addr,&address_len);

        //解析接收到的地址和端口(难点)
        char *ip= inet_ntoa(recv_addr.sin_addr)  ;//将网络字节序转换成本机字节序
        int port = ntohs(recv_addr.sin_port);//将网络端口转换为本机端口

        printf("[%s][%d]收到数据 buf:%s ret:%d\n",ip,port,buf,ret);

        //做主动退出的判断条件
        if(!strcmp(buf,"exit"))
            break;
    }

    //4、关闭套接字
    close(socketfd);

    return 0;
}

  • 12
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值