Mac下Android studio搭建Android开发环境【新手】

mikel阅读(1495)

上学期用的还是windows,这学期新入手了mac,突然也想装个Android studio来玩玩。安装过程中出现了问题,记录如下。

 

先是装安卓studio的下载链接,不推荐去官网装,这里提供一个挺好的网站

http://tools.Androidstudio.org/(就如平时安装应用程序一样,这里不再赘述)

接着下载Mac下使用的JDK

http://www.oracle.com/technetwork/java/javase/downloads/index.html

点击download按钮

选择Accept License Agreement.Mac OS X x64(你看到这篇文章的时候可能更新了版本,不是8u73了,不要害怕,下载就是。

 

这个安装向导也十分的人性化,只要一直点继续继续完成就好了。

接着你直接打开安卓studio,会发现,无法新建程序。(前面有一些欢迎语,还要你选择OK不OK,如果你英语不好,可以忽略掉)

这时候,去终端查看JDK是否安装完毕。

输入         java -version 显示:
java version “1.8.0_73”
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

可见我的JDK安装成功,并且版本号是1.8.0_73

那么可以去应用程序里,找到Android studio,右键查看包,/contents/info.plist找到这个文件以后,右键打开方式选择文本编辑器。

搜索一下,找到  <key>JVMVersion</key>
把 <string>1.6*、1.7+</string>里面改成你的JDK版本号。

改完之后,再去启动Android studio,你会发现,它开始自动查找配置JDK了。这个过程有些慢,泡杯咖啡吃个泡面什么的等待下就好了_(:з」∠)_

期间出现了一个错误,我retry了一下,就安装成功了。

upstream sent too big header while reading response header from upstream

mikel阅读(1056)

来源: upstream sent too big header while reading response header from upstream_·L,`M_新浪博客

年底了事情真多,club服务器有问必答 提交页面 提交出这个问题

The page you are looking for is temporarily unavailable.Please try again later.

一看就知道是nginx的请求的错误,,惆怅啊。。

就开启了 错误日志查看。。。

tail -f error.log
就具体错误是 :

upstream sent too big header while reading response header from upstream

我们是nginx反向代理
proxy是nginx作为client转发时使用的,如果header过大,超出了默认的1k,就会引发上述的upstream sent too big header (说白了就是nginx把外部请求给后端apache ,apache返回的header  太大nginx处理不过来就导致了。

server {
       listen       80;
       server_name  *.xywy.com ;

       large_client_header_buffers 4 16k;

       #charset koi8-r;

       # access_log off;

       location / {

#添加这3行 ,
               proxy_buffer_size 64k;
               proxy_buffers   32 32k;
               proxy_busy_buffers_size 128k;

          proxy_set_header Host $host;
          proxy_set_header X-Real-IP       $remote_addr;
          proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;

          set $baiduspider ”;

          if ( $http_user_agent ~ Baiduspider) {
             set $baiduspider Baidu;
         }

…………

如果是 nginx+PHPcgi 就该 

fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on
011/01/07 11:12:57 [error] 10770#0: *38585340 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 116.22.131.154, server: *.xywy.com, request: “GET /ysmp/index.php?did=124994 HTTP/1.0”, upstream: “http://127.0.0.1:8080/ysmp/index.php?did=124994”, host: “xywy.yn16.com”

后来原来那错误没了出了新错误了 upstream timed out 超时?

server {
       listen       80;
       server_name  *.xywy.com ;
large_client_header_buffers 4 16k;
       client_max_body_size 300m;
       client_body_buffer_size 128k;
       proxy_connect_timeout 600;
       proxy_read_timeout 600;
       proxy_send_timeout 600;
               proxy_buffer_size 64k;
               proxy_buffers   4 32k;
               proxy_busy_buffers_size 64k;
               proxy_temp_file_write_size 64k;
       #charset koi8-r;

       # access_log off;

后来参数我又改了下 就好了。。。

可以参考:

http://wiki.nginx.org/NginxHttpProxyModule

http://blog.sina.com.cn/s/blog_5dc960cd0100i4mt.html

使用数字签名实现数据库记录防篡改(Java实现) - 小小Prince - 博客园

mikel阅读(1485)

来源: 使用数字签名实现数据库记录防篡改(Java实现) – 小小Prince – 博客园

本文大纲

一、提出问题

二、数字签名

三、实现步骤

四、参考代码

五、后记

六、参考资料

 

一、提出问题

最近在做一个项目,需要对一个现成的产品的数据库进行操作,增加额外的功能。为此,需要对该产品对数据库有什么操作进行研究(至于怎么监控一个产品的操作会引发什么数据库操作,以后会详细解说)。本来已经对数据库的操作了如指掌的,无意中发现数据库表里的每条记录都会有这样一个字段:

这感觉不妙了,字段名叫signature,顾名思义,就是签名的意思呀。难道数据库表中的每条记录都会有签名?也就是说如果我不能正确生成签名,而直接改记录中的字段,会被程序认为非法篡改了数据?那以后我的产品设计,是否也可采用这种方式来对每条记录做签名,防止数据被非法篡改,例如日志表中的数据?抱着这一发现以及这一连串的问题,我进行了以下的研究。在这里我将研究整理了一下,分享给大家。

 

二、数字签名

要解决上面的问题,首先就要对最基础的知识进行了解。这里最基础的知识,无疑就是什么是数字签名了。很多同学可能对这个名词并不陌生,但估计大多数人都是对其一知半解,会把散列、非对称加密、数字签名、数字证书的几个概念混为一谈,造成混乱。所以我先对相关概念进行解释,再往下讲。如果很熟悉这方面的同学可以跳过此部分,但对于绝大多数同学来说,不建议这样做。基础没搭好,直接看怎么实现,换了个说法又不知道怎么去做了。要想提高个人能力,做到举一反三很重要。

言归正传,先对跟数字签名有关的密码学知识简单说一下。加密方法分两大类,分别是单钥加密和双钥加密,数字签名涉及到双钥加密。关于双钥加密,主要涉及到以下几个要点[1]

  • 双钥加密的密钥有两把,一把是公开的公钥,一把是不公开的私钥
  • 公钥和私钥是一一对应的关系,有一把公钥就必然有一把与之对应的、独一无二的私钥,反之亦成立。
  • 所有的(公钥, 私钥)对都是不同的。
  • 用公钥可以解开私钥加密的信息,反之亦成立。
  • 同时生成公钥和私钥应该相对比较容易,但是从公钥推算出私钥,应该是很困难或者是不可能的。
  • 在双钥体系中,公钥用来加密信息,私钥用来数字签名。
  • 还有一点关于数字证书的。因为任何人都可以生成自己的公钥私钥对,所以为了防止有人散布伪造的骗取信任,就需要一个可靠的第三方机构来生成经过认证的公钥、私钥对。简单来说,数字证书是权威的第三方机构颁发的,用来认证某对公钥私钥的证书,经过这个数字证书认证的公钥私钥,就可以明确属于某人或者某机构,是合法的,可信任的。就如同身份证,是证明你身份的一个证件。所以数字证书跟数字签名是两回事,要分清楚。

数字签名,顾名思义,就类似于一种写在纸上的普通的物理签名,不同的是,数字签名是电子信息化的,采用双钥加密的技术实现,是一种用于鉴别数字信息的方法。处理的过程,简单说就是将文件内容进行hash散列,信息发送者对散列后的字符串使用私钥加密,得到的最终字符串就是签名。然后将得到的签名字符串添加到文件信息的后面一同发送出去。接收者获取到文件信息和签名后,使用公钥对签名进行解密,就得到文件内容加密后的hash散列。此时,他可以对获取到的文件内容做hash散列,与签名中的hash散列进行匹对,从而鉴别出最终获取信息的真伪。主要过程如这四幅图所示[2]

对文件内容进行hash散列,生成摘要

对生成的摘要,使用私钥进行加密,形成签名

将得到的签名,附到文件内容后部,就想到与签名签到文件尾部那样子

使用公钥对签名进行解密,得到摘要,并与获取到的文件内容生成的摘要做对比,以确定是否被篡改

 

想了解更详细的数字证书相关内容,可以访问此地址:http://www.youdzone.com/signature.html。里面解释得很形象,应该一看就明白的了。

 

三、实现步骤

看到这里,开篇提出的问题也就呼之欲出了。没错,就是使用数字签名技术,将数据库中的重要字段进行签名,将签名结果作为记录的一列存在记录中。这样当有人入侵数据库,恶意修改字段,程序读数据时拿签名校验一下,就知道数据是否有被修改过了。

在java.security包中,有很多有用的类,用以进行安全机制的开发。对于要创建数字签名,我们主要用到以下的接口或类:

接口名 描述
PrivateKey A private key
PublicKey A public key

接口

 类名 描述
Signature The Signature class is used to provide applications the functionality of a digital signature algorithm.
KeyPair This class is a simple holder for a key pair (a public key and a private key)
KeyPairGenerator The KeyPairGenerator class is used to generate pairs of public and private keys.

对于接口和类的描述,我直接引用了Oracle上的J2SE 7的API描述[3],就不翻译成中文了,以防词不达意。大家看英文应该能更精确的明白其意思。

利用上述的接口和类,就可以进行数字签名和验证了,下面分三部分进行基本步骤的描述。

第一部分:生成密钥并存储

  1. 生成KeyPairGenerator实例,并调用其genKeyPair()方法生成KeyPair对象。
  2. 利用ObjectOutputStream实例,将KeyPair对象写到文件中,从而把密钥保存到文件中。

第二部分:进行数字签名

  1. 从密钥文件中读取KeyPair对象。
  2. 调用KeyPair对象的getPrivate()和getPublic()方法,分别获取PrivateKey和PublicKey。
  3. 利用密钥的指定算法生成Signature实例,然后利用PrivateKey和文件内容,分别调用其initSign()和update()方法,最后调用sign()方法生成数字签名。

第三部分:进行签名验证

  1. 从密钥文件中读取KeyPair对象。
  2. 调用KeyPair对象的getPrivate()和getPublic()方法,分别获取PrivateKey和PublicKey。
  3. 利用密钥的指定算法生成Signature实例,然后利用PublicKey和文件内容,分别调用其initSign()和update()方法,最后利用数字签名调用verify()方法验证签名。

 

四、参考代码

根据上面的步骤描述,基本可以写出程序来了。下面是参考代码,未必尽善尽美,但是基本功能都体现到了,供你参考。

工程结构:

DataSecurity类:

复制代码
package com.hzj.security;

import java.io.UnsupportedEncodingException;
import java.nio.charset.CharsetEncoder;
import java.security.KeyPair;

import com.hzj.util.StringHelper;

public class DataSecurity {
    private KeyPair keyPair;
    private static final String KEY_FILE = "/ca.key";
    private DataSignaturer dataSignaturer;

    public DataSecurity() {
        try {
            this.keyPair = KeyPairUtil.loadKeyPair(getClass().getResourceAsStream("/ca.key"));
            this.dataSignaturer = new DataSignaturer(this.keyPair.getPublic(), this.keyPair.getPrivate());
        } catch (RuntimeException e) {
            System.out.println("没有找到KeyPair文件[/ca.key]!");
        }
    }

    /**
     * 验证数字签名
     * @param data
     * @param signs
     * @return
     */
    public boolean verifySign(String data, String signs) {
        if ((data == null) || (signs == null)) {
            System.out.println("参数为Null");
        }
        boolean verifyOk = false;
        try {
            verifyOk = this.dataSignaturer.verifySign(data.getBytes("UTF-8"), StringHelper.decryptBASE64(signs));
        } catch (RuntimeException e) {
            System.out.println("fail!data=" + data + ", sign=" + signs + ", exception:" + e.getMessage());
        } catch (UnsupportedEncodingException e) {
            System.out.println("不支持UTF-8字符集");
        } catch (Exception e) {
            System.out.println("Exception:" + e.getMessage());
        }
        if (!verifyOk) {
            System.out.println("fail!data=" + data + ", sign=" + signs + ", verifyOk=false!");
        }
        return verifyOk;
    }
    
    
    /**
     * 生成数字签名
     * @param data
     * @return
     */
    public String sign(String data)
    {
      if (data == null) {
          System.out.println("参数为Null");
      }
      String sign = null;
      try
      {
        sign = StringHelper.encryptBASE64(this.dataSignaturer.sign(data.getBytes("UTF-8")));
      }
      catch (UnsupportedEncodingException e)
      {
          System.out.println("不支持UTF-8字符集");
      }
      catch (Exception e)
      {
          System.out.println(e.getMessage());
      }
      return sign;
    }
 }
复制代码

 

DataSignaturer类:

 

复制代码
package com.hzj.security;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;

public class DataSignaturer {

    private PrivateKey privateKey;
    private PublicKey publicKey;

    public DataSignaturer(PublicKey publicKey, PrivateKey privateKey){
        this.privateKey = privateKey;
        this.publicKey = publicKey;
    }
    
    /**
     * 进行数字签名
     * @param data
     * @return
     */
    public byte[] sign(byte[] data) {
        if (this.privateKey == null) {
            System.out.println("privateKey is null");
            return null;
        }
        Signature signer = null;
        try {
            signer = Signature.getInstance(this.privateKey.getAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e.getMessage());
        }
        try {
            signer.initSign(this.privateKey);
        } catch (InvalidKeyException e) {
            System.out.println(e.getMessage());
        }
        try {
            signer.update(data);
            return signer.sign();
        } catch (SignatureException e) {
            System.out.println(e.getMessage());
            return null;
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
            return null;
        }
    }

    /**
     * 验证数字签名
     * @param data
     * @param signature
     * @return
     */
    public boolean verifySign(byte[] data, byte[] signature) {
        if (this.publicKey == null) {
            System.out.println("publicKey is null");
            return false;
        }
        Signature signer = null;
        try {
            signer = Signature.getInstance(this.publicKey.getAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e.getMessage());
            return false;
        }
        try {
            signer.initVerify(this.publicKey);
        } catch (InvalidKeyException e) {
            System.out.println(e.getMessage());
            return false;
        }
        try {
            signer.update(data);
            return signer.verify(signature);
        } catch (SignatureException e) {
            System.out.println(e.getMessage());
            return false;
        }
    }
}
复制代码

 

 

KeyPair类:

 

复制代码
package com.hzj.security;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;

public class KeyPairUtil {

    // 采用的双钥加密算法,既可以用DSA,也可以用RSA
    public static final String KEY_ALGORITHM = "DSA";

    /**
     * 从输入流中获取KeyPair对象
     * @param keyPairStream
     * @return
     */
    public static KeyPair loadKeyPair(InputStream keyPairStream) {
        if (keyPairStream == null) {
            System.out.println("指定的输入流=null!因此无法读取KeyPair!");
            return null;
        }
        try {
            ObjectInputStream ois = new ObjectInputStream(keyPairStream);
            KeyPair keyPair = (KeyPair) ois.readObject();
            ois.close();
            return keyPair;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return null;
    }

    /**
     * 将整个KeyPair以对象形式存储在OutputStream流中, 当然也可以将PublicKey和PrivateKey作为两个对象分别存到两个OutputStream流中,
     * 从而私钥公钥分开,看需求而定。
     * @param keyPair 公钥私钥对对象
     * @param out 输出流
     * @return
     */
    public static boolean storeKeyPair(KeyPair keyPair, OutputStream out) {
        if ((keyPair == null) || (out == null)) {
            System.out.println("keyPair=" + keyPair + ", out=" + out);
            return false;
        }
        try {
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(keyPair);
            oos.close();
            return true;
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
        return false;
    }

    /**
     * 生成KeyPair公钥私钥对
     * 
     * @return
     */
    public static KeyPair initKeyPair() throws NoSuchAlgorithmException{
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        return keyPairGen.genKeyPair();

    }

    /**
     * 生成密钥,并存储
     * @param out
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static boolean initAndStoreKeyPair(OutputStream out) throws NoSuchAlgorithmException {
        return storeKeyPair(initKeyPair(), out);
    }
}
复制代码

 

 

StringHelper类:

 

复制代码
package com.hzj.util;

import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;

public class StringHelper {
    /** 
     * BASE64Encoder 加密 
     * @param data 要加密的数据 
     * @return 加密后的字符串 
     */  
    public static String encryptBASE64(byte[] data) {  
        BASE64Encoder encoder = new BASE64Encoder();  
        String encode = encoder.encode(data);  
        return encode;  
    }  
      
    /** 
     * BASE64Decoder 解密 
     * @param data 要解密的字符串 
     * @return 解密后的byte[] 
     * @throws Exception  
     */  
    public static byte[] decryptBASE64(String data) throws Exception {  
        BASE64Decoder decoder = new BASE64Decoder();  
        byte[] buffer = decoder.decodeBuffer(data);  
        return buffer;  
    }  
}
复制代码

 

 

Program类:

 

复制代码
package com.hzj.main;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.security.NoSuchAlgorithmException;

import com.hzj.security.DataSecurity;
import com.hzj.security.KeyPairUtil;

public class Program {

    public static void main(String[] args) {
        // 1.生成证书
//        File file = new File("ca.key");
//        try {
//            FileOutputStream fileOutputStream = new FileOutputStream(file);
//            KeyPairUtil.initAndStoreKeyPair(fileOutputStream);
//        } catch (FileNotFoundException | NoSuchAlgorithmException e) {
//            e.printStackTrace();
//        }

        // 2.生成数字签名
//        DataSecurity dataSecurity = new DataSecurity();
//        String sign = dataSecurity.sign("大家好");
//        System.out.println("sign:" + sign);
        
        //3.验证数字签名
        DataSecurity dataSecurity = new DataSecurity();
        boolean result = dataSecurity.verifySign("大家好", "MCwCFCDs3sBw/fXK9flndl0M5lAUiPYFAhR9vyNNc91UiUBxFwK3GzLLjWgTkQ==");
        System.out.println("result:" + result);

    }
}
复制代码

 

 

这里需要注意的是,为什么要对数字签名进行进行Base64编码呢?这是因为生成的数字签名是byte[]型的,无论对应哪一种字符集来转化成String,都会有乱码出现。所以,采用Base64进行编码,就可以得到一串可见的字符串,方便存储和重新调用。

 

五、后记

写到这里,本文的内容就基本上完结了。有人看到这里就会问,这不是说数据库记录防篡改嘛,一直都在讲数字签名,究竟怎么个防篡改?前文已经对数字签名的一些基本原理,使用的场景,开发的步骤、代码等进行了描述,开篇是也描述了项目中遇到的数据库中的问题。将这些信息综合起来,应该就知道怎么将数字签名应用到数据库记录中来作为数据库防篡改的工具了。知道工具怎么用是基础,会用工具来完成自己想做的事情,就是进阶了。祝你步步高升!

 

六、参考资料:

[1] http://www.ruanyifeng.com/blog/2006/12/notes_on_cryptography.html

[2] http://www.youdzone.com/signature.html

[3] http://docs.oracle.com/javase/7/docs/api/java/security/package-summary.html

Google Protocol Buffer 的使用和原理

mikel阅读(1553)

简介

什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍:

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

或许您和我一样,在第一次看完这些介绍后还是不明白 Protobuf 究竟是什么,那么我想一个简单的例子应该比较有助于理解它。

一个简单的例子

安装 Google Protocol Buffer

网站 http://code.google.com/p/protobuf/downloads/list上可以下载 Protobuf 的源代码。然后解压编译安装便可以使用它了。

安装步骤如下所示:

 tar -xzf protobuf-2.1.0.tar.gz 
 cd protobuf-2.1.0 
 ./configure --prefix=$INSTALL_DIR 
 make 
 make check 
 make install

关于简单例子的描述

我打算使用 Protobuf 和 C++ 开发一个十分简单的例子程序。

该程序由两部分组成。第一部分被称为 Writer,第二部分叫做 Reader。

Writer 负责将一些结构化的数据写入一个磁盘文件,Reader 则负责从该磁盘文件中读取结构化数据并打印到屏幕上。

准备用于演示的结构化数据是 HelloWorld,它包含两个基本数据:

  • ID,为一个整数类型的数据
  • Str,这是一个字符串

书写 .proto 文件

首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义。代码清单 1 显示了例子应用中的 proto 文件内容。

清单 1. proto 文件
 package lm; 
 message helloworld 
 { 
    required int32     id = 1;  // ID 
    required string    str = 2;  // str 
    optional int32     opt = 3;  //optional field 
 }

一个比较好的习惯是认真对待 proto 文件的文件名。比如将命名规则定于如下:

 packageName.MessageName.proto

在上例中,package 名字叫做 lm,定义了一个消息 helloworld,该消息有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 str。opt 是一个可选的成员,即消息中可以不包含该成员。

编译 .proto 文件

写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。

假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

 protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

命令将生成两个文件:

lm.helloworld.pb.h , 定义了 C++ 类的头文件

lm.helloworld.pb.cc , C++ 类的实现文件

在生成的头文件中,定义了一个 C++ 类 helloworld,后面的 Writer 和 Reader 将使用这个类来对消息进行操作。诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。

编写 writer 和 Reader

如前所述,Writer 将把一个结构化数据写入磁盘,以便其他人来读取。假如我们不使用 Protobuf,其实也有许多的选择。一个可能的方法是将数据转换为字符串,然后将字符串写入磁盘。转换为字符串的方法可以使用 sprintf(),这非常简单。数字 123 可以变成字符串”123”。

这样做似乎没有什么不妥,但是仔细考虑一下就会发现,这样的做法对写 Reader 的那个人的要求比较高,Reader 的作者必须了 Writer 的细节。比如”123”可以是单个数字 123,但也可以是三个数字 1,2 和 3,等等。这么说来,我们还必须让 Writer 定义一种分隔符一样的字符,以便 Reader 可以正确读取。但分隔符也许还会引起其他的什么问题。最后我们发现一个简单的 Helloworld 也需要写许多处理消息格式的代码。

如果使用 Protobuf,那么这些细节就可以不需要应用程序来考虑了。

使用 Protobuf,Writer 的工作很简单,需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个 C++ 的类,并定义在 lm.helloworld.pb.h 中。对于本例,类名为 lm::helloworld。

Writer 需要 include 该头文件,然后便可以使用这个类了。

现在,在 Writer 代码中,将要存入磁盘的结构化数据由一个 lm::helloworld 类的对象表示,它提供了一系列的 get/set 函数用来修改和读取结构化数据中的数据成员,或者叫 field。

当我们需要将该结构化数据保存到磁盘上时,类 lm::helloworld 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

对于想要读取这个数据的程序来说,也只需要使用类 lm::helloworld 的相应反序列化方法来将这个字节序列重新转换会结构化数据。这同我们开始时那个“123”的想法类似,不过 Protobuf 想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给 Protobuf 吧。

程序清单 2 演示了 Writer 的主要代码,您一定会觉得很简单吧?

清单 2. Writer 的主要代码
 #include "lm.helloworld.pb.h"
…

 int main(void) 
 { 
  
  lm::helloworld msg1; 
  msg1.set_id(101); 
  msg1.set_str(“hello”); 
    
  // Write the new address book back to disk. 
  fstream output("./log", ios::out | ios::trunc | ios::binary); 
        
  if (!msg1.SerializeToOstream(&output)) { 
      cerr << "Failed to write msg." << endl; 
      return -1; 
  }         
  return 0; 
 }

Msg1 是一个 helloworld 类的对象,set_id() 用来设置 id 的值。SerializeToOstream 将对象序列化后写入一个 fstream 流。

代码清单 3 列出了 reader 的主要代码。

清单 3. Reader
 #include "lm.helloworld.pb.h" 
…
 void ListMsg(const lm::helloworld & msg) { 
  cout << msg.id() << endl; 
  cout << msg.str() << endl; 
 } 
 
 int main(int argc, char* argv[]) { 

  lm::helloworld msg1; 
 
  { 
    fstream input("./log", ios::in | ios::binary); 
    if (!msg1.ParseFromIstream(&input)) { 
      cerr << "Failed to parse address book." << endl; 
      return -1; 
    } 
  } 
 
  ListMsg(msg1); 
  … 
 }

同样,Reader 声明类 helloworld 的对象 msg1,然后利用 ParseFromIstream 从一个 fstream 流中读取信息并反序列化。此后,ListMsg 中采用 get 方法读取消息的内部信息,并进行打印输出操作。

运行结果

运行 Writer 和 Reader 的结果如下:

 >writer 
 >reader 
 101 
 Hello

Reader 读取文件 log 中的序列化信息并打印到屏幕上。本文中所有的例子代码都可以在附件中下载。您可以亲身体验一下。

这个例子本身并无意义,但只要您稍加修改就可以将它变成更加有用的程序。比如将磁盘替换为网络 socket,那么就可以实现基于网络的数据交换任务。而存储和交换正是 Protobuf 最有效的应用领域。

和其他类似技术的比较

看完这个简单的例子之后,希望您已经能理解 Protobuf 能做什么了,那么您可能会说,世上还有很多其他的类似技术啊,比如 XML,JSON,Thrift 等等。和他们相比,Protobuf 有什么不同呢?

简单说来 Protobuf 的主要优点就是:简单,快。

这有测试为证,项目 thrift-protobuf-compare 比较了这些类似的技术,图 1 显示了该项目的一项测试结果,Total Time.

图 1. 性能测试结果

图 1. 性能测试结果Total Time 指一个对象操作的整个时间,包括创建对象,将对象序列化为内存中的字节序列,然后再反序列化的整个过程。从测试结果可以看到 Protobuf 的成绩很好,感兴趣的读者可以自行到网站 http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking上了解更详细的测试结果。

Protobuf 的优点

Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。

它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。

Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。

使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。

Protobuf 的不足

Protbuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。

XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。

由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容【 2 】。

高级应用话题

更复杂的 Message

到这里为止,我们只给出了一个简单的没有任何用处的例子。在实际应用中,人们往往需要定义更加复杂的 Message。我们用“复杂”这个词,不仅仅是指从个数上说有更多的 fields 或者更多类型的 fields,而是指更加复杂的数据结构:

嵌套 Message

嵌套是一个神奇的概念,一旦拥有嵌套能力,消息的表达能力就会非常强大。

代码清单 4 给出一个嵌套 Message 的例子。

清单 4. 嵌套 Message 的例子
 message Person { 
  required string name = 1; 
  required int32 id = 2;        // Unique ID number for this person. 
  optional string email = 3; 

  enum PhoneType { 
    MOBILE = 0; 
    HOME = 1; 
    WORK = 2; 
  } 

  message PhoneNumber { 
    required string number = 1; 
    optional PhoneType type = 2 [default = HOME]; 
  } 
  repeated PhoneNumber phone = 4; 
 }

在 Message Person 中,定义了嵌套消息 PhoneNumber,并用来定义 Person 消息中的 phone 域。这使得人们可以定义更加复杂的数据结构。

4.1.2 Import Message

在一个 .proto 文件中,还可以用 Import 关键字引入在其他 .proto 文件中定义的消息,这可以称做 Import Message,或者 Dependency Message。

比如下例:

清单 5. 代码
 import common.header; 

 message youMsg{ 
  required common.info_header header = 1; 
  required string youPrivateData = 2; 
 }

其中 ,common.info_header定义在common.header包内。

Import Message 的用处主要在于提供了方便的代码管理机制,类似 C 语言中的头文件。您可以将一些公用的 Message 定义在一个 package 中,然后在别的 .proto 文件中引入该 package,进而使用其中的消息定义。

Google Protocol Buffer 可以很好地支持嵌套 Message 和引入 Message,从而让定义复杂的数据结构的工作变得非常轻松愉快。

动态编译

一般情况下,使用 Protobuf 的人们都会先写好 .proto 文件,再用 Protobuf 编译器生成目标语言所需要的源代码文件。将这些生成的代码和应用程序一起编译。

可是在某且情况下,人们无法预先知道 .proto 文件,他们需要动态处理一些未知的 .proto 文件。比如一个通用的消息转发中间件,它不可能预知需要处理怎样的消息。这需要动态编译 .proto 文件,并使用其中的 Message。

Protobuf 提供了 google::protobuf::compiler 包来完成动态编译的功能。主要的类叫做 importer,定义在 importer.h 中。使用 Importer 非常简单,下图展示了与 Import 和其它几个重要的类的关系。

图 2. Importer 类

图 2. Importer 类Import 类对象中包含三个主要的对象,分别为处理错误的 MultiFileErrorCollector 类,定义 .proto 文件源目录的 SourceTree 类。

下面还是通过实例说明这些类的关系和使用吧。

对于给定的 proto 文件,比如 lm.helloworld.proto,在程序中动态编译它只需要很少的一些代码。如代码清单 6 所示。

清单 6. 代码
 google::protobuf::compiler::MultiFileErrorCollector errorCollector;
 google::protobuf::compiler::DiskSourceTree sourceTree; 

 google::protobuf::compiler::Importer importer(&sourceTree, &errorCollector); 
 sourceTree.MapPath("", protosrc); 

 importer.import(“lm.helloworld.proto”);

首先构造一个 importer 对象。构造函数需要两个入口参数,一个是 source Tree 对象,该对象指定了存放 .proto 文件的源目录。第二个参数是一个 error collector 对象,该对象有一个 AddError 方法,用来处理解析 .proto 文件时遇到的语法错误。

之后,需要动态编译一个 .proto 文件时,只需调用 importer 对象的 import 方法。非常简单。

那么我们如何使用动态编译后的 Message 呢?我们需要首先了解几个其他的类

Package google::protobuf::compiler 中提供了以下几个类,用来表示一个 .proto 文件中定义的 message,以及 Message 中的 field,如图所示。

图 3. 各个 Compiler 类之间的关系

图 3. 各个 Compiler 类之间的关系类 FileDescriptor 表示一个编译后的 .proto 文件;类 Descriptor 对应该文件中的一个 Message;类 FieldDescriptor 描述一个 Message 中的一个具体 Field。

比如编译完 lm.helloworld.proto 之后,可以通过如下代码得到 lm.helloworld.id 的定义:

清单 7. 得到 lm.helloworld.id 的定义的代码
 const protobuf::Descriptor *desc = 
    importer_.pool()->FindMessageTypeByName(“lm.helloworld”); 
 const protobuf::FieldDescriptor* field = 
    desc->pool()->FindFileByName (“id”);

通过 Descriptor,FieldDescriptor 的各种方法和属性,应用程序可以获得各种关于 Message 定义的信息。比如通过 field->name() 得到 field 的名字。这样,您就可以使用一个动态定义的消息了。

编写新的 proto 编译器

随 Google Protocol Buffer 源代码一起发布的编译器 protoc 支持 3 种编程语言:C++,java 和 Python。但使用 Google Protocol Buffer 的 Compiler 包,您可以开发出支持其他语言的新的编译器。

类 CommandLineInterface 封装了 protoc 编译器的前端,包括命令行参数的解析,proto 文件的编译等功能。您所需要做的是实现类 CodeGenerator 的派生类,实现诸如代码生成等后端工作:

程序的大体框架如图所示:

图 4. XML 编译器框图

图 4. XML 编译器框图在 main() 函数内,生成 CommandLineInterface 的对象 cli,调用其 RegisterGenerator() 方法将新语言的后端代码生成器 yourG 对象注册给 cli 对象。然后调用 cli 的 Run() 方法即可。

这样生成的编译器和 protoc 的使用方法相同,接受同样的命令行参数,cli 将对用户输入的 .proto 进行词法语法等分析工作,最终生成一个语法树。该树的结构如图所示。

图 5. 语法树

图 5. 语法树其根节点为一个 FileDescriptor 对象(请参考“动态编译”一节),并作为输入参数被传入 yourG 的 Generator() 方法。在这个方法内,您可以遍历语法树,然后生成对应的您所需要的代码。简单说来,要想实现一个新的 compiler,您只需要写一个 main 函数,和一个实现了方法 Generator() 的派生类即可。

在本文的下载附件中,有一个参考例子,将 .proto 文件编译生成 XML 的 compiler,可以作为参考。

Protobuf 的更多细节

人们一直在强调,同 XML 相比, Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。

对于这些 “小 3 到 10 倍”,“快 20 到 100 倍”的说法,严肃的程序员需要一个解释。因此在本文的最后,让我们稍微深入 Protobuf 的内部实现吧。

有两项技术保证了采用 Protobuf 的程序能获得相对于 XML 极大的性能提高。

第一点,我们可以考察 Protobuf 序列化后的信息内容。您可以看到 Protocol Buffer 信息的表示非常紧凑,这意味着消息的体积减少,自然需要更少的资源。比如网络上传输的字节数更少,需要的 IO 更少等,从而提高性能。

第二点我们需要理解 Protobuf 封解包的大致过程,从而理解为什么会比 XML 快很多。

Google Protocol Buffer 的 Encoding

Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用的非常巧妙的 Encoding 方法。

考察消息结构之前,让我首先要介绍一个叫做 Varint 的术语。

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010

下图演示了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。

图 6. Varint 编码

图 6. Varint 编码消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对。如下图所示:

图 7. Message Buffer

图 7. Message Buffer采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field,这些特性都有助于节约消息本身的大小。

以代码清单 1 中的消息为例。假设我们生成如下的一个消息 Test1:

 Test1.id = 10; 
 Test1.str = “hello”;

则最终的 Message Buffer 中有两个 Key-Value 对,一个对应消息中的 id;另一个对应 str。

Key 用来标识具体的 field,在解包的时候,Protocol Buffer 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 field。

Key 的定义如下:

 (field_number << 3) | wire_type

可以看到 Key 由两部分组成。第一部分是 field_number,比如消息 lm.helloworld 中 field id 的 field_number 为 1。第二部分为 wire_type。表示 Value 的传输类型。

Wire Type 可能的类型如下表所示:

表 1. Wire Type
Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimi string, bytes, embedded messages, packed repeated fields
3 Start group Groups (deprecated)
4 End group Groups (deprecated)
5 32-bit fixed32, sfixed32, float

在我们的例子当中,field id 所采用的数据类型为 int32,因此对应的 wire type 为 0。细心的读者或许会看到在 Type 0 所能表示的数据类型中有 int32 和 sint32 这两个非常类似的数据类型。Google Protocol Buffer 区别它们的主要意图也是为了减少 encoding 后的字节数。

在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数,那么一定需要 5 个 byte。为此 Google Protocol Buffer 定义了 sint32 这种类型,采用 zigzag 编码。

Zigzag 编码用无符号数来表示有符号数字,正数和负数交错,这就是 zigzag 这个词的含义了。

如图所示:

图 8. ZigZag 编码

图 8. ZigZag 编码使用 zigzag 编码,绝对值小的数字,无论正负都可以采用较少的 byte 来表示,充分利用了 Varint 这种技术。

其他的数据类型,比如字符串等则采用类似数据库中的 varchar 的表示方法,即用一个 varint 表示长度,然后将其余部分紧跟在这个长度部分之后即可。

通过以上对 protobuf Encoding 方法的介绍,想必您也已经发现 protobuf 消息的内容小,适于网络传输。假如您对那些有关技术细节的描述缺乏耐心和兴趣,那么下面这个简单而直观的比较应该能给您更加深刻的印象。

对于代码清单 1 中的消息,用 Protobuf 序列化后的字节序列为:

 08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML,则类似这样:

 31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65 
 6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C 
 6F 77 6F 72 6C 64 3E 

一共 55 个字节,这些奇怪的数字需要稍微解释一下,其含义用 ASCII 表示如下:
 <helloworld> 
    <id>101</id> 
    <name>hello</name> 
 </helloworld>

封解包的速度

首先我们来了解一下 XML 的封解包过程。XML 需要从文件中读取出字符串,再转换为 XML 文档对象结构模型。之后,再从 XML 文档对象结构模型中读取指定节点的字符串,最后再将这个字符串转换成指定类型的变量。这个过程非常复杂,其中将 XML 文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。

反观 Protobuf,它只需要简单地将一个二进制序列,按照指定的格式读取到 C++ 对应的结构类型中就可以了。从上一节的描述可以看到消息的 decoding 过程也可以通过几个位移操作组成的表达式计算即可完成。速度非常快。

为了说明这并不是我拍脑袋随意想出来的说法,下面让我们简单分析一下 Protobuf 解包的代码流程吧。

以代码清单 3 中的 Reader 为例,该程序首先调用 msg1 的 ParseFromIstream 方法,这个方法解析从文件读入的二进制数据流,并将解析出来的数据赋予 helloworld 类的相应数据成员。

该过程可以用下图表示:

图 9. 解包流程图

图 9. 解包流程图整个解析过程需要 Protobuf 本身的框架代码和由 Protobuf 编译器生成的代码共同完成。Protobuf 提供了基类 Message 以及 Message_lite 作为通用的 Framework,,CodedInputStream 类,WireFormatLite 类等提供了对二进制数据的 decode 功能,从 5.1 节的分析来看,Protobuf 的解码可以通过几个简单的数学运算完成,无需复杂的词法语法分析,因此 ReadTag() 等方法都非常快。 在这个调用路径上的其他类和方法都非常简单,感兴趣的读者可以自行阅读。 相对于 XML 的解析过程,以上的流程图实在是非常简单吧?这也就是 Protobuf 效率高的第二个原因了。

结束语

往往了解越多,人们就会越觉得自己无知。我惶恐地发现自己竟然写了一篇关于序列化的文章,文中必然有许多想当然而自以为是的东西,还希望各位能够去伪存真,更希望真的高手能不吝赐教,给我来信。谢谢。

参考资料

学习

讨论

(一)Hololens Unity 开发环境搭建(Mac BOOTCAMP WIN10) - Erma_Jack - 博客园

mikel阅读(1357)

来源: (一)Hololens Unity 开发环境搭建(Mac BOOTCAMP WIN10) – Erma_Jack – 博客园

一、系统要求

  • 64位 Windows 10 除了家庭版的 都支持 ~
  • 64位CPU
  • CPU至少是四核心以上~
  • 至少需要 8 GB 内存, 这个不够自己可以加~
  • GPU
    1. 最低支持DirectX 11.0 或者更高
    2. 最低WDDM 1.2
  • Hardware-assisted virtualization 一个基于主板的虚拟服务(这个就是MAC本上需要手动启动的了~)
    接着上面的说,这个在mac本上就没有BIOS主板系统,但是昂贵的Mac肯定也是有虚拟化服务的~只不过Mac本不是手动启动,而是每次启动完OSX系统自动启动~ 但是如果 第一次启动的是 bootcamp的Windows 系统 那么 这个 虚拟化是启动不了的。。。这时候有一个解决办法就是先启动OSX系统,再更具目标磁盘重启到bootcamp的Windows系统~ 参考下图~

通过这个启动盘重启的Windows虚拟化是 打开的状态~ 如下图

虚拟化状态打开后就可以下载安装 HoloLens 的模拟器了 ~ 而且在开发调试中 也一定要把虚拟化打开(切记注意~)

二、软件安装 ~

这个按照下面安装下一步下一步就好~

Visual Studio 安装
Hololens模拟器安装
Unity 5.5 安装

基本环境搭建完成~

三、HoloLens模拟器网络连接

在任务栏搜索框输入H,打开hyper-管理器,然后选择交换机管理器,选择外部网络,并选择自己的网卡。如果无效请顺便将windows phone emulator也改了





Do you want to spend the rest of your life selling sugared water or do you want a chance to change the world?

(二)Hololens Unity 开发之 语音识别 - Erma_Jack - 博客园

mikel阅读(1309)

来源: (二)Hololens Unity 开发之 语音识别 – Erma_Jack – 博客园

学习源于官方文档 Voice input in Unity

笔记一部分是直接翻译官方文档,部分各人理解不一致的和一些比较浅显的保留英文原文

(二)Hololens Unity 开发之 语音识别

HoloLens 有三大输入系统,凝视点、手势和声音 ~ 本文主要讲解 语音输入 ~ (测试不支持中文语音输入~)

一、概述

HoloToolKit Unity 包提供了三种 语音输入的方式 :

  • Phrase Recognition 短语识别
    • KeywordRecognizer 单一关键词识别
    • GrammarRecognizer 语法识别
  • Dictation Recognition 听写识别
    • DictationRecognizer 将声音识别转化为文字

Note: KeywordRecognizer 和 GrammarRecognizer 是主动活跃识别的方式~ 也就是说调用开始识别的方法,那么久处于活跃状态开始识别,而DictationRecognizer只要注册了就就在默默的监听语音输入,一旦监听到关键词~那么久触发回调~

二、Unity开发打开Microphone权限

下面是官方文档 讲解 如何打开microphone权限,直接上配图~

The Microphone capability must be declared for an app to leverage Voice input.

  1. In the Unity Editor, go to the player settings by navigating to “Edit > Project Settings > Player”
  2. Click on the “Windows Store” tab
  3. In the “Publishing Settings > Capabilities” section, check the Microphone capability

三、Phrase Recognition 短语识别

To enable your app to listen for specific phrases spoken by the user then take some action, you need to:

  1. Specify which phrases to listen for using a KeywordRecognizer or GrammarRecognizer
  2. Handle the OnPhraseRecognized event and take action corresponding to the phrase recognized

使用短语识别嘞~需要做两个步骤:

  1. 指定需要监听的 短语 或者 关键词
  2. 处理识别到 短语 或者 关键词 之后的事件回调 ~ OnPhraseRecognized

1、 关键词识别 (直接Demo代码~)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;
using System.Linq;

public class VoiceInputDemo : MonoBehaviour {

    public Material yellow;
    public Material red;
    public Material blue;
    public Material green;

    /// <summary>
    /// 关键词识别对象
    /// </summary>
    private KeywordRecognizer keywordRecognizer;

    /// <summary>
    /// 存放关键词的字典
    /// </summary>
    private Dictionary<string, System.Action> keywords = new Dictionary<string, System.Action>();
    // Use this for initialization
    void Start () {

        // 向字典中添加关键词,key为关键词, vallue为一个匿名action
        keywords.Add("yellow", () =>
        {
            Debug.Log("听到了 yellow");
            transform.GetComponent<MeshRenderer>().material = yellow;
        });

        keywords.Add("red", () =>
        {
            Debug.Log("听到了 red");
            transform.GetComponent<MeshRenderer>().material = red;
        });

        keywords.Add("green", () =>
        {
            Debug.Log("听到了 green");
            transform.GetComponent<MeshRenderer>().material = green;
        });

        keywords.Add("blue", () =>
        {
            Debug.Log("听到了 blue");
            transform.GetComponent<MeshRenderer>().material = blue;
        });

        // 初始化关键词识别对象
        keywordRecognizer = new KeywordRecognizer(keywords.Keys.ToArray());

        // 添加关键词代理事件
        keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;

        // 注意: 这方法一定要写,开始执行监听
        keywordRecognizer.Start();
    }

    private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {

        System.Action keywordAction;
        // if the keyword recognized is in our dictionary, call that Action.
        // 如果关键字在我们的字典中被识别,调用该action。
        if (keywords.TryGetValue(args.text, out keywordAction))
        {
            Debug.Log("听到了,进入了事件方法  关键词语 : " + args.text.ToString());

           // 执行该action
            keywordAction.Invoke();
        }
    }

    // Update is called once per frame
    void Update () {

    }
}

## 2、 语法识别 GrammarRecognizer

按照官方文档上来说的 我得 创建一个 SRGS 的XML文件放在 StreamingAssets 文件夹下~不过我没有做到英文语法输入的需求 ~ 感兴趣的点击 https://msdn.microsoft.com/en-us/library/hh378349(v=office.14).aspx 自己查看官方文段对SRGS的讲解~

下面贴的一段官方文档的代码
Once you have your SRGS grammar, and it is in your project in a StreamingAssets folder:

<PROJECT_ROOT>/Assets/StreamingAssets/SRGS/myGrammar.xml

Create a GrammarRecognizer and pass it the path to your SRGS file:

private GrammarRecognizer grammarRecognizer;
grammarRecognizer = new GrammarRecognizer(Application.streamingDataPath + "/SRGS/myGrammar.xml");

Now register for the OnPhraseRecognized event

grammarRecognizer.OnPhraseRecognized += grammarRecognizer_OnPhraseRecognized;

You will get a callback containing information specified in your SRGS grammar which you can handle appropriately. Most of the important information will be provided in the semanticMeanings array.

private void Grammar_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
    SemanticMeaning[] meanings = args.semanticMeanings;
    // do something
}

Finally, start recognizing!

grammarRecognizer.Start();

四、听写

1、概述

DictationRecognizer 使用这个对象可以识别语音输入转化为文本,使用这个对象有三个步骤~

  1. 创建一个DictationRecognizer对象
  2. 注册Dictation 事件
  3. 开始识别听写

2、开启网络客户端权限

The “Internet Client” capability, in addition to the “Microphone” capability mentioned above, must be declared for an app to leverage dictation.

  1. In the Unity Editor, go to the player settings by navigating to “Edit > Project Settings > Player” page
  2. Click on the “Windows Store” tab
  3. In the “Publishing Settings > Capabilities” section, check the InternetClient capability

直接上Unity的图吧~

3、Demo代码示例~

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;

public class VoiceDictationDemo : MonoBehaviour
{

    private DictationRecognizer dictationRecognizer;

    // Use this for initialization
    void Start()
    {

        // 定义一个听写对象
        dictationRecognizer = new DictationRecognizer();

        // 注册一个 结果回调 事件
        dictationRecognizer.DictationResult += DictationRecognizer_DictationResult;
        // 注册一个 完成 事件
        dictationRecognizer.DictationComplete += DictationRecognizer_DictationComplete;
        // 注册一个 错误 事件
        dictationRecognizer.DictationError += DictationRecognizer_DictationError;
        // 注册一个 识别语句 的 事件
        dictationRecognizer.DictationHypothesis += DictationRecognizer_DictationHypothesis;

        dictationRecognizer.Start();
    }

    private void DictationRecognizer_DictationHypothesis(string text)
    {
        Debug.Log("进入了Hypothesis 的 事件 回调 ~ " + text);
        dictationRecognizer.Start();
    }

    private void DictationRecognizer_DictationError(string error, int hresult)
    {
        Debug.Log("进入了Error 的 事件 回调 ~ " + error + " 状态码 " + hresult);
        dictationRecognizer.Start();
    }

    private void DictationRecognizer_DictationComplete(DictationCompletionCause cause)
    {

        Debug.Log("进入了Complete 的 事件 回调 ~ " + cause);
        dictationRecognizer.Start();
    }

    private void DictationRecognizer_DictationResult(string text, ConfidenceLevel confidence)
    {
        Debug.Log("进入了Result 的 事件 回调 ~ " + text + " 枚举 " + confidence);
        dictationRecognizer.Start();
    }

    void OnDestroy()
    {
        // 销毁事件
        dictationRecognizer.DictationResult -= DictationRecognizer_DictationResult;
        dictationRecognizer.DictationComplete -= DictationRecognizer_DictationComplete;
        dictationRecognizer.DictationHypothesis -= DictationRecognizer_DictationHypothesis;
        dictationRecognizer.DictationError -= DictationRecognizer_DictationError;
        dictationRecognizer.Dispose();
    }

}

用有道 里面 的英语短视频 做了下测试~ 几乎能达到百分之九十八 以上的 识别率。。感叹微软做的挺不错的~

五、同时使用 语音识别 和 听写 (文档翻译)

If you want to use both phrase recognition and dictation in your app, you’ll need to fully shut one down before you can start the other. If you have multiple KeywordRecognizers running, you can shut them all down at once with:
如果你想同时使用 语音识别 和 听写识别,那么你必须关闭一个再启动另外一个~ 如果你有多个语音识别的对象KeywordRecognizers,那么你可以通过下面的方法把他们全部关闭~

PhraseRecognitionSystem.Shutdown();

In order to restore all recognizers to their previous state, after the DictationRecognizer has stopped, you can call:
当然,你也可以恢复关闭前的所有状态,当在你的听写识别结束的时候,你可以调用下面的方法恢复之前的语音识别~

PhraseRecognitionSystem.Restart();

You could also just start a KeywordRecognizer, which will restart the PhraseRecognitionSystem as well.
当然,你也可以只启动一个KeywordRecognizer语音识别对象~同样的也是用PhraseRecognitionSystem来控制其暂停或者恢复~

[工具] 如何利用Notepad++去除重复行 - 飞雪天影 - 博客园

mikel阅读(1352)

来源: [工具] 如何利用Notepad++去除重复行 – 飞雪天影 – 博客园

问题:

需要去除重复数据, 例如:

解决方案:

1. 打开notepad++;

2. 如果没有找到”TextFx” 选项, 需要先安装该插件。 依次打开”插件”-“Plugin Manager”-“Show Plugin Manager”-“Available” tab, 找到”TextFx Character”选项并安装。

3. 选上”TextFx”-“TextFx Tools”-“Sort outputs only UNIQUE(at column) lines”。

4. 选中要去除重复行的数据,

5. 选择”TextFx”-“TextFx Tools”-“Sort lines case sensitive/insensitive (at column)”。

6.这样重复行就消失了。

关于此实现不是 Windows 平台 FIPS 验证的加密算法的一部分。error - 嘿啾嘿啾的专栏 - 博客频道 - CSDN.NET

mikel阅读(1314)

来源: 关于此实现不是 Windows 平台 FIPS 验证的加密算法的一部分。error – 嘿啾嘿啾的专栏 – 博客频道 – CSDN.NET

最近因为要做一个启动器,在使用WPF做UI的时候,发现有错误如下:

错误 1 未知的生成错误“此实现不是 Windows 平台 FIPS 验证的加密算法的一部分。 行 8 位置 3.” c:\users\tinayh\documents\visual studio 2013\Projects\WpfApplication1\WpfApplication1\App.xaml 8 3 WpfApplication1

百思不得其解啊,于是在网上solo,找到了解决方法:
1、在window中打开功能里输入regedit,回车打开注册器;这里写图片描述
2、进入如下路径中
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy
这里写图片描述
将 enable设置为0 即可。
3、再运行你的vs上的项目。
简单的三步完美的解决了我的问题。

服务器慢 mysql-bin.000001文件占满磁盘的原因与解决

mikel阅读(1135)

发现 VPS 服务器上的网站反应超级慢,简单的重启、重启各主要服务,发现mySQL 的反应极其不正常。

一方面是问题,这与站点访问量有关。开始时从mySQL 的配置文件 my.cnf 考虑,但志文工作室在这里的问题并不是此原因。

排查过程中 df -lh 命令,发现系统主目录磁盘使用率 100% 了。
粗略估计应该是 /usr/ 下出了问题,执行命令:

du -h –max-depth=1 /usr/local/ | sort -nr | more

查看并分析结果,发现 /usr/local/mySQL/var/ 目录异常,该目录下有众多类似这样的文件:mysql-bin.00001

这是数据库的日志文件,原因就在于此。那么我们可以删除它,执行命令:

/usr/local/mysql/bin/mysql -u root -p

输入密码后进入 mysql 命令行,执行:

reset master;

此时再查看日志文件是否已被清除。
日志文件一般用于主从服务器的备份恢复,这里是单服务器,那么就应该关闭日志记录。所以应该这么做,编辑 my.cnf 文件:

vim /etc/my.cnf

注释掉如下两行:

#log-bin=mysql-bin
#binlog_format=mixed

然后重启 mysql,若出现类似如下错误提示:

Starting MySQL.Manager of pid-file quit without updating fi[Failed]

这应该是日志索引的问题,删除文件 mysql-bin.index:

rm -f /usr/local/mysql/var/mysql-bin.index

然后再重启。此时若 mysql-bin.00001 这些文件还在,可删除之:

rm -f /usr/local/mysql/var/mysql-bin.000*

如何在被黑的wordpress站点中发现后门并修复它 - 点金主题网

mikel阅读(1046)

来源: 如何在被黑的wordpress站点中发现后门并修复它 – 点金主题网

一般的站点都存在安全隐患,wordpress架构的站点也是如此。我们接触过几个被黑的用户的站点,发现他们在本身的安全意思不强导致的,从而被黑客上传了后门程序。这些后门程序有的非常的隐蔽,不易发现。本文将从我们实际接触的几个案例来探讨一下如何在被黑的wordpress站点中发现后门并修复它。其它程序架构的网站不在此讨论中。

一、什么是后门?

后门是一种为删除能通过正常的授权并获得远程访问服务器的程序方法。许多聪明的黑客总会上传后门程序以获得站点的正常授权。如果你没有发现并适当的清除它,黑客们会一直已管理员的身份访问你的站点。这是非常可怕的。

后门程序各种各样,有一些后门程序仅允许常见隐藏的管理员用户名;然后一些复杂的后面程序能够允许黑客执行PHP代码;还有一些能够完全注入UI,允许他们以服务器名义发送电子邮件执行SQL查询等等任何可以做到的事。

后门

二、后门代码隐藏在什么地方?

通过我们实际接触及国外安全方面的介绍,一般后门代码隐藏在一下的位置:

  1. 主题 – 为了让后门代码更加隐蔽,黑客门不把代码放在当前主题中,他们可能把代码放在一些没有激活的主题中。如果你的wordpress站点中存在多个主题,请不未激活的主题删除。
  2. 插件 – 有三点足以说明插件是黑客们乐于放后门代码的地方。第一是因为我们不去看其代码,只管用;第二是因为我们不喜欢更新插件,担心更新后会失去设置;第三一些插件本身就藏有后面。
  3. Uploads 目录 – 作为一名wordpress使用者,可能不愿意去看uploads目录,因为那里面存在许许多多上传的文件,有时显得很乱。然后这正被黑客们利用。由于这个文件夹是可写的,所以导致一些后面代码就存在这里。
  4. wp-config.php – 这个文件也是被黑客们高度注意的一个文件。
  5. Includes文件夹 – /wp-includes/文件夹也是黑客们喜欢上传文件的地方,因为这里面存在大量的PHP文件,他们上传一个php文件在这里很容易混淆视听,不易被发现。

在这些地方,他们会让他们的文件看上去像一个wordpress文件。

例如:在一个案例中,这个后门代码存在 wp-includes文件夹中,它叫wp-user.php,这个文件在正常的安装中没有的。还有叫 user.php,正常安装中也没有。在另一个案例中还有一个叫 hello.php,存在于uploads文件夹内,这与 Hello Dolly插件是不同的。

可能还有使用类似wp-content.old.tmp, data.php, php5.php这样的文件,甚至使用zip文件。在大多少案例中,这些文件采用了base64 代码加密,让你看不到内容而不敢删除。

现在可能然你认为wordpress是不安全的而不敢使用,这你就错了。我一开始就讲了,任何程序都存在安全隐患。而wordpress在安全方面做的足够好,而且一旦发现马上就会有更新,这一点大家不要担心。之所以出现这些,往往是大家设置不当导致的。

三、如何发现并清除后面?

现在已经知道后门是什么以及在哪里,接下来就是去找到它,一旦找到,清除就很简单,直接删除即可。难点是如何发现它的位置。不过我们可以借助插件来做。以下几个插件可以实现这点:

1、Theme Authenticity Checker (免费插件)

2、Exploit Scanner(免费插件)

3、Sucuri(付费插件)

当然如果你不愿意使用插件,也可手动来实现。

搜索 Uploads 目录

尽管上面的插件可以轻易做到这点,然后如果你属性SSH,则需要写下面的命令即可搜索到:

find uploads -name "*.php" -print

显然在uploads目录里如果存在php文件,我们应引起高度重视。一般这里是媒体文件。

删除未激活的主题

如上所述,这个未激活的主题也是黑客们的目标。最直接的办法就是删除它。因为不必要的无需放在那里,也不必花时间去找。

.htaccess 文件

前段时间,我发现一个用户的网站在这个文件里出现了重定向的代码,致使网站不能访问。所以我们可以直接删除这个文件,然后到后台设置固定连接保存一下就会重新生成.htaccess文件。

wp-config.php 文件

一般可以通过比较软件来比较这个文件与wp-config-sample.php的区别,一旦发现就清除。

扫描数据库

有一些黑客他们会在数据库里添加一些执行代码,让你不易发现。如PHP函数、新管理员帐户、垃圾链接等等。这个新管理员帐户在用户列表中不易看到的。如果不熟悉SQL或不知从何开始,则需使用Exploit Scanner插件或 Sucuri 插件。

再次检视一下网站

这些后面已经清除了吗?别急。登入时浏览没发现异常,请登录再浏览。因为有的后门非常的聪明,登入看不到,登出则会发现。

四、如何防范于未然?

显然,但发现了后门之后,损失已经造成。所以我们平时应该加强安全意识,做到防患于未然。下面一些提示比较重要,有利于加强网站的安全。

  1. 使用强密码 – 我们不建议使用纯数字的密码,也不建议使用少于6位的密码。建议大于6位,多字符混合,大小写混合。
  2. 2-步授权 –如果密码被攻破了,则进行第二步验证,通过手机验证。现在已经有这方面的插件。
  3. 限制登录尝试 – 通过插件来限制用户登录错误密码的次数。
  4. 关闭主题和插件编辑 – 即使被登录进去,他们也不能修改你的主题和插件。
  5. 密码保护 WP-Admin – 你可以设置密码保护此目录,限制登录IP。
  6. 关闭 某个目录下的PHP执行权限 –包括上传目录和其它选择的目录。T
  7. 保持更新 –使用最新的wordpress版本,保持插件更新。
以上2-6通过插件来实现。
至此本文完成,希望本文能够帮助你,令你的站点固若金汤。