【网络编程】UDP编程

目录

一、字节序

二、网络字节序和主机字节序

[三、 字节序的转换函数](#三、 字节序的转换函数)

四、地址转换函数

​编辑

五、UDP编程概述

1、socket

2、udp的编程流程

[​编辑 3、socket函数创建一个套接字(通信的端点)](#编辑 3、socket函数创建一个套接字(通信的端点))

4、IPv4地址结构体

5、通用套接字地址结构

6、sendto发送udp消息

7、bind给套接字绑定固定的IP和端口

8、recvfrom接收数据(阻塞)

一、字节序

将多字节看成一个整体存储的顺序。 文件------字节流(流指针)

字节序分为:大端格式、小端格式。

大小端是系统决定的,不能自由更改。

**判断当前系统是大端还是小端:**只需要判断低地址 存放的是高字节 还是低字节

二、网络字节序和主机字节序

如果计算机没有考虑字节序的问题,导致传输的数据错误 :

解决上述问题:

网络中的数据 必须是大端。

发送数据:需要将 主机字节序 转 网络字节序 hton

接收数据:需要将 网络字节序 转 主机字节序 ntoh

三、 字节序的转换函数

头文件:#include <arpa/inet.h>

1、主机字节序 转 网络字节序(发送数据)

uint32_t htonl(uint32_t hostint32);//转IP

uint16_t htons(uint16_t hostint16);//转端口号

2、网络字节序 转 主机字节序(接收数据)

uint32_t ntohl(uint32_t netint32);//转IP

uint16_t ntohs(uint16_t netint16);//转端口

四、地址转换函数

"10.9.11.3" 点分十进制数串 (用户识别的IP地址)

计算机 是用32位无符号数据 存储IP地址 。

发送数据 将点分十进制数据 转换成 32为无符号数据 :

#include <arpa/inet.h>

int inet_pton(int family,const char *strptr, void *addrptr);

功能: 将点分十进制数串转换成32位无符号整数

参数:

  • family 协议族 AF_INET‐‐>IPv4, AF_INET6‐‐>IPv6
  • strptr 点分十进制数串
  • addrptr 32位无符号整数的地址

返回值: 成功返回1 、 失败返回其它

接收数据 将32为无符号数据 转换成 点分十进制数串:

const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len)

功能: 将32位无符号整数转换成点分十进制数串

参数:

  • family 协议族 AF_INET
  • addrptr 32位无符号整数
  • strptr 点分十进制数串
  • len strptr缓存区长度

len的宏定义

#define INET_ADDRSTRLEN 16 //for ipv4

#define INET6_ADDRSTRLEN 46 //for ipv6

返回值: 成功:则返回字符串的首地址 失败:返回NULL

五、UDP编程概述

1、socket

  • 网络通信的3要素:IP、PORT、协议
  • socket 他是网络编程接口的统称。
  • socket 他是通信的一个端点(IP、PORT信息),也是特殊的文件描述符(套接字)。

2、udp的编程流程

3、socket函数创建一个套接字(通信的端点)

#include <sys/socket.h>

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

功能: 创建一个用于网络通信的socket套接字(描述符)

参数:

  • family:协议族(AF_INET、AF_INET6、PF_PACKET(链路层编程接口)等)
  • type:套接字类(SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW等)
  • protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等

返回值: 套接字

特点: 创建套接字时,系统不会分配端口

4、IPv4地址结构体

定义 地址结构体变量

复制代码
#include <netinet/in.h>
struct in_addr
{
    in_addr_t s_addr;//4字节
};
struct sockaddr_in //IPv4地址结构体
{
    sa_family_t sin_family;//2字节 AF_INET AF_INET6
    in_port_t sin_port;//2字节
    struct in_addr sin_addr;//4字节
    char sin_zero[8]//8字节
};

5、通用套接字地址结构

对传递给函数的实参进行类型转换 不做赋值操作

复制代码
struct sockaddr
{
    sa_family_t sa_family; // 2字节
    char sa_data[14] //14字节
};

6、sendto发送udp消息

ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,

const struct sockaddr *to, socklen_t addrlen);

功能: 向to结构体指针中指定的ip,发送UDP数据

