Windows网络编程之select模型(二)

一、select模型的特点

select 函数通常用于多路复用(multiplexing)操作,允许你同时监视多个套接字(sockets)的状态,并在其中任何一个套接字准备好进行 I/O 操作时进行响应。

以下是 select 模型的特点和作用:

  1. 并发处理多个套接字: select 允许你同时监视多个套接字的状态,例如可读、可写、出错等,而不需要为每个套接字创建一个独立的线程。这使得在一个单独的线程中能够有效地管理多个连接。

  2. 事件驱动: select 是事件驱动的,它会阻塞等待套接字上的事件发生,例如数据可读或可写。一旦有事件发生,它会通知你,你可以采取适当的操作响应这些事件。

  3. 跨平台: 尽管 select 最初是在 Unix 系统中引入的,但它也在 Windows 中提供支持。

实现原理:

  • 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数组结构中,即数组,通过select函数遍历数组中的socket数组,当某个socket有响应时,select就会通过其参数/返回值反馈出来
  • 如果检测到的是服务器socket,那么就是有客户端链接。
  • 如果检测到的是客户端socket,那么就是客户端请求通信。

二、select函数

函数原型:

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

参数介绍:

  1. nfds:表示最大套接字描述符(socket descriptor)加1的值。在 Windows 中,这个参数通常设置为要监视的最大套接字描述符的值加1。

  2. readfds:一个指向 fd_set 结构体的指针,用于指定要监视可读事件的套接字集合。你可以使用 FD_ZEROFD_SET 宏来清除和设置套接字集合中的套接字。初始化为所有的socket,通过select投放给系统,系统将有事件发生的socket再赋值回来,调用后,这个参数就只剩下有请求的socket。

  3. writefds:一个指向 fd_set 结构体的指针,用于指定要监视可写事件的套接字集合。检查是否有可写的socket,就是可以给哪些客户端套接字发消息,只要链接成功建立了,那么该客户端套接字就是可写的

  4. exceptfds:一个指向 fd_set 结构体的指针,用于指定要监视异常事件的套接字集合。

  5. timeout:一个指向 struct timeval 结构体的指针,用于设置超时时间,以毫秒为单位。如果设置为 NULLselect 将会一直阻塞,直到有套接字就绪或出现错误。如果设置为 0select 将立即返回,用于轮询套接字状态。

返回值:

  • 如果 select 函数成功,它将返回可读、可写或异常事件的套接字数量,即就绪的套接字数量。
  • 如果发生错误,select 返回 SOCKET_ERROR,你可以使用 WSAGetLastError 函数获取详细的错误码。

三、Server模型的源代码

