UDP概述

一、UDP概述

1、特点

面向无连接的用户数据报协议 ----> 在传输数据前不需要建立连接!!!传输后也不需要确认!!!

UDP 是无连接、不可靠的传输层协议!!!

支持 广播和多播(组播) ----> 支持0字节报文!!

特点:

1、相对TCP速度稍 快!

2、简单的请求/应答 可以使用UDP

3、对于海量数据的传输不应该使用 UDP

4、广播/多播 必须使用UDP

2、UDP的编程框架

3、网络字节序

大端: 低字节序 存在 高地址

小端: 低字节序 存在 低地址

LSB: 低位有效 在一个字节(8位)中,LSB 是第 0 位。

MSB: 高位有效 在一个字节(8位)中,LSB 是第 7 位。

htonl()   32位
ntohl()
htons()   16位
ntohs()
主机是 小端 ---->  以上功能 可以将数据转为大端(换序)
主机是 大端 ---->  以上不能不做任何操作
htonl 和 ntohl   功能没有区别  ----> 名字不一样增加可读性
本质是宏
10.7.164.45  --->  都是字符串!!!
IPV4  是32位无符号整型!!!
int inet_pton(int family,const char *strptr,void * addr);
    family:  AF_INET     AF_INET6
    strptr:   需要转换的ip 字符串首地址
    addr:  转换后的IP整型 存储在这个地址指向的空间中!
返回值: 成功 1     失败 其他
const char *inet_ntop(int family,const void *addr,char *strptr,size_t len);
    family:  AF_INET     AF_INET6
    addr:  需转换的IP地址--->整型 
    strptr:   转换后的ip 字符串首地址
    len:   strptr缓冲区长度!!!
返回值: 成功就是转换后的Ip字符串首地址

二、UDP编程

1、socket套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int family,int type,int protocol)
功能:
    创建一个用于网络通信的socket套接字(描述符)
参数:
    family:协议族(AF_INET、AF_INET6、PF_PACKET 等)
        协议簇  地址族
        AF_INET  // internet 协议
        AF_UNIX // unix internal协议
    type:套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW 等)
        1、流式套接字(SOCK_STREAM)   ----->  TCP
            提供一个面向连接、可靠的数据传输服务,
            数据无差错、无重复的发送且按发送顺序接收。
            内设置流量控制,避免数据流淹没慢的接收方。
            数据被看作是字节流,无长度限制。
    
        2、数据报套接字(SOCK_DGRAM)   -----> UDP
            提供无连接服务。数据包以独立数据包的形式
            被发送,不提供无差错保证,数据可能丢失或重复,
            顺序发送,可能乱序接收。

        3、原始套接字(SOCK_RAW)
            可以对较低层次协议如IP、ICMP直接访问。
    protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP 等
        函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。
        通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;
        但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
        参数通常置为0

2、地址类型

// 通用地址结构体   ---->   地址  主要IP地址  IPV4  IPV6
struct sockaddr
{
    unsigned short sa_family;     // 地址族 2字节   AF_INET ipv4  AFINET6 ipv6   AF_LOCAL unix   AF_LINK  链路地址 
    char buf[14];   //  14字节 --->  装的 ip地址和port
}
IPV4的结构体
struct sockaddr_in
{
    short sa_family;  // 地址族 2字节   AF_INET ipv4  AFINET6 ipv6   AF_LOCAL unix   AF_LINK  链路地址
    unsigned short sin_port;  //2 字节  端口 ---> 转为网络字节序
    struct in_addr sin_addr;  // 4个字节  ---> IP地址
    unsigned char sin_zero[8];  //8个字节   填充0  保持和  struct sockaddr  同样大小!!!
}
struct in_addr
{
    in_addr_t s_addr;   // ipv4 的地址结构
}
// 注意
ip 和端口,要转换为 网络字节序(大小端转换)

// 用于端口转换   --->字符串转整型  ---> 不考虑大小端
char *p = "8000";
unsigned short num = 0;
sscanf(p, "%hu", &num);
int num1 = atoi(p);
printf("%d\n", num1);
atoi函数是将字符串转换成整数。
    int atoi (const char * str);
    该函数的返回值为int类型的整数,转换后的值不可超出int可表示的范围。
htons  

IP地址转换  ---> 转为网络字节序
inet_pton()
inet_aton()
    将strptr所指的字符串转换成32位的网络字节序二进制值
    #include <arpa/inet.h>
    int inet_aton(const char *strptr,struct in_addr *addrptr);
inet_addr() /用这个函数进行IP转换/  ----> 被抛弃 
    功能同上,返回转换后的地址。
    in_addr_t inet_addr(const char *strptr);
inet_ntoa()
    将32位网络字节序二进制地址转换成点分十进制的字符串。
    char *inet_ntoa(stuct in_addr inaddr);