参数:

  • sockfd:套接字
  • buf: 发送数据缓冲区
  • nbytes: 发送数据缓冲区的大小
  • flags:一般为0
  • to: 指向目的主机地址结构体的指针
  • addrlen:to所指向内容的长度

注意:

通过to和addrlen确定目的地址

可以发送0长度的UDP数据包

返回值: 成功:发送数据的字符数 ,失败: ‐1

例1:发送消息

复制代码
#include <stdio.h>
#include <sys/socket.h> // socket
#include <unistd.h>     // close
#include <string.h>     // memset strlen
#include <netinet/in.h> // struct sockaddr_in
#include <arpa/inet.h>  // inet_pton

int main(int argc, char const *argv[]) {
    // 创建用于通信的udp套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 0;
    }
    printf("sockfd = %d\n", sockfd);

    // 发送数据 sendto
    char buf[] = "hello sockfd";

    // 目的地址信息
    struct sockaddr_in dst_addr;
    memset(&dst_addr, 0, sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;   // IPv4
    dst_addr.sin_port = htons(8080); // 端口
    // 将目的IP 赋值到地址结构体中
    inet_pton(AF_INET, "10.9.11.251", &dst_addr.sin_addr.s_addr);

    // 如果套接字 没有事先bind绑定固定的IP以及端口
    // 那么第一次调用sendto的时候系统会给本地主机IP,以及随机端口,作为源地址信息
    sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));

    char buf1[] = "hello buf1";
    sendto(sockfd, buf1, strlen(buf1), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));

    close(sockfd);

    return 0;
}

7、bind给套接字绑定固定的IP和端口

bind只能绑定本地机的IP地址

  1. 创建套接字
  2. bind固定IP以及端口
  3. sendto发送数据或recvfrom接收数据

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

功能: 将本地协议地址与sockfd绑定

参数:

  • sockfd: socket套接字
  • myaddr: 指向特定协议的地址结构指针
  • addrlen:该地址结构的长度

返回值: 成功:返回0 ,失败:其他

复制代码
#include <stdio.h>
#include <sys/socket.h> // socket
#include <unistd.h>     // close
#include <string.h>     // memset strlen
#include <netinet/in.h> // struct sockaddr_in
#include <arpa/inet.h>  // inet_pton

int main(int argc, char const *argv[])
{
    // 创建用于通信的udp套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        return 0;
    }
    printf("sockfd = %d\n", sockfd);

    // bind固定的IP以及端口
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(9000);              // 源端口
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配地址
    int ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if (ret < 0) {
        // Handle error
    }
    {
        perror("bind");
        return 0;
    }

    // 发送数据 sendto
    char buf[] = "hello sockfd";

    // 目的地址信息
    struct sockaddr_in dst_addr;
    memset(&dst_addr, 0, sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;   // IPv4
    dst_addr.sin_port = htons(8080); // 端口
    // 将目的IP 赋值到地址结构体中
    inet_pton(AF_INET, "100.92.78.145", &dst_addr.sin_addr.s_addr);

    // 如果套接字 没有事先bind绑定固定的IP以及端口
    // 那么第一次调用sendto的时候系统会给本地主机IP,以及随机端口,作为源地址信息
    sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));

    char buf1[] = "hello buf1";
    sendto(sockfd, buf1, strlen(buf1), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));

    close(sockfd);

    return 0;
}

8、recvfrom接收数据(阻塞)

先bind固定的端口以及IP

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

功能: 接收UDP数据,并将源地址信息保存在from指向的结构中

参数:

  • sockfd: 套接字
  • buf:接收数据缓冲区
  • nbytes:接收数据缓冲区的大小
  • flags: 套接字标志(常为0)
  • from: 源地址结构体指针,用来保存数据的来源
  • addrlen: from所指内容的长度

注意:

通过from和addrlen参数存放数据来源信息

from和addrlen可以为NULL, 表示不保存数据来源

返回值: 成功:接收到的字符数 ,失败: ‐1

复制代码
#include <stdio.h>
#include <sys/socket.h> // socket
#include <unistd.h>     // close
#include <string.h>     // memset strlen
#include <netinet/in.h> // struct sockaddr_in
#include <arpa/inet.h>  // inet_pton

