单线程select模型

阻塞:
   阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。
   函数只有在得到结果之后才会返回。
非阻塞:
   非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

/**
参 数 :int nfds -- maxfd是要监视的最大的文件描述符值+1;
参 数 : fd_set *readfds -- 读文件描述符的集合
参 数 : fd_set *writefds -- 可写文件描述符集合
参 数 : fd_set *exceptfds -- 异常文件描述符集合
参 数 : struct timeval *timeout --超时描述,在一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
返回值 :
说 明 :select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型。
*/

int select(_In_ int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval * timeout);

FD_ZERO(fd_set *fdset);         --将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET(fd_set *fdset);          --用于在文件描述符集合中增加一个新的文件描述符。 
FD_CLR(fd_set *fdset);          --用于在文件描述符集合中删除一个文件描述符。 
FD_ISSET(int fd,fd_set *fdset); --用于测试指定的文件描述符是否在该集合中。
跨平台-简易TCP服务端
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#define SOCKET int
#define INVALID_SOCKET  -1
#define SOCKET_ERROR    -1
#define closesocket(s)  close(s)
#endif

#include <stdio.h>
#include <thread>
#include <vector>
#include <algorithm>  // std::remove_if

// 缓冲区大小
#define BUFFER_SIZE 4096

// 全局客户端列表
std::vector<SOCKET> g_clients;

// 设置非阻塞
void setNonBlocking(SOCKET sock) {
#ifdef _WIN32
    unsigned long mode = 1;
    ioctlsocket(sock, FIONBIO, &mode);
#else
    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}

// 处理客户端数据
int processor(SOCKET client_sock) {
    char buffer[BUFFER_SIZE];
    ssize_t n;

#ifdef _WIN32
    n = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
#else
    n = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
#endif

    if (n > 0) {
        buffer[n] = '\0';
        printf("Received from %d: %s", (int)client_sock, buffer);

        // 回显
        if (send(client_sock, buffer, n, 0) != n) {
            perror("send failed");
            return -1;
        }
        return 0;
    }
    else if (n == 0) {
        printf("Client %d disconnected.\n", (int)client_sock);
        return -1; // 客户端断开
    }
    else {
        // 非阻塞下 EWOULDBLOCK/EAGAIN 是正常的
#ifdef _WIN32
        if (WSAGetLastError() != WSAEWOULDBLOCK) {
            printf("recv error on %d: %d\n", (int)client_sock, WSAGetLastError());
            return -1;
        }
#else
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("recv error");
            return -1;
        }
#endif
        return 0; // 无数据可读,正常
    }
}

// 关闭客户端 socket 并从列表移除
void closeClient(SOCKET sock) {
#ifdef _WIN32
    closesocket(sock);
#else
    close(sock);
#endif
}

int main() {
#ifdef _WIN32
    // 启动 Windows socket 环境
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    if (WSAStartup(ver, &dat) != 0) {
        printf("WSAStartup failed.\n");
        return -1;
    }
#endif

    // 1. 创建 socket
    SOCKET server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_sock == INVALID_SOCKET) {
        printf("Failed to create socket.\n");
        return -1;
    }

    // 设置端口复用(避免 Address already in use)
    int opt = 1;
#ifdef _WIN32
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif

    // 绑定地址
    sockaddr_in server_addr = {};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(4567);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
        printf("Bind failed.\n");
#ifdef _WIN32
        closesocket(server_sock);
        WSACleanup();
#else
        close(server_sock);
#endif
        return -1;
    } else {
        printf("Bind successful.\n");
    }

    // 监听
    if (listen(server_sock, SOMAXCONN) == SOCKET_ERROR) {
        printf("Listen failed.\n");
        closesocket(server_sock);
        return -1;
    } else {
        printf("Listening on port 4567...\n");
    }

    // 设置服务器 socket 为非阻塞(accept 也需要非阻塞)
    setNonBlocking(server_sock);

    bool running = true;
    fd_set read_set, write_set, except_set;
    struct timeval tv;

    while (running) {
        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_ZERO(&except_set);

        // 总是监听服务器 socket
        FD_SET(server_sock, &read_set);
        FD_SET(server_sock, &except_set);  // 异常

        SOCKET max_sock = server_sock;

        // 添加所有客户端
        for (SOCKET client : g_clients) {
            FD_SET(client, &read_set);
            FD_SET(client, &except_set);
            if (client > max_sock) max_sock = client;
        }

        // 设置超时:1秒(避免无限阻塞)
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        int activity = select((int)max_sock + 1, &read_set, nullptr, &except_set, &tv);
        if (activity < 0) {
            perror("select error");
            break;
        }

        // 处理异常(通常表示连接断开)
        for (int i = (int)g_clients.size() - 1; i >= 0; i--) {
            SOCKET sock = g_clients[i];
            if (FD_ISSET(sock, &except_set)) {
                printf("Exception on socket %d, closing.\n", (int)sock);
                closeClient(sock);
                g_clients.erase(g_clients.begin() + i);
            }
        }

        // 处理客户端读事件
        for (int i = (int)g_clients.size() - 1; i >= 0; i--) {
            SOCKET sock = g_clients[i];
            if (FD_ISSET(sock, &read_set)) {
                if (processor(sock) == -1) {
                    closeClient(sock);
                    g_clients.erase(g_clients.begin() + i);
                }
            }
        }

        // 处理服务器 socket:新连接
        if (FD_ISSET(server_sock, &read_set)) {
            sockaddr_in client_addr = {};
            int addr_len = sizeof(client_addr);
            SOCKET client_sock = accept(server_sock, (sockaddr*)&client_addr, &addr_len);

            if (client_sock != INVALID_SOCKET) {
                setNonBlocking(client_sock);  // 必须非阻塞!
                g_clients.push_back(client_sock);
                printf("New client: fd=%d, IP=%s\n",
                       (int)client_sock, inet_ntoa(client_addr.sin_addr));
            }
            else {
#ifdef _WIN32
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    printf("accept error: %d\n", WSAGetLastError());
                }
#else
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("accept error");
                }
#endif
            }
        }

        // 可在此处处理其他业务(如定时任务)
        // printf("Idle processing...\n");
    }

    // 清理资源
    for (SOCKET sock : g_clients) {
        closeClient(sock);
    }
    closesocket(server_sock);

