config = Config::getInstance(); $this->host = $host; $this->port = $port; $this->maxConnections = $maxConnections; // 初始化日志记录器 $logPath = $this->config->get('log.path', '../logs/'); $logLevel = $this->config->get('log.level', 'info'); $this->logger = new Logger($logPath, $logLevel); } /** * 启动SMTP服务器 * @return bool 是否启动成功 */ public function start() { // 创建socket $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($this->socket === false) { $this->logger->error("Failed to create socket: " . socket_strerror(socket_last_error())); return false; } // 设置socket选项 socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); // 绑定地址和端口 if (!socket_bind($this->socket, $this->host, $this->port)) { $this->logger->error("Failed to bind socket: " . socket_strerror(socket_last_error($this->socket))); socket_close($this->socket); return false; } // 开始监听,提高监听队列长度 $backlog = $this->config->get('smtp.backlog', 50); if (!socket_listen($this->socket, $backlog)) { $this->logger->error("Failed to listen on socket: " . socket_strerror(socket_last_error($this->socket))); socket_close($this->socket); return false; } $this->logger->info("SMTP server started on {host}:{port}", ['host' => $this->host, 'port' => $this->port]); // 主循环,处理连接 while (true) { // 处理客户端连接和请求 $this->handleConnections(); } return true; } /** * 停止SMTP服务器 */ public function stop() { // 关闭所有客户端连接 foreach ($this->connections as $connection) { socket_close($connection['socket']); } // 关闭服务器socket if ($this->socket) { socket_close($this->socket); } $this->logger->info("SMTP server stopped"); } /** * 处理客户端请求 */ private function handleConnections() { // 检查是否有客户端数据可读 $readSockets = array_column($this->connections, 'socket'); $readSockets[] = $this->socket; $writeSockets = null; $exceptSockets = null; $activity = socket_select($readSockets, $writeSockets, $exceptSockets, 1); if ($activity === false) { $this->logger->error("Socket select error: " . socket_strerror(socket_last_error())); return; } // 处理可读的socket foreach ($readSockets as $socket) { // 如果是服务器socket,接受新连接 if ($socket === $this->socket) { $this->acceptNewConnection(); } else { // 处理客户端请求 $this->handleClientRequest($socket); } } // 清理超时连接 $this->cleanupTimeoutConnections(); } /** * 接受新连接 */ private function acceptNewConnection() { // 检查连接数是否超过最大值 if (count($this->connections) >= $this->maxConnections) { $this->logger->warning("Maximum connections reached: {max}", ['max' => $this->maxConnections]); return; } // 接受新连接 $clientSocket = socket_accept($this->socket); if ($clientSocket === false) { $this->logger->error("Failed to accept connection: " . socket_strerror(socket_last_error($this->socket))); return; } // 获取客户端信息 socket_getpeername($clientSocket, $clientIp, $clientPort); $this->logger->info("New connection from {ip}:{port}", ['ip' => $clientIp, 'port' => $clientPort]); // 添加到连接列表 $connectionId = uniqid(); $socketKey = (int)$clientSocket; $this->connections[$connectionId] = [ 'socket' => $clientSocket, 'ip' => $clientIp, 'port' => $clientPort, 'handler' => new SmtpHandler($clientSocket, $clientIp, $this->logger), 'lastActivity' => time() ]; // 维护socket到connectionId的映射 static $socketToConnection = []; $socketToConnection[$socketKey] = $connectionId; // 发送欢迎消息 $welcomeMsg = "220 {domain} ESMTP Service Ready\r\n"; socket_write($clientSocket, $welcomeMsg, strlen($welcomeMsg)); } /** * 处理客户端请求 * @param resource $socket 客户端socket */ private function handleClientRequest($socket) { // 优化:使用socket资源作为键直接查找 // 在acceptNewConnection中维护socket到connectionId的映射 $socketKey = (int)$socket; static $socketToConnection = []; // 初始化映射表(如果为空) if (empty($socketToConnection) && !empty($this->connections)) { foreach ($this->connections as $id => $connection) { $socketToConnection[(int)$connection['socket']] = $id; } } // 查找对应的连接 if (!isset($socketToConnection[$socketKey])) { // 重建映射表并再次查找 $socketToConnection = []; foreach ($this->connections as $id => $connection) { $socketToConnection[(int)$connection['socket']] = $id; } if (!isset($socketToConnection[$socketKey])) { return; } } $connectionId = $socketToConnection[$socketKey]; // 读取客户端数据 $data = socket_read($socket, 1024); if ($data === false) { $this->logger->error("Failed to read from socket: " . socket_strerror(socket_last_error($socket))); $this->closeConnection($connectionId); return; } // 如果客户端关闭连接 if (empty($data)) { $this->logger->info("Connection closed by client: {ip}:{port}", [ 'ip' => $this->connections[$connectionId]['ip'], 'port' => $this->connections[$connectionId]['port'] ]); $this->closeConnection($connectionId); return; } // 更新最后活动时间 $this->connections[$connectionId]['lastActivity'] = time(); // 处理数据 $this->connections[$connectionId]['handler']->handle($data); } /** * 关闭客户端连接 * @param string $connectionId 连接ID */ private function closeConnection($connectionId) { if (isset($this->connections[$connectionId])) { $socket = $this->connections[$connectionId]['socket']; $socketKey = (int)$socket; // 从映射表中移除 static $socketToConnection = []; if (isset($socketToConnection[$socketKey])) { unset($socketToConnection[$socketKey]); } socket_close($socket); unset($this->connections[$connectionId]); } } /** * 清理超时连接 */ private function cleanupTimeoutConnections() { $timeout = 300; // 5分钟超时 $now = time(); foreach ($this->connections as $id => $connection) { if ($now - $connection['lastActivity'] > $timeout) { $this->logger->info("Connection timed out: {ip}:{port}", [ 'ip' => $connection['ip'], 'port' => $connection['port'] ]); $this->closeConnection($id); } } } }