/*
 * 03_pollfd_array_pitfalls.c —— 紧凑数组管理与 POLLHUP 检测陷阱
 *
 * 编译: gcc 03_pollfd_array_pitfalls.c -o pitfalls
 * 运行: ./pitfalls 9999
 * 测试: nc localhost 9999 → 立刻按 Ctrl+] / Ctrl+D 断开
 *
 * 这个示例专门展示三个常见坑：
 *   ① events != revents → 用按位与而非 ==
 *   ② 删除元素用末尾填空，且循环不要 i++
 *   ③ 对端关闭时 POLLIN 也会触发，read 返回 0 才知道断开
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#define MAX_FDS 1024

static const char *flag_str(short e) {
    static char buf[128];
    buf[0] = '\0';
    if (e & POLLIN)    strcat(buf, "POLLIN ");
    if (e & POLLOUT)   strcat(buf, "POLLOUT ");
    if (e & POLLERR)   strcat(buf, "POLLERR ");
    if (e & POLLHUP)   strcat(buf, "POLLHUP ");
    if (e & POLLNVAL)  strcat(buf, "POLLNVAL ");
    if (e & POLLRDHUP) strcat(buf, "POLLRDHUP ");
    if (buf[0] == '\0') strcat(buf, "(none)");
    return buf;
}

int main(int argc, char *argv[]) {
    int port = (argc > 1) ? atoi(argv[1]) : 9999;

    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(lfd, (struct sockaddr*)&addr, sizeof(addr)) < 0 || listen(lfd, 8) < 0) {
        perror("bind/listen"); exit(1);
    }
    printf("🚀 端口 %d，连上来后立刻断开试试\n", port);

    struct pollfd pfds[MAX_FDS];
    pfds[0].fd = lfd; pfds[0].events = POLLIN; pfds[0].revents = 0;
    int n = 1;

    while (1) {
        int r = poll(pfds, n, -1);
        if (r < 0 && errno != EINTR) { perror("poll"); break; }

        if (pfds[0].revents & POLLIN) {
            int c = accept(lfd, NULL, NULL);
            pfds[n].fd = c;
            pfds[n].events = POLLIN | POLLRDHUP;   /* 加上 POLLRDHUP */
            pfds[n].revents = 0;
            n++;
            printf("➕ accept fd=%d, 当前 n=%d\n", c, n);
        }

        for (int i = 1; i < n; ) {
            short re = pfds[i].revents;
            if (re == 0) { i++; continue; }

            printf("🔔 fd=%d 触发: %s\n", pfds[i].fd, flag_str(re));

            int dead = 0;

            /* ⚠️ 即使对端先关，POLLIN 通常也会同时被设置 */
            if (re & POLLIN) {
                char buf[256];
                ssize_t got = read(pfds[i].fd, buf, sizeof(buf));
                if (got == 0) {
                    printf("    read=0, 对端正常关闭 → 必须 close 否则就泄漏\n");
                    dead = 1;
                } else if (got < 0) {
                    perror("    read");
                    dead = 1;
                } else {
                    printf("    收到 %ld 字节: '%.*s'\n", got, (int)got, buf);
                }
            }
            if (re & (POLLHUP | POLLERR | POLLNVAL | POLLRDHUP)) dead = 1;

            if (dead) {
                close(pfds[i].fd);
                pfds[i] = pfds[--n];
                printf("    🗑  fd 已关，数组紧凑后 n=%d\n", n);
                /* 关键：不 i++，下次循环重新检查 i 位置（原末尾元素移到这里了） */
            } else {
                i++;
            }
        }
    }
    return 0;
}