配置地址信息
struct sockaddr_in dest_addr = {0};  // memset(&dest_addr,0,sizeof(dest_addr));  bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8000);  //  dest_addr.sin_port = htons(atoi("8000"));
inet_pton(AF_INET,"10.7.164.44",&dest_addr.sin_addr.s_addr);
//inet_aton("10.7.164.44",&dest_addr.sin_addr);
///dest_addr.sin_addr.s_addr = inet_addr("10.7.164.44");  ---- 被抛弃
不支持 IPv6: inet_addr 仅支持 IPv4 地址,对于 IPv6 地址无法处理。
无法检测错误: 函数返回的结果不能指示是否有错误发生,因此在实际使用中,我们很难判断是否解析成功。

如果获取自己主机IP
my_addr.sin_addr.s_addr = INADDR_ANY; 
//my_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); 
// INADDR_ANY就是inet_addr("0.0.0.0")
//INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 
//一般来说,在各个系统中均定义成为0值。

3、sendto发送

ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
功能:
    向to结构体指针中指定的ip,发送UDP数据
参数:
    sockfd:套接字
    buf:发送数据缓冲区
    nbytes: 发送数据缓冲区的大小
    flags:一般为0
    dest_addr:指向目的主机地址结构体的指针
    addrlen:dest_addr所指向内容的长度
注意:
    通过to和addrlen确定目的地址
    可以发送0长度的UDP数据包
返回值:
    成功:发送数据的字符数
    失败: -1
// 发送 ---> 需要确认发送的目标地址
//定义目标地址 变量 并赋值
struct sockaddr_in des_addr = {0};
des_addr.sin_family = AF_INET;  // ipv4
des_addr.sin_port = htons(8000);  // 端口 转为网络字节序
inet_pton(AF_INET,"10.7.164.45",&des_addr.sin_addr.s_addr);  // IPV4
// 发送了个hello   长度为6   目标des_addr---> 强转为通用地址格式   目标地址大小 sizeof(des_addr)
sendto(sockfd,"hello",6,0,(struct sockaddr *)&des_addr,sizeof(des_addr))

demo udp发送数据

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

int main(int argc, char const *argv[])
{
    // 创建socket 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error:");
        return -1;
    }
    // 发送数据
    char buf[128] = {0};
    fgets(buf, 128, stdin);

    // 发送前 配置目标地址
    struct sockaddr_in des_addr = {0};
    des_addr.sin_family = AF_INET;
    des_addr.sin_port = htons(8000);
    inet_pton(AF_INET, "10.7.164.45", &des_addr.sin_addr.s_addr);

    sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));

    close(sockfd);
    return 0;
}

4、接收recvfrom

ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:
    接收UDP数据,并将源地址信息保存在from指向的结构中
参数:
    sockfd:    套接字
    buf:       接收数据缓冲区
    nbytes:    接收数据缓冲区的大小
    flags:     套接字标志(常为0)
    src_addr:     源地址结构体指针,用来保存数据的来源
    addrlen: src_addr所指内容的长度
注意:
    通过from和addrlen参数存放数据来源信息
    from和addrlen可以为NULL, 表示不保存数据来源
返回值:
    成功:接收到的字符数
    失败: -1
// 接收 ---> 可以创建地址变量接受 收到的数据 来自于谁
//定义 接收的是谁的消息,获取对方地址 变量
struct sockaddr_in rcv_addr = {0};
// 发送了个hello   长度为6   目标des_addr---> 强转为通用地址格式   目标地址大小 sizeof(des_addr)
char rcvbuf[128] = {0};
int len = recvfrom(sockfd,rcvbuf,128,0,(struct sockaddr *)&rcv_addr,sizeof(rcv_addr));
if(len >= 0)
{
    //消息
    printf("%s\n",rcvbuf);
    //对方地址
    char rcvip[16]="";
    inet_ntop(AF_INET,&rcv_add.sin_addr.s_addr,rcvip,16);
    unsigned short rcvport=ntohs(rcv_add.sin_port);
    printf("rcv: %s: %u\n",rcvip,rcvport);
}

demo

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

int main(int argc, char const *argv[])
{
    // 创建socket 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error:");
        return -1;
    }
    // 发送数据
    char buf[128] = {0};
    fgets(buf, 128, stdin);

    // 发送前 配置目标地址
    struct sockaddr_in des_addr = {0};
    des_addr.sin_family = AF_INET;
    des_addr.sin_port = htons(8000);
    inet_pton(AF_INET, "10.7.164.45", &des_addr.sin_addr.s_addr);

    sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));

    // 想要获取发送对象的地址 就需要定义一个变量接收
    struct sockaddr_in rcv_addr = {0};
    // 阻塞
    char rcv_buf[128] = {0};
    // 接受对方地址 的长度  不能为 0!!
    socklen_t rcv_addr_len = sizeof(rcv_addr);
    int len = recvfrom(sockfd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
    printf("len = %d\n", len);
    printf("rcv_buf: %s\n", rcv_buf);
    printf("rcv_port:%d\n", ntohs(rcv_addr.sin_port));
    char rcv_ip[16] = {0};
    inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
    printf("rcv_ip:%s\n", rcv_ip);

    close(sockfd);
    return 0;
}

