面试题答案
一键面试设计思路
- 多路复用机制选择:
- 在Windows平台,使用
WSAAsyncSelect
模型。它基于消息驱动,通过窗口接收网络事件通知。 - 在Linux平台,使用
epoll
。epoll
具有高效的事件通知机制,支持大量并发连接。
- 在Windows平台,使用
- 连接管理:
- 监听连接:在Windows和Linux上都创建监听套接字,绑定到指定端口并开始监听。
- 新连接处理:当有新连接事件触发(Windows通过
WSAAsyncSelect
消息,Linux通过epoll
事件),接受新连接并将其加入连接管理列表。可以使用一个std::vector
或std::unordered_map
来存储连接套接字和相关信息。
- 数据收发:
- 发送数据:为每个连接维护一个发送缓冲区。当需要发送数据时,将数据拷贝到发送缓冲区,然后根据多路复用机制的事件通知,在套接字可写时将数据发送出去。
- 接收数据:同样为每个连接维护一个接收缓冲区。当有数据可读事件触发,将数据读取到接收缓冲区,之后进行数据解析和处理。
- 异常情况处理:
- 连接断开:无论是Windows还是Linux,当检测到连接断开事件(如
WSAECONNRESET
错误或epoll
返回的事件类型表示连接关闭),从连接管理列表中移除该连接,并释放相关资源。 - 其他错误:对于其他网络错误,如发送或接收数据失败,根据错误码进行相应处理,如记录日志、尝试重连等。
- 连接断开:无论是Windows还是Linux,当检测到连接断开事件(如
主要跨平台代码框架
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#pragma comment(lib, "ws2_32.lib")
// 自定义消息用于WSAAsyncSelect
#define WM_SOCKET WM_USER + 1
// 连接信息结构体
struct Connection {
SOCKET sock;
// 其他连接相关信息,如发送/接收缓冲区
};
// 全局变量
std::unordered_map<SOCKET, Connection> connections;
HWND hwnd;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int main() {
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl;
return 1;
}
// 创建窗口类
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"SocketServer";
RegisterClass(&wc);
// 创建窗口
hwnd = CreateWindow(wc.lpszClassName, L"Socket Server", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, wc.hInstance, nullptr);
if (hwnd == nullptr) {
std::cerr << "CreateWindow failed: " << GetLastError() << std::endl;
WSACleanup();
return 1;
}
// 创建监听套接字
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET) {
std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
inet_pton(AF_INET, "0.0.0.0", &hint.sin_addr);
if (bind(listenSocket, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) {
std::cerr << "bind failed: " << WSAGetLastError() << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
std::cerr << "listen failed: " << WSAGetLastError() << std::endl;
closesocket(listenSocket);
WSACleanup();
return 1;
}
// 关联消息和套接字
WSAAsyncSelect(listenSocket, hwnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
// 消息循环
MSG msg = {0};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 清理资源
for (auto& conn : connections) {
closesocket(conn.first);
}
closesocket(listenSocket);
WSACleanup();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_SOCKET:
if (WSAGETSELECTERROR(lParam)) {
std::cerr << "Socket error: " << WSAGETSELECTERROR(lParam) << std::endl;
SOCKET sock = (SOCKET)wParam;
if (connections.find(sock) != connections.end()) {
closesocket(sock);
connections.erase(sock);
}
}
else {
switch (WSAGETSELECTEVENT(lParam)) {
case FD_ACCEPT:
{
SOCKET listenSocket = (SOCKET)wParam;
SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
if (clientSocket != INVALID_SOCKET) {
Connection newConn;
newConn.sock = clientSocket;
connections[clientSocket] = newConn;
WSAAsyncSelect(clientSocket, hwnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
}
}
break;
case FD_READ:
{
SOCKET clientSocket = (SOCKET)wParam;
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
std::cout << "Received: " << buffer << std::endl;
}
else if (bytesRead == 0) {
std::cout << "Connection closed" << std::endl;
closesocket(clientSocket);
connections.erase(clientSocket);
}
else {
std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
closesocket(clientSocket);
connections.erase(clientSocket);
}
}
break;
case FD_WRITE:
// 发送数据逻辑
break;
case FD_CLOSE:
{
SOCKET clientSocket = (SOCKET)wParam;
std::cout << "Connection closed" << std::endl;
closesocket(clientSocket);
connections.erase(clientSocket);
}
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
#else
#include <iostream>
#include <vector>
#include <unordered_map>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
// 连接信息结构体
struct Connection {
int sock;
// 其他连接相关信息,如发送/接收缓冲区
};
// 全局变量
std::unordered_map<int, Connection> connections;
const int MAX_EVENTS = 10;
void setNonblocking(int sock) {
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
int main() {
// 创建监听套接字
int listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == -1) {
std::cerr << "socket failed" << std::endl;
return 1;
}
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
hint.sin_addr.s_addr = INADDR_ANY;
if (bind(listenSocket, (sockaddr*)&hint, sizeof(hint)) == -1) {
std::cerr << "bind failed" << std::endl;
close(listenSocket);
return 1;
}
if (listen(listenSocket, SOMAXCONN) == -1) {
std::cerr << "listen failed" << std::endl;
close(listenSocket);
return 1;
}
setNonblocking(listenSocket);
int epollFd = epoll_create1(0);
if (epollFd == -1) {
std::cerr << "epoll_create1 failed" << std::endl;
close(listenSocket);
return 1;
}
epoll_event event;
event.data.fd = listenSocket;
event.events = EPOLLIN;
if (epoll_ctl(epollFd, EPOLL_CTL_ADD, listenSocket, &event) == -1) {
std::cerr << "epoll_ctl add listen socket failed" << std::endl;
close(listenSocket);
close(epollFd);
return 1;
}
epoll_event events[MAX_EVENTS];
while (true) {
int numEvents = epoll_wait(epollFd, events, MAX_EVENTS, -1);
if (numEvents == -1) {
std::cerr << "epoll_wait failed" << std::endl;
break;
}
for (int i = 0; i < numEvents; ++i) {
if (events[i].data.fd == listenSocket) {
int clientSocket = accept(listenSocket, nullptr, nullptr);
if (clientSocket != -1) {
setNonblocking(clientSocket);
Connection newConn;
newConn.sock = clientSocket;
connections[clientSocket] = newConn;
event.data.fd = clientSocket;
event.events = EPOLLIN | EPOLLOUT | EPOLLET;
if (epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &event) == -1) {
std::cerr << "epoll_ctl add client socket failed" << std::endl;
close(clientSocket);
}
}
}
else {
int clientSocket = events[i].data.fd;
if (events[i].events & EPOLLIN) {
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
std::cout << "Received: " << buffer << std::endl;
}
else if (bytesRead == 0) {
std::cout << "Connection closed" << std::endl;
close(clientSocket);
connections.erase(clientSocket);
epoll_ctl(epollFd, EPOLL_CTL_DEL, clientSocket, nullptr);
}
else {
std::cerr << "recv failed" << std::endl;
close(clientSocket);
connections.erase(clientSocket);
epoll_ctl(epollFd, EPOLL_CTL_DEL, clientSocket, nullptr);
}
}
if (events[i].events & EPOLLOUT) {
// 发送数据逻辑
}
}
}
}
// 清理资源
for (auto& conn : connections) {
close(conn.first);
}
close(listenSocket);
close(epollFd);
return 0;
}
#endif
上述代码展示了在Windows和Linux平台下,使用同步多路复用技术实现网络服务器的基本框架,涵盖了连接管理、数据收发及异常处理等方面。在实际应用中,还需根据具体需求进一步完善和优化,如缓冲区管理、性能调优等。