面试题答案
一键面试-
基本原理:
select
模式是一种同步I/O多路复用技术。它允许程序监视一组文件描述符(在网络编程中通常是套接字),当其中任何一个文件描述符准备好进行读、写或有异常事件发生时,select
函数会返回,程序可以据此对准备好的文件描述符进行相应操作。
-
关键代码片段及解释:
#include <iostream>
#include <winsock2.h> // Windows下的套接字库,Linux下用<sys/socket.h>等
#include <vector>
#pragma comment(lib, "ws2_32.lib") // Windows下链接ws2_32库
int main() {
// 初始化Winsock(Windows下),Linux下无需此步骤
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建监听套接字
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET) {
std::cerr << "Failed to create socket" << std::endl;
return 1;
}
// 绑定地址和端口
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(12345); // 假设端口为12345
if (bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Failed to bind socket" << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
// 开始监听
if (listen(listenSocket, 5) == SOCKET_ERROR) {
std::cerr << "Failed to listen" << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
fd_set readFds;
FD_ZERO(&readFds);
FD_SET(listenSocket, &readFds);
std::vector<SOCKET> clientSockets;
while (true) {
fd_set tempFds = readFds;
int activity = select(0, &tempFds, nullptr, nullptr, nullptr);
if (activity == SOCKET_ERROR) {
std::cerr << "select error" << std::endl;
break;
} else if (activity > 0) {
if (FD_ISSET(listenSocket, &tempFds)) {
// 有新的客户端连接
SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
if (clientSocket != INVALID_SOCKET) {
std::cout << "New client connected" << std::endl;
clientSockets.push_back(clientSocket);
FD_SET(clientSocket, &readFds);
}
}
for (auto it = clientSockets.begin(); it != clientSockets.end(); ) {
SOCKET clientSocket = *it;
if (FD_ISSET(clientSocket, &tempFds)) {
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead <= 0) {
// 客户端关闭连接
std::cout << "Client disconnected" << std::endl;
closesocket(clientSocket);
FD_CLR(clientSocket, &readFds);
it = clientSockets.erase(it);
} else {
buffer[bytesRead] = '\0';
std::cout << "Received from client: " << buffer << std::endl;
// 处理请求,这里简单回显
send(clientSocket, buffer, bytesRead, 0);
++it;
}
} else {
++it;
}
}
}
}
// 清理资源
for (SOCKET clientSocket : clientSockets) {
closesocket(clientSocket);
}
closesocket(listenSocket);
WSACleanup();
return 0;
}
-
初始化部分:
- 在Windows下,通过
WSAStartup
初始化Winsock库。 - 创建监听套接字
listenSocket
,指定地址族为AF_INET
(IPv4),套接字类型为SOCK_STREAM
(TCP)。 - 绑定套接字到指定的地址和端口。
- 开始监听,设置最大挂起连接数为5。
- 在Windows下,通过
-
select
循环部分:- 初始化一个
fd_set
类型的readFds
集合,并将监听套接字listenSocket
添加到该集合中。 - 在
while (true)
循环中,首先复制readFds
到tempFds
,这是因为select
函数会修改传入的fd_set
集合。 - 调用
select
函数,第一个参数为0(在Windows下无意义,在Linux下通常设置为最大文件描述符加1),第二个参数为tempFds
用于检查可读事件,第三、四个参数为nullptr
表示不关心可写和异常事件,最后一个参数为nullptr
表示阻塞等待直到有事件发生。 - 如果
select
返回错误(activity == SOCKET_ERROR
),输出错误信息并退出循环。 - 如果
select
返回有活动(activity > 0
),首先检查监听套接字是否有可读事件(FD_ISSET(listenSocket, &tempFds)
),如果有则表示有新的客户端连接,调用accept
接受连接,并将新的客户端套接字添加到clientSockets
向量和readFds
集合中。 - 遍历
clientSockets
向量,检查每个客户端套接字是否有可读事件。如果有,接收客户端发送的数据,根据接收结果处理客户端连接(关闭或处理请求并回显)。
- 初始化一个
-
清理部分:
- 关闭所有客户端套接字和监听套接字,并在Windows下调用
WSACleanup
清理Winsock库资源。
- 关闭所有客户端套接字和监听套接字,并在Windows下调用