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函数

函数原型:

1
2
3
4
5
6
7
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模型的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#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模型源代码 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#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 @   TechNomad  阅读(622)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示