5、bind函数:绑定

#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:
    将本地协议地址与sockfd绑定
参数:
    sockfd: socket套接字
    myaddr: 指向特定协议的地址结构指针
    addrlen:该地址结构的长度
返回值:
    成功:返回0
    失败:其他
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    // 创建socket 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error:");
        return -1;
    }

    // 给当前网络进程 绑定固定的地址  ---> 当前程序自己的网络地址
    struct sockaddr_in my_addr = {0};
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(9000);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    // inet_pton(AF_INET, "0.0.0.0", &my_addr.sin_addr.s_addr);

    // 绑定
    int bind1 = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if (bind1 < 0)
    {
        perror("bind my_addr:");
        return -1;
    }

    // 想要获取发送对象的地址 就需要定义一个变量接收
    struct sockaddr_in rcv_addr = {0};
    // 阻塞
    char rcv_buf[128] = {0};
    // 接受对方地址 的长度  不能为 0!!
    socklen_t rcv_addr_len = sizeof(rcv_addr);
    int len = recvfrom(sockfd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
    printf("len = %d\n", len);
    printf("rcv_buf: %s\n", rcv_buf);
    printf("rcv_port:%d\n", ntohs(rcv_addr.sin_port));
    char rcv_ip[16] = {0};
    inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
    printf("rcv_ip:%s\n", rcv_ip);

    close(sockfd);
    return 0;
}

三、UDP完整收发案例

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

#define MY_PORT 9000

#define DEST_IP "10.7.164.45"
#define DEST_PORT 8989

void *send_func(void *arg);
void *rcv_func(void *arg);
int main(int argc, char const *argv[])
{
    // 创建socket 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error:");
        return -1;
    }

    // 给当前网络进程 绑定固定的地址  ---> 当前程序自己的网络地址
    struct sockaddr_in my_addr = {0};
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(MY_PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    // inet_pton(AF_INET, "0.0.0.0", &my_addr.sin_addr.s_addr);

    // 绑定
    int bind1 = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if (bind1 < 0)
    {
        perror("bind my_addr:");
        return -1;
    }

    // 线程
    pthread_t send_lwp, rcv_lwp;
    pthread_create(&send_lwp, NULL, send_func, (void *)&sockfd);
    pthread_create(&rcv_lwp, NULL, rcv_func, (void *)&sockfd);

    pthread_join(send_lwp, NULL);
    pthread_join(rcv_lwp, NULL);

    close(sockfd);
    return 0;
}

void *send_func(void *arg)
{
    // 接受参数  获取描述符
    int fd = *(int *)arg;
    // 创建目的地址
    struct sockaddr_in des_addr = {0};
    des_addr.sin_family = AF_INET;
    des_addr.sin_port = htons(DEST_PORT);
    inet_pton(AF_INET, DEST_IP, &des_addr.sin_addr.s_addr);

    while (1)
    {
        char buf[128] = {0};
        fgets(buf, 128, stdin);
        buf[strlen(buf) - 1] = 0; // 去掉\n
        // 加规则 sendto:10.7.164.100:9569
        if (strncmp(buf, "sendto:", 7) == 0)
        {
            unsigned short pport = 0;
            char iip[16] = {0};
            sscanf(buf, "sendto:%s %hu", iip, &pport);
            printf("%s--%hu\n", iip, pport);
            des_addr.sin_port = htons(pport);
            inet_pton(AF_INET, iip, &des_addr.sin_addr.s_addr);
            continue;
        }

        sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&des_addr, sizeof(des_addr));
        if (strcmp(buf, "quit") == 0)
        {
            break;
        }
    }
}

void *rcv_func(void *arg)
{
    // 接受参数  获取描述符
    int fd = *(int *)arg;
    while (1)
    {
        // 想要获取发送对象的地址 就需要定义一个变量接收
        struct sockaddr_in rcv_addr = {0};
        // 阻塞
        char rcv_buf[128] = {0};
        // 接受对方地址 的长度  不能为 0!!
        socklen_t rcv_addr_len = sizeof(rcv_addr);
        int len = recvfrom(fd, rcv_buf, 128, 0, (struct sockaddr *)&rcv_addr, &rcv_addr_len);
        // printf("len = %d\n", len);
        char rcv_ip[16] = {0};
        inet_ntop(AF_INET, &rcv_addr.sin_addr.s_addr, rcv_ip, 16);
        unsigned short rcv_port = ntohs(rcv_addr.sin_port);
        printf("rcve:%s:%hu: %s\n", rcv_ip, rcv_port, rcv_buf);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值