你不知道的JavaScript--Item36 客户端检测技术

本文介绍了JavaScript客户端检测技术,包括如何通过JavaScript判断客户端环境如浏览器和操作系统等,并探讨了能力检测、怪癖检测和用户代理检测等方法。

JavaScript 客户端检测

JavaScript 客户端检测是指通过javascript,判断客户端的环境,诸如使用的浏览器和操作系统。客户端检测是一种行之有效的开发策略。但不到万不得已,就不要使用客户端检测。先设计通用的方案,然后根据浏览器之间的差异和各自的怪癖quirky,再使用特定于浏览器的技术增强该方案。

user-agent字符串检测法是最原始的浏览器检测方式,每个访问网站的程序都必须提供一个user-agent字符来向服务器确定他的身份。navigator对象包含一些浏览器信息,诸如名称、版本号和平台。

//取得用户代理字符串 并全部小写。
var ua = navigator.userAgent.toLowerCase();
document.write(ua);
if(us.indexOf('msie')>0){
    alert('IE');
}else{
    alert('W3C');
}

一般涉及到的检测类型包括下面这几种:

1、识别呈现引擎
引擎主要包含四种:IE、Gecko、WebKit、Opera

2、识别浏览器
主流浏览器包含四种:IE、Chrome、Firefox、Opera

3、识别平台
主流平台包含三类:Windows、Mac、Unix

4、识别Windows操作系统
Windows操作系统包含:Windows 98、Window NT、Window XP、Window Vista、Windows 7…

5、识别移动设备
主流的移动设备包含三类:iPhone、iPod、Anroid、Nokia

1 能力检测

能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力,知道浏览器能干什么不能干什么。能力检测的基本模式如下:

if(object.propertyInQuestion){
   //使用object.propertyInQuestion
}

//判断document是否支持getelementById方法
if(document.getElementById){
   return document.getElementById();
}

两个重要的概念:

  • 第一个概念是先检测达成目的的最常用的特性。先检测最常用的特性,可以保证代码最优化,因为在多数情况下都可以避免测试多个条件。
  • 第二个概念是必须测试实际要用到的特性。一个特性存在,不一定意味着另一个特性也存在。

可靠的能力检测

如果判断某个对象是否支持排序方法,可以使用操作符typeof

   //只检测了是否存在相应的方法
function isSortable(object){
       return !!object.sort;  
}
var result= isSortable({sort:true});

在可能的情况下,尽量使用typeof进行能力检测。

function isSortable(object){
       return  typeof object.sort == "function ";  
}

但是并不是所有浏览器都会返回的值都合理。大部分浏览器在检测到document.createElement存在时,都会返回ture,但是在IE8及之前的IE版本会返回false。

function hasCreateElement(){
       return  typeof document.createElement =="function ";  
}

IE8之前的版本返回false.因为DOM对象是宿主对象,是通过COM而不是JScript实现的,document.createElement()函数是一个COM对象。IE9已更正。

 var xhr=new ActiveXObject("Microsoft.XMLHttp");
 if(xhr.open){ //error
     //TO-DO
 }

直接把函数属性访问会导致JS错误。typeof xhr.open 返回“unknown”

在浏览器环境测试任何对象的某个特性是否存在使用如下函数:

   //Peter Michaux
   function isHostMethod(object, property) {
       var t = typeof object[property];
       return t == 'function' ||
           (!!(t == 'object' && object[property])) ||
               t == 'unknown';
   }

   result = isHostMethod(xhr, "open"); //true
   result = isHostMethod(xhr, "foo"); //false

检测某个或某几个特性并不能够确定浏览器。实际上,根据浏览器不同将能力组合起来是更可取的方式。如果知道自己的应用程序需要使用某些特定的浏览器特性,那么最好是一次性检测所有相关特性,而不要分别检测。

//确定浏览器是否支持 Netscape 风格的插件
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length );

