如何实现PHP限制单IP请求?完整方法与代码教程
- 云服务器
- 2026-02-10
- 3666
在PHP中限制单IP请求频率是防止滥用和分布攻破的常见策略,以下是几种实现方法,根据需求选择适合的方案:
方法1:基于Session计数(简单计数器)
<?php session_start(); $ip = $_SERVER['REMOTE_ADDR']; $limit = 10; // 允许的最大请求次数 $timeWindow = 60; // 时间窗口(秒) // 初始化计数器 if (!isset($_SESSION['requests'])) { $_SESSION['requests'] = [ 'count' => 1, 'start_time' => time() ]; } else { // 检查时间窗口 if (time() - $_SESSION['requests']['start_time'] > $timeWindow) { // 重置计数器 $_SESSION['requests'] = [ 'count' => 1, 'start_time' => time() ]; } else { // 增加计数 $_SESSION['requests']['count']++; // 检查是否超过限制 if ($_SESSION['requests']['count'] > $limit) { http_response_code(429); // Too Many Requests die("请求过于频繁,请稍后再试"); } } } // 正常处理请求... ?>
缺点:Session基于Cookie,客户端禁用Cookie则失效。
方法2:使用文件存储(无Session依赖)
<?php $ip = $_SERVER['REMOTE_ADDR']; $limit = 10; // 最大请求次数 $timeWindow = 60; // 时间窗口(秒) $logDir = __DIR__ . '/ip_logs/'; // 日志目录 // 确保目录存在 if (!is_dir($logDir)) mkdir($logDir, 0700, true); $logFile = $logDir . md5($ip) . '.log'; // 读取现有记录 if (file_exists($logFile)) { $data = json_decode(file_get_contents($logFile), true); if (time() - $data['start_time'] > $timeWindow) { // 重置计数器 $data = ['count' => 1, 'start_time' => time()]; } else { $data['count']++; if ($data['count'] > $limit) { http_response_code(429); die("请求过多,请等待{$timeWindow}秒"); } } } else { $data = ['count' => 1, 'start_time' => time()]; } // 保存记录 file_put_contents($logFile, json_encode($data)); ?>
缺点:高并发时文件IO可能成为瓶颈。

方法3:使用Redis(高性能推荐)
<?php $ip = $_SERVER['REMOTE_ADDR']; $limit = 10; // 最大请求次数 $timeWindow = 60; // 时间窗口(秒) $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 连接Redis $key = "rate_limit:$ip"; // 使用Redis计数器 $current = $redis->incr($key); if ($current === 1) { // 首次设置时添加过期时间 $redis->expire($key, $timeWindow); } elseif ($current > $limit) { http_response_code(429); die("请求过于频繁"); } // 正常处理请求... ?>
优势:高性能,适合分布式环境。

方法4:.htaccess限制(Apache专用)
在项目根目录创建.htaccess文件:

<IfModule mod_rewrite.c> RewriteEngine on RewriteMap access counter:/path/to/access_map.txt RewriteCond ${access:%{REMOTE_ADDR}} >10 RewriteRule ^ - [F,L] </IfModule>
需配合脚本更新计数器,适用于熟悉Apache配置的场景。
关键注意事项:
- 获取真实IP:如果使用代理(如CDN),需通过HTTP_X_FORWARDED_FOR获取真实IP: $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
- 分布式环境:使用Redis等共享存储,避免单点失效。
- 突发请求:考虑使用令牌桶算法(如Redis + Lua实现)。
- 白名单机制:对可信IP免除限制: $whitelist = ['192.168.1.1', '10.0.0.1']; if (in_array($ip, $whitelist)) return;
完整Redis令牌桶示例
<?php $ip = $_SERVER['REMOTE_ADDR']; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $key = "token_bucket:$ip"; $capacity = 20; // 桶容量 $refillRate = 10; // 每秒补充10个令牌 // 使用Lua脚本保证原子性 $lua = <<<LUA local key = KEYS[1] local now = tonumber(ARGV[1]) local capacity = tonumber(ARGV[2]) local refillRate = tonumber(ARGV[3]) local lastTime = tonumber(redis.call('hget', key, 'time') or now) local tokens = tonumber(redis.call('hget', key, 'tokens') or capacity) -- 计算补充的令牌 local refill = math.floor((now - lastTime) * refillRate) tokens = math.min(capacity, tokens + refill) -- 检查令牌是否足够 if tokens < 1 then return 0 end -- 消耗令牌并更新 redis.call('hmset', key, 'tokens', tokens-1, 'time', now) redis.call('expire', key, math.ceil(capacity/refillRate)*2) return 1 LUA; $result = $redis->eval($lua, [$key, time(), $capacity, $refillRate], 1); if (!$result) { http_response_code(429); die("请求超出速率限制"); } ?>
根据业务需求选择方案,对于高并发场景,Redis方案是最佳选择,结合Lua脚本可保证原子性和精确控制。