#ifdef _WIN32
    WSACleanup();
#endif

    printf("Server exited gracefully.\n");
    return 0;
}
简易TCP客户端

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#define SOCKET int
#define INVALID_SOCKET  -1
#define SOCKET_ERROR    -1
#define closesocket(s)  close(s)
#endif

#include <stdio.h>
#include <thread>
#include <atomic>
#include <string>

// 全局运行标志(原子操作,线程安全)
std::atomic<bool> g_bRun(true);

// 设置非阻塞
void setNonBlocking(SOCKET sock) {
#ifdef _WIN32
    unsigned long mode = 1;
    ioctlsocket(sock, FIONBIO, &mode);
#else
    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}

// 接收数据处理器
int processor(SOCKET client_sock) {
    char buffer[4096];
    ssize_t n;

#ifdef _WIN32
    n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
#else
    n = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
#endif

    if (n > 0) {
        buffer[n] = '\0';
        printf("Received: %s", buffer);
        return 0;
    }
    else if (n == 0) {
        printf("Server disconnected.\n");
        g_bRun = false;
        return -1;
    }
    else {
        // 非阻塞下 EWOULDBLOCK/EAGAIN 是正常的
#ifdef _WIN32
        if (WSAGetLastError() != WSAEWOULDBLOCK) {
            printf("recv error: %d\n", WSAGetLastError());
            g_bRun = false;
            return -1;
        }
#else
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("recv error");
            g_bRun = false;
            return -1;
        }
#endif
        return 0; // 无新数据
    }
}

// 发送线程函数
void sendThread(SOCKET sock) {
    char hello[] = "Hello from client!\n";
    send(sock, hello, strlen(hello), 0);
    printf("Sent: %s", hello);

    while (g_bRun.load()) {  // 检查全局标志
        printf("Enter message to send (or 'exit' to quit): ");
        fflush(stdout);

        char cmdBuf[256] = {};
        if (!fgets(cmdBuf, sizeof(cmdBuf), stdin)) {
            break;  // EOF
        }

        // 移除换行符
        cmdBuf[strcspn(cmdBuf, "\n")] = 0;

        if (strcmp(cmdBuf, "exit") == 0) {
            g_bRun = false;
            break;
        }

        if (strlen(cmdBuf) > 0) {
            std::string msg = std::string(cmdBuf) + "\n";
#ifdef _WIN32
            send(sock, msg.c_str(), (int)msg.length(), 0);
#else
            send(sock, msg.c_str(), msg.length(), 0);
#endif
        }
    }

    printf("Send thread exiting...\n");
}

int main() {
#ifdef _WIN32
    // 启动 Windows socket 环境
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    if (WSAStartup(ver, &dat) != 0) {
        printf("WSAStartup failed.\n");
        return -1;
    }
#endif

    // 1. 创建 socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        printf("Failed to create socket.\n");
        return -1;
    }

    // 设置为非阻塞(确保 accept 和 recv 不阻塞)
    setNonBlocking(sock);

    // 2. 连接服务器
    sockaddr_in server_addr = {};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(4567);

    // 统一使用本地回环地址
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    int ret = connect(sock, (sockaddr*)&server_addr, sizeof(server_addr));
    if (ret == SOCKET_ERROR) {
#ifdef _WIN32
        if (WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAEINPROGRESS) {
            printf("Connect failed.\n");
            closesocket(sock);
            WSACleanup();
            return -1;
        }
#else
        if (errno != EINPROGRESS) {
            perror("Connect failed");
            close(sock);
            return -1;
        }
#endif
    } else {
        printf("Connected to server.\n");
    }

    // 启动发送线程(使用 join,确保主线程等待)
    std::thread t1(sendThread, sock);

    fd_set read_set;
    struct timeval tv;

    while (g_bRun) {
        FD_ZERO(&read_set);
        FD_SET(sock, &read_set);

        // 每次 select 最多等待 1 秒
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        int activity = select((int)sock + 1, &read_set, nullptr, nullptr, &tv);

        if (activity < 0) {
            perror("select error");
            break;
        }

        if (activity == 0) {
            // 超时,可做其他事(如心跳)
            continue;
        }

        // 有数据可读
        if (FD_ISSET(sock, &read_set)) {
            if (processor(sock) == -1) {
                break;
            }
        }
    }

    // 通知发送线程退出
    g_bRun = false;

    // 等待发送线程结束
    if (t1.joinable()) {
        t1.join();
    }

    // 关闭 socket
    closesocket(sock);

#ifdef _WIN32
    WSACleanup();
#endif

    printf("Client exited gracefully.\n");
    return 0;
}
posted @ 2018-11-14 23:24  osbreak  阅读(1347)  评论(0)    收藏  举报