//确定浏览器是否具有 DOM1 级规定的能力
var hasDOM1 = !!(document.getElementById && document.createElement 
                && document.getElementByTagName);

2. 怪癖检测 Quirkys Detection

目标识别浏览器特殊的行为,与能力检测不同,是想知道浏览器存在的什么缺陷.运行一小段代码,以确定某一特性不能正常工作.

“怪癖”都是个别浏览器所独有的,而且通常被归为 bug。由于检测“怪癖”涉及运行代码,因此建议仅检测那些对你有直接影响的“怪癖”,而且最好在脚本一开始就执行此类检测,以便尽早解决问题。

例如,IE中存在的一个 bug ,即如果某个实例属性与标记为 [[DontEnum]] 的某个原型属性同名,那么该实例属性将不会出现在 fon-in 循环当中。可以使用如下代码来检测这种“怪癖”:

var hasDontEnumQuirk = function(){
    var o = { toString : function(){}};

    for( var prop in o){
        if( prop == "toString"){
            return false;
        }
    }
    return true;
}();

3. 用户代理检测

  争议最大的一种客户端检测技术。用户代理检测通过检测用户代理字符串来确定实际使用的浏览器。在每一次HTTP请求过程中,用户代理字符串是作为响应首部发送的,而该字符串可以通过 JavaScript 的 navigator.userAgent 属性访问。在服务器端,通过检测用户代理字符串来确定用户使用的浏览器是一种常用而且广为接受的做法。而在客户端,用户代理检测一般被当作一种万不得已才使用的做法,其优先级排在能力检测和怪癖检测之后。 

3.1 识别呈现引擎

  确切的知道浏览器的名字和版本号不如确定他们使用的是什么呈现引擎。如果Firefox、Camino 和 Netsacpe 都使用相同版本的 Gecko ,那么他们一定支持相同的特性。类似的,不管是什么浏览器,只要它跟 Safari 3 使用的是同一个版本的 WebKit,那么该浏览器也就跟 Safari 3 具备同样的功能。因此,我们需要编写的脚本将主要检测五大呈现引擎: IE、Gecko、WebKit、KHTML 和 Opera1。

  注1:现在Opera 和 Chrome将采用基于WebKit开发自主渲染引擎Blink。

  模块增强模式封装检测脚本。

var client = function(){

    //呈现引擎
    var engine = {
        ie : 0,
        gecko : 0,
        webkit : 0,
        khtml : 0,
        opera : 0,

        //具体的版本号
        ver : null
    };

    //再次检测呈现引擎、平台和设备
    return {
        engine : engine
    };
}();

  如果检测到那个呈现引擎,就以浮点数值形式将该引擎的版本号写入相应的属性。而呈现引擎的完整版本是一个字符串,则被写入ver属性。

if(client.engine.ie){   //如果是IE ,engine.ie的值应大于0
    //针对IE
} else if (client.engine.gecko > 1.5) {
    if(client.engine.ver == "1.8.1"){
        //to-do
    }

}

  检测到一个呈现引擎后,其 client.engine 中对应的属性将被设置成一个大于 0 的值,该值可以转换成布尔值的 true。这样就可以在 if 语句中检测相应的属性,以确定当前使用的呈现引擎,连具体的版本号都不需要考虑。鉴于每个属性都包含一个浮点数值,因此有可能丢失某些版本信息。例如,将字符串”1.8.1”传入 parseFloat() 后悔得到数值 1.8。不过,在必要的时候,可以检测 ver 属性,该属性中保存着完整的版本信息。

  正确的识别呈现引擎关键是检测顺序要正确。

  首先应该检测的是 Opera,因为它的用户代理字符串可能完全模仿其他浏览器。

//Opera 5起支持window.opera对象
if ( window.opera ){
    engine.ver = window.opera.version();  //Opera 7.6起返回浏览器版本字符串
    engine.opera = parseFloat( engine.ver );
}

  其次检测 WebKit.因为WebKit的用户代理字符串包含“Gecko”和“KHTML”,若先检测他们,可能会出现错误的结论。

