Linux网络——socket网络通信udp

文章目录

UDP通信基础

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,提供简单、不可靠的数据传输服务。与TCP不同,UDP不保证数据包的顺序、完整性或可靠性,但因其低延迟和高效性,常用于实时性要求高的场景。

UDP的特点

无连接 :通信前无需建立连接,直接发送数据。
不可靠 :不保证数据包能否到达目的地,也不保证顺序。
高效 :头部开销小(仅8字节),适合高速传输。
支持单播、多播和广播:可同时向多个目标发送数据。

Linux下UDP通信核心步骤

创建UDP套接字

使用socket()函数创建套接字,指定协议族和套接字类型:

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

参数

domain:协议族

  • AF_INET:IPv4
  • AF_INET6:IPv6
  • AF_UNIX:本地套接字

type:套接字类型

  • SOCK_STREAM:TCP流式套接字
  • SOCK_DGRAM:UDP数据报套接字
  • SOCK_RAW:原始套接字

protocol:通常为0,自动选择

返回值

  • 成功:文件描述符
  • 失败:-1

绑定本地地址(可选)

服务器端通常需要绑定固定端口:

c 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数

  • sockfd:socket()返回的描述符
  • addr:指向包含地址的结构体
  • addrlen:地址结构长度

地址结构体

c 复制代码
struct sockaddr_in {
    sa_family_t    sin_family; // 地址族,如AF_INET
    in_port_t      sin_port;   // 端口号(网络字节序)
    struct in_addr sin_addr;   // IP地址
    char           sin_zero[8];// 填充
};

struct in_addr {
    uint32_t s_addr; // IPv4地址(网络字节序)
};
  • INADDR_ANY:监听所有可用网络接口
  • htons():将端口号转为网络字节序,解决不同主机字节序差异问题

发送数据函数:sendto()

函数原型

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd,               // 套接字描述符
              const void *buf,          // 发送数据缓冲区
              size_t len,               // 数据长度
              int flags,                // 发送标志
              const struct sockaddr *dest_addr,  // 目标地址结构
              socklen_t addrlen);       // 地址结构长度

参数详解

参数 说明
sockfd 通过socket()创建的UDP套接字描述符
buf 要发送的数据缓冲区指针
len 要发送的数据长度(字节数)
flags 通常设为0,可选标志:MSG_CONFIRM(链路层确认)MSG_DONTWAIT(非阻塞发送)
dest_addr 指向目标地址的sockaddr_in结构体
addrlen 目标地址结构体的长度

返回值

  • 成功:返回实际发送的字节数

  • 失败:返回-1,并设置errno

典型使用示例

c 复制代码
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);  // 目标端口
inet_pton(AF_INET, "192.168.1.100", &dest_addr.sin_addr);  // 目标IP

char *message = "Hello UDP Server";
ssize_t sent = sendto(sockfd, message, strlen(message), 0,
                     (struct sockaddr*)&dest_addr, sizeof(dest_addr));
if (sent == -1) {
    perror("sendto failed");
    exit(EXIT_FAILURE);
}

接收数据函数:recvfrom()

函数原型

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd,         // 套接字描述符
                void *buf,          // 接收缓冲区
                size_t len,         // 缓冲区长度
                int flags,          // 接收标志
                struct sockaddr *src_addr,  // 源地址结构
                socklen_t *addrlen); // 地址结构长度指针
                

参数详解

参数 说明
sockfd UDP套接字描述符
buf 接收数据的缓冲区
len 缓冲区的最大容量
flags 通常设为0,可选标志:MSG_WAITALL(等待完整数据报)MSG_DONTWAIT(非阻塞接收)
src_addr 用于存储发送方地址的结构体(可为NULL)
addrlen 输入时为缓冲区大小,输出时为实际地址长度

返回值

  • 成功:返回接收到的字节数

  • 失败:返回-1,并设置errno

  • 连接关闭:返回0(UDP中罕见)

典型使用示例

c 复制代码
#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];
struct sockaddr_in src_addr;
socklen_t addrlen = sizeof(src_addr);

ssize_t received = recvfrom(sockfd, buffer, BUFFER_SIZE-1, 0,
                           (struct sockaddr*)&src_addr, &addrlen);
if (received == -1) {
    perror("recvfrom failed");
    exit(EXIT_FAILURE);
}

buffer[received] = '\0';  // 添加字符串终止符
printf("Received %zd bytes from %s:%d\n", received,
       inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));

关键设计原因

无连接特性

