• Linux网络编程- recvfrom() & sendto()


    recvfrom()

    recvfrom() 函数是一个系统调用,用于从套接字接收数据。该函数通常与无连接的数据报服务(如 UDP)一起使用,但也可以与其他类型的套接字使用。与简单的 recv() 函数不同,recvfrom() 可以返回数据来源的地址信息。

    函数原型为:

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                     struct sockaddr *src_addr, socklen_t *addrlen);
    
    • 1
    • 2

    参数解释:

    1. sockfd:一个已打开的套接字的描述符。

    2. buf:一个指针,指向用于存放接收到的数据的缓冲区。

    3. len:缓冲区的大小(以字节为单位)。

    4. flags:控制接收行为的标志。通常可以设置为0,但以下是一些可用的标志:

      • MSG_WAITALL:尝试接收全部请求的数据。函数可能会阻塞,直到收到所有数据。
      • MSG_PEEK:查看即将接收的数据,但不从套接字缓冲区中删除它【1】。
      • 其他一些标志还可以影响函数的行为,但在大多数常规应用中很少使用。
    5. src_addr:一个指针,指向一个 sockaddr 结构,用于保存发送数据的源地址。

    6. addrlen:一个值-结果参数。开始时,它应该设置为 src_addr 缓冲区的大小。当 recvfrom() 返回时,该值会被修改为实际地址的长度(以字节为单位)。

    返回值:

    • 在成功的情况下,recvfrom() 返回接收到的字节数。
    • 如果没有数据可读或套接字已经关闭,那么返回值为0。
    • 出错时,返回 -1,并设置全局变量 errno 以指示错误类型。

    例子:

    struct sockaddr_in sender;
    socklen_t sender_len = sizeof(sender);
    char buffer[1024];
    
    int bytes_received = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                                  (struct sockaddr*)&sender, &sender_len);
    if (bytes_received < 0) {
        perror("recvfrom failed");
        // handle error
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这个例子中,我们使用 recvfrom() 从套接字 sockfd 接收数据。发送者的地址和端口信息保存在 sender 结构中。

    sendto()

    sendto() 函数是一个系统调用,用于发送数据到一个指定的地址。它经常与无连接的数据报协议,如UDP,一起使用。不像 send() 函数只能发送数据到一个预先建立连接的远端,sendto() 允许在每次发送操作时指定目的地址。

    函数原型为:

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);
    
    • 1
    • 2

    参数解释:

    1. sockfd:一个已打开的套接字的描述符。

    2. buf:一个指针,指向要发送的数据的缓冲区。

    3. len:要发送的数据的大小(以字节为单位)。

    4. flags:控制发送行为的标志。通常可以设置为0。一些可用的标志包括:

      • MSG_CONFIRM:在数据报协议下告诉网络层该数据已经被确认。
      • MSG_DONTROUTE:不查找路由,数据报将只发送到本地网络。
      • 其他标志可以影响函数的行为,但在大多数常规应用中很少使用。
    5. dest_addr:指向 sockaddr 结构的指针,该结构包含目标地址和端口信息。

    6. addrlendest_addr 缓冲区的大小(以字节为单位)。

    返回值:

    • 成功时,sendto() 返回实际发送的字节数。
    • 出错时,返回 -1 并设置全局变量 errno 以指示错误类型。

    例子:

    struct sockaddr_in receiver;
    receiver.sin_family = AF_INET;
    receiver.sin_port = htons(12345);  // Some port number
    inet_pton(AF_INET, "192.168.1.1", &receiver.sin_addr);  // Some IP address
    
    char message[] = "Hello, World!";
    ssize_t bytes_sent = sendto(sockfd, message, sizeof(message), 0,
                                (struct sockaddr*)&receiver, sizeof(receiver));
    if (bytes_sent < 0) {
        perror("sendto failed");
        // handle error
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这个例子中,我们使用 sendto() 发送一个字符串到指定的IP地址和端口号。如果发送失败,我们打印一个错误消息。


    注1

    让我们通过一个简单的示例来展示如何使用MSG_PEEK标志来窥探套接字中的数据。

    假设场景:我们正在编写一个简单的TCP服务器,该服务器接收客户端发送的消息。这些消息由一个头部开始,表示随后的数据长度。我们希望窥探这个头部,以便知道接下来应该读取多少数据。

    假设消息头部长度固定为4字节,表示数据的长度。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PORT 8080
    #define HEADER_SIZE 4
    
    int main() {
        int server_socket, client_socket;
        struct sockaddr_in address;
        int addr_len = sizeof(address);
        char buffer[1024];
    
        // 创建TCP套接字
        server_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (server_socket == -1) {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
        }
    
        // 绑定套接字到指定的端口
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons(PORT);
        if (bind(server_socket, (struct sockaddr*)&address, sizeof(address)) == -1) {
            perror("bind failed");
            close(server_socket);
            exit(EXIT_FAILURE);
        }
    
        // 开始监听
        if (listen(server_socket, 3) == -1) {
            perror("listen failed");
            close(server_socket);
            exit(EXIT_FAILURE);
        }
    
        printf("Waiting for a connection...\n");
        client_socket = accept(server_socket, (struct sockaddr*)&address, (socklen_t*)&addr_len);
        if (client_socket == -1) {
            perror("accept failed");
            close(server_socket);
            exit(EXIT_FAILURE);
        }
        printf("Connection established.\n");
    
        // 窥探消息头部
        int peek_len = recv(client_socket, buffer, HEADER_SIZE, MSG_PEEK);
        if (peek_len != HEADER_SIZE) {
            printf("Failed to peek header\n");
            close(client_socket);
            close(server_socket);
            exit(EXIT_FAILURE);
        }
    
        // 假设头部数据是一个整数,表示接下来的消息长度
        int message_len = *((int*)buffer);
        printf("Expected message length: %d\n", message_len);
    
        // 现在,可以根据消息长度读取整个消息
    
        close(client_socket);
        close(server_socket);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    这只是一个简化的示例,实际应用中还需要考虑其他方面,如错误处理、多个客户端的处理、消息头和数据的解析等。


    【1】若想了解sockaddr_in结构体,可移步到

         Linux网络编程- sockaddr & sockaddr_in & in_addr

    【2】若想了解常用的网络编程函数,可移步到

         Linux- 网络编程初探

  • 相关阅读:
    像设计师一样展示自己
    Linux--信号
    云原生Kubernetes:Yaml文件编写
    【JavaScript】-- 数组去重的方法
    第19章-IPv6基础
    十五、异常(7)
    MySQL -- 索引
    vs2017 配置 opencv
    R语言按周为采样规则计算dataframe数据中、指定数据列的每周最大值并生成新的dataframe
    ArduPilot开源飞控之AP_Baro_ExternalAHRS
  • 原文地址:https://blog.csdn.net/weixin_43844521/article/details/133659725