函数如下:

/**
 * 获取客户端IP
 * @return string
 */
function getClientIp() {
    $keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
    foreach ($keys as $key) {
        if (isset($_SERVER[$key])) {
            $ip = array_filter(explode(', ', $_SERVER[$key]));
            $ip = filter_var(end($ip), FILTER_VALIDATE_IP);
            if ($ip) {
                return $ip;
            }
        }
    }
    return '';
}

分析:

按顺序依次判断$_SERVER['HTTP_X_FORWARDED_FOR']、$_SERVER['HTTP_CLIENT_IP']、$_SERVER['REMOTE_ADDR']这三个变量,如果有值就马上返回。

翻开PHP手册,查看$_SERVER数组的解释,你会发现,手册里居然没有HTTP_X_FORWARDED_FOR、HTTP_CLIENT_IP这两个元素,只有REMOTE_ADDR的解释。

首先,$_SERVER数组以HTTP_开头的元素,是来自客户端的header头信息,也就是说,它们是客户端传给服务器的。因此,这些值是可以伪造的,是不安全的!但是,这并不代表它们就没有用处。

解释如下:

  • HTTP_X_FORWARDED_FOR 是有标准定义,用来识别经过HTTP代理后的客户端IP地址,多级代理的话IP以 英文逗号+空格 分隔,格式:clientip, proxy1, proxy2。详细解释见 http://zh.wikipedia.org/wiki/X-Forwarded-For
  • HTTP_CLIENT_IP 未成标准,不一定服务器都实现了。
  • REMOTE_ADDR 是最后一个跟你的服务器握手的IP,可能是用户的代理服务器,也可能是你自己的反向代理。

如果你的服务器是直接暴露在公网,允许用户直连,那单独用$_SERVER['REMOTE_ADDR']就可以获取到用户的真实IP。

如果你的服务器是经过nginx反向代理,那就要用$_SERVER['HTTP_X_FORWARDED_FOR']判断。最后一个地址是最后一个代理服务器的IP,这通常是一个比较可靠的信息来源。但前提是nginx有设置:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;