UDP不需要connect()操作,每个数据报独立路由。sendto/recvfrom每次携带地址参数符合无连接协议特性。

网络字节序转换

htons/htonl确保数据在不同架构主机间正确传输。网络协议规定使用大端字节序作为标准。

INADDR_ANY的使用

服务器绑定INADDR_ANY可以接收所有网卡的数据,避免指定具体IP带来的限制。

缓冲区设计

UDP报文最大长度受MTU限制(通常约1500字节),应用程序需要合理设置缓冲区大小并处理报文截断情况。### Linux下UDP通信流程

客户端和服务端具体实现

客户端

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"  // 本地回环地址
#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    /* 1. 创建UDP套接字 */
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    /* 2. 配置服务器地址结构 */
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr) <= 0) {
        perror("invalid address");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 3. 发送数据到服务器 */
    const char *message = "Hello from UDP Client";
    if (sendto(sockfd, message, strlen(message), 0,
              (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
        perror("sendto failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("Message sent to server\n");

    /* 4. 接收服务器响应 */
    char buffer[BUFFER_SIZE];
    struct sockaddr_in from_addr;
    socklen_t len = sizeof(from_addr);
    
    ssize_t n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
                        (struct sockaddr*)&from_addr, &len);
    if (n < 0) {
        perror("recvfrom failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    buffer[n] = '\0';

    /* 验证响应来源 */
    char server_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &from_addr.sin_addr, server_ip, sizeof(server_ip));
    if (strcmp(server_ip, SERVER_IP) != 0 || 
        ntohs(from_addr.sin_port) != PORT) {
        printf("Warning: Received packet from unknown source %s:%d\n",
               server_ip, ntohs(from_addr.sin_port));
    }

    printf("Server reply: %s\n", buffer);

    /* 5. 关闭套接字 */
    close(sockfd);
    return 0;
}

服务端

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    /* 1. 创建UDP套接字 */
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    /* 2. 配置服务器地址结构 */
    struct sockaddr_in servaddr = {0};  // 初始化结构体
    servaddr.sin_family = AF_INET;      // IPv4地址族
    servaddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
    servaddr.sin_port = htons(PORT);    // 端口号(主机字节序转网络字节序)

    /* 3. 绑定套接字到指定端口 */
    if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    /* 4. 进入主循环处理客户端请求 */
    while (1) {
        char buffer[BUFFER_SIZE];
        struct sockaddr_in cliaddr;      // 存储客户端地址
        socklen_t len = sizeof(cliaddr); // 地址结构长度

        /* 5. 接收客户端数据 */
        ssize_t n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
                            (struct sockaddr*)&cliaddr, &len);
        if (n < 0) {
            perror("recvfrom failed");
            continue;  // 继续等待下一个包
        }
        buffer[n] = '\0';  // 添加字符串终止符

        /* 打印客户端信息 */
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &cliaddr.sin_addr, client_ip, sizeof(client_ip));
        printf("Received %zd bytes from %s:%d\n", n, 
               client_ip, ntohs(cliaddr.sin_port));
        printf("Message: %s\n", buffer);

        /* 6. 发送响应给客户端 */
        const char *reply = "Server received your message";
        if (sendto(sockfd, reply, strlen(reply), 0,
                  (struct sockaddr*)&cliaddr, len) < 0) {
            perror("sendto failed");
        }
    }

    /* 7. 关闭套接字(实际不会执行到这里) */
    close(sockfd);
    return 0;
}
相关推荐
tan77º5 分钟前
【Linux网络编程】Socket - UDP
linux·服务器·网络·c++·udp
czhc114007566316 分钟前
Linux 76 rsync
linux·运维·python
小白爱电脑30 分钟前
光纤的最小弯曲半径是多少?
网络
蓝易云1 小时前
Qt框架中connect()方法的ConnectionType参数使用说明 点击改变文章字体大小
linux·前端·后端
花落已飘2 小时前
多线程 vs 异步
linux·网络·系统架构
PanZonghui2 小时前
Centos项目部署之Nginx部署项目
linux·nginx
码出钞能力3 小时前
linux内核模块的查看
linux·运维·服务器
星辰云-3 小时前
# Linux Centos系统硬盘分区扩容
linux·运维·centos·磁盘扩容
聽雨2374 小时前
02每日简报20250704
linux·科技·金融·生活·社交电子·娱乐·媒体
Maki Winster4 小时前
Peek-Ubuntu上Gif录制工具-24.04LTS可装
linux·ubuntu·peek