#include <WinSock2.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData; // 创建一个 WSADATA 结构

    // 初始化 Winsock 库,指定要使用的版本
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (ret != 0) {
        printf("WSAStartup 失败,错误码: %d\n", ret);

        return 0;
    }

    //校验版本
    if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) {
        printf("版本不符合");
        WSACleanup();

        return 0;
    }

    // 在这里进行网络编程操作
    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketServer == INVALID_SOCKET) {
        int errorCode = WSAGetLastError();
        printf("socket创建失败,错误码:%u\n", errorCode);

        closesocket(socketServer);
        WSACleanup();
    }

    sockaddr_in si;
    si.sin_family = AF_INET;
    si.sin_addr.s_addr = inet_addr("127.0.0.1");
    //si.sin_addr.s_addr = INADDR_ANY; // 使用0.0.0.0监听所有可用端口
    si.sin_port = htons(1234);

    ret = bind(socketServer, (SOCKADDR*)&si, sizeof(si));
    if (ret == SOCKET_ERROR) {
        printf("bind绑定失败,错误码:%u\n", WSAGetLastError());

        closesocket(socketServer);
        WSACleanup();
        return 0;
    }

    ret = listen(socketServer, SOMAXCONN);
    if (ret == SOCKET_ERROR) {
        printf("listen监听失败,错误码:%u\n", WSAGetLastError());

        closesocket(socketServer);
        WSACleanup();
        return 0;
    }

    fd_set allSockets;
    //清零
    FD_ZERO(&allSockets);
    FD_SET(socketServer, &allSockets);
    //添加一个元素
    //FD_SET(socketServer, &allSockets);
    //删除一个元素
    //FD_CLR(socketServer, &allSockets);
    //判断socket是否在集合中,不在返回0,在则返回非0
    //FD_ISSET(socketServer, &allSockets);
    
    timeval st;
    st.tv_sec = 3;//3秒
    st.tv_usec = 0;
    while (true) {
        fd_set readSockets = allSockets;
        fd_set writeSockets = allSockets;
        FD_CLR(socketServer, &writeSockets);
        fd_set errorSocket = allSockets;
        ret = select(0, &readSockets, &writeSockets, &errorSocket, &st);
        if (ret == 0) {
            //没有可链接的socket
            Sleep(2000);
            continue;
        } else if (ret > 0) {
            //处理错误
            for (u_int i = 0; i < errorSocket.fd_count; i++) {
                char error[100] = { 0 };
                int len = sizeof(error);
                ret = getsockopt(errorSocket.fd_array[i], SOL_SOCKET, SO_ERROR, error, &len);
                if (ret == SOCKET_ERROR) {
                    printf("无法得到错误信息\n");
                } else {
                    printf("%s\n", error);
                }
            }
            
            //只要有客户端链接成功,那么writeSockets集合中就一直会有可用的客户端socket
            //writeSockets集合中的客户端socket和readSockets集合中的客户端socket是等价的,都可以用来向客户端发送数据
            for (u_int i = 0; i < writeSockets.fd_count; i++) {
                Sleep(5000);
                printf("服务器%d,%d:可写\n", socketServer, writeSockets.fd_array[i]);
                //向客户端发送消息
                const char send_buff[] = "hello, I'm is  writeSockets";
                ret = send(writeSockets.fd_array[i], send_buff, strlen(send_buff), 0);
                if (ret == SOCKET_ERROR) {
                    printf("send失败,错误码:%u\n", WSAGetLastError());
                }
            }
            
            //有socket
            for (u_int i = 0; i < readSockets.fd_count; i++) {
                if (readSockets.fd_array[i] == socketServer) {
                    //accept
                    SOCKET socketClient = accept(socketServer, NULL, NULL);
                    if (socketClient == SOCKET_ERROR) {
                        continue;
                    }
                    printf("客户端上线\n");
                    FD_SET(socketClient, &allSockets);
                } else {
                    //客户端
                    char buffer[1240] = { 0 };
                    int iResult = recv(readSockets.fd_array[i], buffer, sizeof(buffer), 0);
                    if (iResult == 0) {
                        printf("客户端下线\n");
                        //从集合中删除
                        SOCKET socketTemp = readSockets.fd_array[i];
                        FD_CLR(readSockets.fd_array[i], &allSockets);
                        //释放
                        closesocket(socketTemp);
                    } else if (iResult > 0) {
                        //接收到了客户端信息
                        printf("%s\n", buffer);

                        //向客户端发送消息
                        const char send_buff[] = "hello, I'm is server";
                        ret = send(readSockets.fd_array[i], send_buff, strlen(send_buff), 0);
                        if (ret == SOCKET_ERROR) {
                            printf("send失败,错误码:%u\n", WSAGetLastError());
                        }
                    } else {
                        //SOCK_ERROR
                        //强制下线也叫出错:10054
                        printf("erroCode:%d", WSAGetLastError());
                        if (10054 == WSAGetLastError()) {
                            //从集合中删除
                            SOCKET socketTemp = readSockets.fd_array[i];
                            FD_CLR(readSockets.fd_array[i], &allSockets);
                            //释放
                            closesocket(socketTemp);
                        }
                    }
                }
            }
        } else {
            //SOCK_ERROR
            printf("select失败,错误码:%u\n", WSAGetLastError());
        }
    }
    
    //释放所有socket
    for (u_int i = 0; i < allSockets.fd_count; i++) {
        closesocket(allSockets.fd_array[i]);
    }

    closesocket(socketServer);
    WSACleanup();

    return 1;
}

四、Client模型源代码 

#include <WinSock2.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData; // 创建一个 WSADATA 结构

    // 初始化 Winsock 库,指定要使用的版本
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (ret != 0) {
        printf("WSAStartup 失败,错误码: %d\n", ret);

        return 0;
    }

    //校验版本
    if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) {
        printf("版本不符合");
        WSACleanup();

        return 0;
    }

    // 在这里进行网络编程操作
    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketServer == INVALID_SOCKET) {
        int errorCode = WSAGetLastError();
        printf("socket创建失败,错误码:%u\n", errorCode);

        closesocket(socketServer);
        WSACleanup();
    }

    //链接服务器
    sockaddr_in si;
    si.sin_family = AF_INET;
    si.sin_addr.s_addr = inet_addr("127.0.0.1");
    si.sin_port = htons(1234);

    ret = connect(socketServer, (SOCKADDR*)&si, sizeof(si));
    if (ret == SOCKET_ERROR) {
        printf("connect失败,错误码:%u\n", WSAGetLastError());

        closesocket(socketServer);
        WSACleanup();
        return 0;
    }

    int nCount = 0;
    while (true) {
        if (nCount >= 5) {
            break;
        }
        
        const char send_buff[] = "hello, I'm is client";
        ret = send(socketServer, send_buff, sizeof(send_buff), 0);
        if (ret == SOCKET_ERROR) {
            printf("send失败,错误码:%u\n", WSAGetLastError());
        }

        Sleep(1000);

        char buffer[1024] = { 0 };
        ret = recv(socketServer, buffer, sizeof(buffer), 0);
        if (ret == 0) {
            printf("客户端连接中断\n");
        }
        else if (ret == SOCKET_ERROR) {
            printf("recv失败,错误码:%u\n", WSAGetLastError());
        }
        else {
            printf("recv_len:%d,recv_data:%s\n", ret, buffer);
        }

        nCount++;
    }
    
    closesocket(socketServer);
    WSACleanup();

    return 1;
}

 

posted @ 2023-09-27 22:27  TechNomad  阅读(774)  评论(0)    收藏  举报