epoll+udp 2025-9-28

在服务器项目中最常使用tcp进行通信,但是在嵌入式开发中使用udp的场景变得多个起来。
我想一边回顾套接字通信,一边写一个udp版本

先做一下基础回忆
socket通信一般用三种:tcp、udp和uds
但是从实现的角度 这样分是不好的(至少个人这么认为),
可以从两个角度分
选网络还是本机(是创建套接字时候的参数)
是字节流(TCP)还是数据报(UDP)(uds可以使用字节流也可以使用数据报)


从函数入手

创建套接字

int socket(int domain,int type,int protrol)
参数名 作用 常见取值与说明
domain (协议族) 指定通信所在的协议域,决定了可使用的地址类型。 AF_INET:IPv4 互联网协议族
AF_INET6:IPv6 互联网协议族
AF_UNIX (或 AF_LOCAL):用于同一台主机上的进程间通信 (IPC),基于文件路径
type (套接字类型) 指定数据的传输语义,即通信风格。 SOCK_STREAM:面向连接的字节流,提供可靠、按序、双向的通信(如 TCP)
SOCK_DGRAM:无连接的数据报,提供固定长度的报文传输,不保证可靠和按序(如 UDP)
SOCK_RAW:原始套接字,允许程序直接访问底层网络协议
protocol (具体协议) 指定套接字使用的具体协议。 通常设置为 0,表示由系统根据 domaintype自动选择默认协议(如 AF_INET+ SOCK_STREAM默认选 TCP)。也可显式指定,如 IPPROTO_TCPIPPROTO_UDP等 。

对于AF_UNIX来说,protocol也是设置为0就好,他会根据AF_UNIX是字节流还是数据报正确选择通信协议

绑定

int bind(int sockfd , const struct sockaddr* addr , socklen_t addren)
参数名 数据类型 作用与说明
sockfd int 套接字描述符,是由 socket()函数成功创建后返回的值,用于唯一标识一个套接字 。
addr const struct sockaddr * 指向通用地址结构体的指针,它包含了准备绑定到套接字的 IP 地址和端口号 信息。实际使用时,通常传入的是 struct sockaddr_in(IPv4)或 struct sockaddr_un(Unix Domain Socket)等具体结构的地址,但需要强制转换为这个通用类型 。
addrlen socklen_t 指定了第二个参数 addr所指向的地址结构体的实际长度(以字节为单位)。通常直接使用 sizeof(struct sockaddr_in)或类似表达式来获取 。

这里要说一下这个第二个参数

对于网络通信要注意 网络字节序

Unix 域套接字 (struct sockaddr_un)

#include <sys/socket.h>
#include <sys/un.h>

struct sockaddr_un {
    sa_family_t sun_family;    /* 地址族: 必须是 AF_UNIX 或 AF_LOCAL */
    char        sun_path[108];  /* 套接字在文件系统中的路径 */
};

网络套接字 (如 struct sockaddr_in)

struct sockaddr_in
{
    sa_family_t sin_family;     /* 地址族协议: AF_INET */
    in_port_t sin_port;         /* 端口, 2字节-> 大端  */
    struct in_addr sin_addr;    /* IP地址, 4字节 -> 大端  */
    /* 填充 8字节 */
    unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};  

绑定示例


#include <sys/un.h>

//unix套接字使用
/*
struct sockaddr_un addr = {
    .sun_family = AF_UNIX,
    .sun_path = "/tmp/my_socket" // 这里是在初始化,是合法的
};
或
*/
struct sockaddr_un addr;
memset(&addr,0,sizeof(addr));
addr.sun_family = AF_UNIX; // 或 AF_LOCAL,两者等价
strncpy(addr.sun_path, "/tmp/my_socket", sizeof(addr.sun_path) - 1); // 绑定到文件路径
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';


//如果是网络字节序 将socket()返回值和本地的ip端口绑定在一起

struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(10000); //大端端口
// INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
// 这个宏可以代表任意一个IP地址
// 这个宏一般用于本地的绑定操作
addr.sin_addr.s_addr = INADDR_ANY;  // 这个宏的值为0 == 0.0.0.0
//指定某一个特定的ip的写法
// inet_pton(AF_INET, "192.168.237.131", &addr.sin_addr.s_addr);


//最后传入的时候
(struct sockaddr*)&addr

udp socket通信

服务端流程:socket->bind->recvfrom->sendto->close
客户端流程:socket->sendto->recvfrom->close

对于udp来说,其是没有accept和listen的
(还想说一点,无论是tcp还是udp其本身都是支持双向通信的(全双工),在嵌入式、中间件或其他领域实际开发中常使用两个服务端、两个客户端那是另外一回事)

下面是服务端udp socket通信的代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 通信的套接字和本地的IP与端口绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);    // 大端
    addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }

    char buf[1024];
    char ipbuf[64];
    struct sockaddr_in cliaddr;
    int len = sizeof(cliaddr);
    // 3. 通信
    while(1)
    {
        // 接收数据
        memset(buf, 0, sizeof(buf));
        int rlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &len);
        printf("客户端的IP地址: %s, 端口: %d\n",
               inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
               ntohs(cliaddr.sin_port));
        printf("客户端say: %s\n", buf);

        // 回复数据
        // 数据回复给了发送数据的客户端
        sendto(fd, buf, rlen, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
    }

    close(fd);

    return 0;
}


epoll+udp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(0);
    }

    // 2. 设置套接字为非阻塞模式
    if (set_nonblocking(fd) == -1) {
        perror("fcntl");
        close(fd);
        exit(0);
    }

    // 3. 通信的套接字和本地的IP与端口绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);    // 大端
    addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1) {
        perror("bind");
        exit(0);
    }

    // 4. 创建epoll实例
    int epoll_fd = epoll_create1(0); //epoll_create(1) //参数大于0
    if (epoll_fd == -1) {
        perror("epoll_create1");
        close(fd);
        exit(0);
    }

    // 5. 注册UDP套接字到epoll,监听读事件
    struct epoll_event event;
    event.events = EPOLLIN;  // 监听可读事件
    event.data.fd = fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_ctl");
        close(fd);
        close(epoll_fd);
        exit(0);
    }

    struct epoll_event events[MAX_EVENTS];
    char buf[BUFFER_SIZE];
    char ipbuf[64];
    
    printf("UDP Server with epoll is running on port 9999...\n");

    // 6. 事件循环
    while(1) {
        // 等待事件发生,超时时间设为-1(无限等待)
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }

        // 处理所有就绪的事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == fd && (events[i].events & EPOLLIN)) {
                // UDP数据可读
                struct sockaddr_in cliaddr;
                socklen_t len = sizeof(cliaddr);
                
                // 接收数据
                memset(buf, 0, sizeof(buf));
                int rlen = recvfrom(fd, buf, sizeof(buf), 0, 
                                   (struct sockaddr*)&cliaddr, &len);
                
                if (rlen > 0) {
                    printf("客户端的IP地址: %s, 端口: %d\n",
                           inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, 
                                   ipbuf, sizeof(ipbuf)),
                           ntohs(cliaddr.sin_port));
                    printf("客户端say: %s\n", buf);

                    // 回复相同的数据给客户端
                    sendto(fd, buf, rlen, 0, 
                          (struct sockaddr*)&cliaddr, len);
                } else if (rlen == -1) {
                    perror("recvfrom");
                }
            }
            
            // 处理错误事件
            if (events[i].events & (EPOLLERR | EPOLLHUP)) {
                printf("Socket error or hang up occurred\n");
                close(events[i].data.fd);
            }
        }
    }

    // 清理资源
    close(fd);
    close(epoll_fd);
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容