int main(int argc, char const *argv[])
{
    // 创建用于通信的udp套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        return 0;
    }
    printf("sockfd = %d\n", sockfd);

    // bind固定的IP以及端口
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(8000);              // 源端口
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配地址
    int ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if (ret < 0)
    {
        perror("bind");
        return 0;
    }

    // recvfrom接收数据
    while (1)
    {
        unsigned char buf[1500] = "";
        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        // &from_addr, &from_len可传NULL,表示不关心对方的ip
        int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from_addr, &from_len);
        if (len < 0)
        {
            perror("recvfrom");
            return 0;
        }

        // 获取发送者的IP
        char ip_str[16] = "";
        inet_ntop(AF_INET, &from_addr.sin_addr.s_addr, ip_str, 16);

        // 获取发送者的端口
        unsigned short port = ntohs(from_addr.sin_port);

        printf("IP:%s PORT:%hu 长度:%d 消息:%s\n", ip_str, port, len, buf);
    }

    close(sockfd);

    return 0;
}
复制代码
#include <stdio.h>
#include <sys/socket.h>     // socket
#include <unistd.h>       // close
#include <string.h>       // memset strlen
#include <netinet/in.h> // struct sockaddr_in
#include <arpa/inet.h>  // inet_pton
#include <pthread.h>
#include <stdlib.h> //htons

void *send_fun(void *arg) {
    struct sockaddr_in dst_addr;
    memset(&dst_addr, 0, sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;

    int sockfd = *(int *)arg;
    while (1) {
        char buf[128] = "";
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;

        if (strncmp(buf, "sayto", 5) == 0) {
            char ip_str[16] = "";
            unsigned short port = 0;
            sscanf(buf, "sayto %s %hu", ip_str, &port);
            dst_addr.sin_port = htons(port);
            inet_pton(AF_INET, ip_str, &dst_addr.sin_addr.s_addr);
            continue;
        } else if (strncmp(buf, "quit", 4) == 0) {
            return NULL;
        }

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

    return NULL;
}

void *recv_fun(void *arg) {
    int sockfd = *(int *)arg;
    while (1) {
        unsigned char buf[1500] = "";
        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);

        int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from_addr, &from_len);
        if (len < 0) {
            perror("recvfrom");
            return 0;
        }

        char ip_str[16] = "";
        inet_ntop(AF_INET, &from_addr.sin_addr.s_addr, ip_str, 16);
        unsigned short port = ntohs(from_addr.sin_port);

        printf("IP:%s PORT:%hu 长度:%d 消息:%s\n", ip_str, port, len, buf);
    }
}

int main(int argc, char const *argv[]) {
    if (argc != 2) {
        printf("./a.out 8000\n");
        return 0;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 0;
    }

    printf("sockfd = %d\n", sockfd);

    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(atoi(argv[1]));     // 源端口
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配地址

    int ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if (ret < 0) {
        perror("bind");
        return 0;
    }

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, send_fun, (void *)&sockfd);
    pthread_create(&tid2, NULL, recv_fun, (void *)&sockfd);

    pthread_join(tid1, NULL);
    pthread_cancel(tid2);
    pthread_join(tid2, NULL);

    close(sockfd);

    return 0;
}
相关推荐
KaMeidebaby18 小时前
卡梅德生物技术快报|原核膜蛋白表达优化实操手册,膜蛋白的纯化梯度洗脱完整流程
前端·网络·数据库·人工智能·算法
小秋求学记.19 小时前
Linux_Ubuntu的相关问题
linux·运维·ubuntu
β添砖java19 小时前
黑马Linux笔记
linux·运维·笔记
一只小菜鸡..19 小时前
Stanford CS144 学习笔记 (七):跨越虚实的边界——物理层、链路层与无线网络
网络·笔记·学习
且随疾风前行.19 小时前
Android Binder 驱动 - 内核驱动层源码初探
android·网络·binder
逑之20 小时前
HTTP、HTTPS2
网络·网络协议·http
雪的季节20 小时前
【无标题】
linux·服务器·python
斌蔚司李20 小时前
Tp-link手动设置有线中继模式(图文版)
网络·智能路由器
ly-2725321 小时前
Ubuntu重启之后挂载盘符变更应该怎么修复
linux·ubuntu