//AppleWebKit独一无二
var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
}

  仅供参考,不保证精确。

  接下来测试 KHTML。KHTML的用户代理字符串包含“Gecko”,所以在排除KHTML之前是无法正确检测基于Gecko的浏览器。Konqueror 3.1及更早的版本不包含KHTML的版本,要使用Konqueror的版本来替代。

var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
}

  下面我们就可以准确的检测Gecko。Gecko的版本号位于字符串“rv:”与一个闭括号之间,还要查找“Gecko/”后的8个数字。

//Windows XP 下的 Firefox 2.0.0.11 :
Mbzilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.

var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
} else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);
} 

最后是IE.IE版本号位于MSIE的后面。

var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
} else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);
} else if (/MSIE ([^;]+)/.test(ua)){
    engine.ver = browser.ver = RegExp["$1"];
    engine.ie = browser.ie = parseFloat(engine.ver);
}

3.2.识别浏览器

  识别了浏览器的呈现引擎就足以为我们采取正确的操作提供依据了。可是,只有呈现引擎还不能够说明存在所需的JavaScript功能。相同内核的浏览器的JavaScript有可能引擎不一样:safari 和chrome。

var client = function(){
    //呈现引擎

    var engine = {
        ie : 0,
        gecko : 0,
        webkit : 0,
        khtml : 0,
        opera : 0,

        //具体的版本号
        ver : null
    };

    var browser = {
        //浏览器
        ie : 0,
        firefox : 0,
        konq : 0,
        opera : 0,
        chrome : 0,
        safari : 0,

        //具体的版本
        ver : null
    };

    //再次检测呈现引擎、平台和设备

    return {
        engine : engine,
        browser : browser
    };
}();

  由于大多数浏览器与其呈现引擎密切相关,所以下面示例中检测浏览器的代码与呈现引擎的代码是混合在一起的

//检测呈现引擎及浏览器
var ua = navigator.userAgent;

if ( window.opera ){

    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );

} else if ( /AppleWebKit\/(\S+)/.test(ua)){

    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);

    //确定是Chrome 还是 Safari
    if ( /Chrome\/(\S+)/.test(ua)){
        browser.ver = RegExp["$1"];
        browser.chrome = parseFloat(browser.ver);
    } else if ( /Version\/(S+)/test(ua)){
        browser.ver = RegExp["$1"];
        borwser.safari = parseFloat(browser.ver);
    } else {
        //近似的确定版本号
        var safariVersion = 1;

        if (engine.webkit < 100 ){
            safariVersion = 1;
        } else if (engine.webkit < 312){
            safariVersoin = 1.2;
        } else if (engine.webkit < 412){
            safariVersion = 1.3;
        } else {
            safariVersion = 2;
        }

        browser.safari = browser.ver = safariVersion;
    }

} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
} else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);

    //确定不是Firefox
    if( /Firefox\/(\S+)/.test(ua)){
        browser.ver = RegExp["$1"];
        browser.firefox = parseFloat(browser.ver);
    }

} else if (/MSIE ([^;]+)/.test(ua)){
    engine.ver = browser.ver = RegExp["$1"];
    engine.ie = browser.ie = parseFloat(engine.ver);
}

我们就可以这样判断了:

if(client.engine.webkit){   
    if (client.browser.chrome) {

    }else if (client.browser.safari){

    }
} else if (client.engine.gecko) {
    if (client.browser.firefox) {

    }else{
    
    }
}

3.3.识别平台

  很多时候,只要知道呈现引擎就可以编写出适合的代码了,但在有些情况下,平台可能是必须关注的问题。三大主流平台:Windows、Mac、Unix(Linux)为了检测这些平台,需要再添加一个对象:

var client = function(){
     //呈现引擎

     var engine = {
         ie : 0,
         gecko : 0,
         webkit : 0,
         khtml : 0,
         opera : 0,

         //具体的版本号
         ver : null
     };

     var browser = {
         //浏览器
         ie : 0,
         firefox : 0,
         konq : 0,
         opera : 0,
         chrome : 0,
         safari : 0,

         //具体的版本
         ver : null
     };

     var system = {
         win : false,
         mac : false,
         xll : false
     };

     //再次检测呈现引擎、平台和设备

     return {
         engine : engine,
         browser : browser,
         system : system
     };
 }();

 //navigator.platform值Win32 、Win64、MacPPC、MacIntel、X11、Linux i686
 var p = navigator.platform;
 system.win = p.indexOf("Win") == 0;
 systemp.mac = p.indexOf("Mac") == 0;
 system.xll = (p.indexOf("Xll")) == 1 || (p.indexOf("Linux") == 0);

参考:JavaScript客户端检测技术

<think>好的,用户之前问过如何实现单机游戏的排行榜,但需要实时联机。我给出了一个分阶段的方案,包括数据采集、传输、排行榜生成和反作弊机制。现在用户再次提问,希望手把手教他做,但暂时需要反作弊和检测机制。这意味着用户可能是一个刚开始接触游戏开发的开发者,或者是一个独立开发者,想要快速实现一个基本的排行榜功能,而暂时需要复杂的安全措施。 首先,我需要明确用户的需求变化:之前的需求中包含了反作弊机制,但现在用户暂时需要这部分。这可能是因为用户想先搭建基础功能,后续再考虑安全问题,或者他的项目目前处于原型阶段,反作弊是优先项。因此,我需要调整之前的方案,去掉反作弊相关的步骤,专注于数据采集、传输和排行榜生成。 接下来,我需要考虑如何简化之前的方案,使其更易于实现。用户需要的是手把手指导,所以步骤要详细,代码示例要具体,可能还需要选择适合初学者的技术栈。例如,使用Python作为后端,因为Python语法简单,库丰富,适合快速开发。数据库方面可以选择SQLite作为本地存储,用Redis来维护实时排行榜,因为Redis的ZSET结构非常适合排行榜的实现。 数据采集部分,之前提到了监控文件变化、OCR识别和内存扫描。但用户现在可能只需要最简单的方法,比如监控游戏生成的结算文件。我需要给出具体的代码示例,比如使用Python的watchdog库来监控文件变化,并自动上传数据。同时,需要说明如何配置监控路径,以及如何处理数据上传。 数据传输部分,需要提供一个简单的HTTP API,使用Flask或FastAPI来创建后端服务。代码示例要展示如何接收数据并存储到数据库。考虑到用户可能对后端开发熟悉,需要详细说明如何设置路由、处理请求以及连接数据库。 排行榜生成部分,使用Redis的ZSET结构,展示如何添加分数和获取排行榜。同时,提供一个简单的前端页面来显示排行榜,这样用户可以看到实际效果。可能需要用到HTML和JavaScript,但保持简单,避免复杂的框架。 部署方面,建议使用Docker来容器化服务,这样用户可以在本地运行,需要复杂的服务器配置。同时,提供docker-compose文件示例,方便用户一键启动Redis和Python后端。 在教程的结构上,需要分步骤进行,每一步都有明确的代码和说明,确保用户能够跟随。可能还需要提示常见的错误和解决方法,比如文件权限问题、依赖安装等。 最后,考虑到用户可能想进一步扩展功能,可以给出后续的建议,比如增加用户系统、数据加密等,但当前保持简单,专注于核心功能的实现。 总结来说,需要将之前的方案简化,去除反作弊部分,提供详细的代码和步骤,使用易上手的技术栈,确保用户能够顺利实现一个基础的排行榜系统。</think>以下是简化版实时排行榜实现教程(含反作弊),使用Python+SQLite+Redis快速搭建: --- ### **第一步:搭建本地数据采集系统** #### 1. 创建游戏结算文件监听器 ```python # monitor.py import time import json from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class GameHandler(FileSystemEventHandler): def on_modified(self, event): if "result.json" in event.src_path: print("检测到新游戏记录") with open(event.src_path) as f: data = json.load(f) # 示例数据结构: # {"player": "user123", "score": 95, "time": 120.5} save_to_db(data) # 存储到本地数据库 def save_to_db(data): import sqlite3 conn = sqlite3.connect('game_records.db') c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY AUTOINCREMENT, player TEXT, score REAL, time REAL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''') c.execute("INSERT INTO records (player, score, time) VALUES (?, ?, ?)", (data['player'], data['score'], data['time'])) conn.commit() conn.close() if __name__ == "__main__": path = "/path/to/your/game/save/folder" # 替换为实际路径 observer = Observer() observer.schedule(GameHandler(), path, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() ``` --- ### **第二步:构建数据传输服务** #### 1. 创建简易HTTP服务器(FastAPI) ```python # server.py from fastapi import FastAPI import sqlite3 import redis app = FastAPI() r = redis.Redis(host='localhost', port=6379, db=0) @app.get("/upload") async def upload_data(): conn = sqlite3.connect('game_records.db') c = conn.cursor() # 获取未上传的记录 c.execute("SELECT * FROM records WHERE uploaded = 0") records = c.fetchall() for record in records: player = record[1] score = record[2] # 更新Redis排行榜 r.zadd("leaderboard", {player: score}) # 标记为已上传 c.execute("UPDATE records SET uploaded = 1 WHERE id = ?", (record[0],)) conn.commit() conn.close() return {"status": "success", "count": len(records)} @app.get("/leaderboard") async def get_leaderboard(top: int = 10): return r.zrevrange("leaderboard", 0, top-1, withscores=True) ``` --- ### **第三步:部署本地运行环境** #### 1. 安装依赖 ```bash pip install fastapi uvicorn redis watchdog ``` #### 2. 启动服务 ```bash # 启动数据监听器(新终端) python monitor.py # 启动API服务(新终端) uvicorn server:app --reload ``` #### 3. 验证系统 ```bash # 生成测试数据(模拟游戏保存) echo '{"player": "test1", "score": 95, "time": 120}' > /path/to/your/game/save/folder/result.json # 手动触发上传 curl http://localhost:8000/upload # 查看排行榜 curl http://localhost:8000/leaderboard ``` --- ### **第四步:实现前端展示** ```html <!-- leaderboard.html --> <!DOCTYPE html> <html> <head> <title>实时排行榜</title> <meta http-equiv="refresh" content="5"> <!-- 每5秒自动刷新 --> </head> <body> <h1>游戏排行榜</h1> <div id="list"> <% items.forEach(function(item){ %> <div class="item"> <span class="rank"><%= item.rank %></span> <span class="name"><%= item.player %></span> <span class="score"><%= item.score %></span> </div> <% }); %> </div> <script> // 使用AJAX获取实时数据(可选) fetch('http://localhost:8000/leaderboard?top=10') .then(response => response.json()) .then(data => { let html = ''; data.forEach(([player, score], index) => { html += `<div>第${index+1}名 ${player}: ${score}</div>`; }); document.getElementById('list').innerHTML = html; }); </script> </body> </html> ``` --- ### **系统运行示意图** ``` 游戏客户端 → 生成result.json → 监听程序 → 写入SQLite → 定时上传 → Redis排序 → 网页展示 ``` **关键调试技巧**: 1. 使用`redis-cli`直接查看数据: ```bash redis-cli 127.0.0.1:6379> ZREVRANGE leaderboard 0 9 WITHSCORES ``` 2. 手动清理数据库: ```bash rm game_records.db # 删除本地记录 redis-cli FLUSHDB # 清空排行榜 ``` **下一步扩展建议**: 1. 增加玩家ID系统(可先用设备IMEI/MAC地址生成唯一标识) 2. 添加时间维度排行(日榜/周榜/总榜) 3. 实现数据加密传输(使用AES加密上传内容)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值