Windows网络编程之select模型(二)
一、select模型的特点
select 函数通常用于多路复用(multiplexing)操作,允许你同时监视多个套接字(sockets)的状态,并在其中任何一个套接字准备好进行 I/O 操作时进行响应。
以下是 select 模型的特点和作用:
-
并发处理多个套接字:
select允许你同时监视多个套接字的状态,例如可读、可写、出错等,而不需要为每个套接字创建一个独立的线程。这使得在一个单独的线程中能够有效地管理多个连接。 -
事件驱动:
select是事件驱动的,它会阻塞等待套接字上的事件发生,例如数据可读或可写。一旦有事件发生,它会通知你,你可以采取适当的操作响应这些事件。 -
跨平台: 尽管
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
);
参数介绍:
-
nfds:表示最大套接字描述符(socket descriptor)加1的值。在 Windows 中,这个参数通常设置为要监视的最大套接字描述符的值加1。 -
readfds:一个指向fd_set结构体的指针,用于指定要监视可读事件的套接字集合。你可以使用FD_ZERO和FD_SET宏来清除和设置套接字集合中的套接字。初始化为所有的socket,通过select投放给系统,系统将有事件发生的socket再赋值回来,调用后,这个参数就只剩下有请求的socket。 -
writefds:一个指向fd_set结构体的指针,用于指定要监视可写事件的套接字集合。检查是否有可写的socket,就是可以给哪些客户端套接字发消息,只要链接成功建立了,那么该客户端套接字就是可写的 -
exceptfds:一个指向fd_set结构体的指针,用于指定要监视异常事件的套接字集合。 -
timeout:一个指向struct timeval结构体的指针,用于设置超时时间,以毫秒为单位。如果设置为NULL,select将会一直阻塞,直到有套接字就绪或出现错误。如果设置为0,select将立即返回,用于轮询套接字状态。
返回值:
- 如果
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;
}

浙公网安备 33010602011771号