浏览器工作原理

看到一篇很好的文章,浏览器的工作原理,http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

看一遍,把要点梳理一下。

1.浏览器的结构

1)UI  主界面、地址栏、前进后退等

2)Browser engine,adapter层。

3)render engine,如果adapter层发来html的请求,render会解析html和css,并将结果最终渲染到屏幕上。

4)network,底层网络模块,与平台无关,负责所有网络接口。

5)JavaScript解释器,负责解析和执行JS

6)UI后端,绘制基本的窗口小控件,和平台无关,在底层使用操作系统的绘制方法。

7)数据存储,如cookie和indexdb等

layers

Render Engine,渲染引擎

渲染引擎是web的核心,因为web中一般不做复杂的计算等操作。页面需要及时响应用户操作,流畅的渲染。那么来看一下渲染的基本流程

flow

1.解析HTML,生成DOM-TREE,解析CSS,生成CSSOM,合成RenderTree,就是包含一堆矩形的树,树结构代表了显示顺序。

2.layout,计算位置

3.paint

下图是webkit的渲染过程

webkitflow

解析过程

上面的流程中,HTML Parser CSS Parser都涉及到了一个解析的过程,什么是解析?

解析就是把文档转化成代码能理解的结构,生成结果被称为解析树或语法树。

解析基于确定的语法规则,即与上下文无关的语法。

解析可以分为两个过程:词法分析和语法分析。

解析把两个过程交给两个组件,词法分析器和解析器。

词法分析器负责把输入内容分成一个个标记。

解析器负责把标记构建成解析树。解析器的解析是一个迭代过程,即向词法分析器请求一个新标记,如果匹配语法规则,则把对应该标记的节点添加到解析树中,如果不匹配,则把这个标记缓存,继续请求标记,直到找到一个匹配的语法规则。

解析树往往还不是最终产品,这个时候需要使用编译器把解析树转成机器代码。

image013

一般的解析可以去看龙书,Html语言不适用于常规解析器,因为HTML不是一种上下文无关的语言,这源于HTML有一定的容错能力。CSS和JavaScript可以。

HTML有自定义的解析器,采用DTD格式定义。

解析算法分为两个步骤,标记化和构建树:

标记化算法使用状态机驱动,该算法比较复杂,通过一个例子叙述:

<html>
  <body>
    Hello world
  </body>
</html>

1.初始状态为 数据状态,

2.遇到<时,更改为标记打开状态,

3.之后读取一个a-z字符,进入标记名状态

4.直到遇到>,进入数据状态

5.数据状态下去读Hello world,直到<,进入标记打开状态

6.标记打开状态下遇到/号,创建 end tag token并进入标记名状态

7.读取a-z字符直到>,回到数据状态。

解析器创建时,会创建Document对象。在解析器工作的时候,已Document为根节点的DOM树不断更新,解析器生成的元素不仅仅会加入DOM树,还会放到堆栈中去,堆栈用来纠正嵌套错误和未关闭的标记。

树构建过程

树构建阶段的输入是一个来自标记化阶段的标记序列。第一个模式是“initial mode”。接收 HTML 标记后转为“before html”模式,并在这个模式下重新处理此标记。这样会创建一个 HTMLHtmlElement 元素,并将其附加到 Document 根对象上。

然后状态将改为“before head”。此时我们接收“body”标记。即使我们的示例中没有“head”标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中。

现在我们进入了“in head”模式,然后转入“after head”模式。系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement,同时模式转变为“in body”

现在,接收由“Hello world”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。

接收 body 结束标记会触发“after body”模式。现在我们将接收 HTML 结束标记,然后进入“after after body”模式。接收到文件结束标记后,解析过程就此结束。

image022

树构建过程

解析过程结束后,浏览器将文档注为交互状态,开始加载 defferd模式下的脚本。然后文档状态成为完成,随之触发加载事件。

容错

浏览器底层有大量的代码来对诸如 无效tag,结束符错误等容错。

来看看webkit的容错说明

解析器对标记化输入内容进行解析,以构建文档树。如果文档的格式正确,就直接进行解析。

遗憾的是,我们不得不处理很多格式错误的 HTML 文档,所以解析器必须具备一定的容错性。

我们至少要能够处理以下错误情况:

  1. 明显不能在某些外部标记中添加的元素。在此情况下,我们应该关闭所有标记,直到出现禁止添加的元素,然后再加入该元素。
  2. 我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。
  3. 向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。
  4. 如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。
  5. 示例网站 www.liceo.edu.mx 嵌套了约 1500 个标记,全都来自一堆 <b> 标记。我们只允许最多 20 层同类型标记的嵌套,如果再嵌套更多,就会全部忽略。
  6. 支持格式非常糟糕的 HTML 代码。我们从不关闭 body 标记,因为一些愚蠢的网页会在实际文档结束之前就关闭。我们通过调用 end() 来执行关闭操作。

CSS解析是遵循一般的解析规则的,这里不赘述。

脚本和文档处理的顺序

当遇到一个script标签时,会立即停止文档的解析,进行js解析。

如果script标签带有defer标记,则会在文档解析完成后再解析。

h5增加了一个标记,将script标记为异步,则会开线程去解析它。

预解析

这是一项优化。在执行脚本的时候,其他线程回去解析剩下的文档部分,找出并加载需要下载的网络资源。预解析器不会动DOM树,只是寻找需要下载的资源。

样式表

理论上CSS和HTML不是一个解析器,但有些JS会请求CSS,所以浏览器在解析CSS的时候会禁止有交互的脚本。

Render-Tree

在DOM-Tree构建的时候,同时也在生成一个Render-Tree。Render-Tree是DOM-Tree的子集,它不包含那些display:none的元素,以及head等。

一般情况下DOM-Tree和Render-Tree的位置是对应的,但不包括absolute和float等非文档流内容,它们位于其他位置,原位置放一个占位符。

样式计算

样式计算是个复杂而庞大的工程,如 匹配规则为 div div div div,这样的匹配会很耗时,而且找了很久可能找到一个三层的。

浏览器为了样式计算进行了很多工作

共享样式数据
规则树
结构划分
使用规则树计算样式上下文
对规则进行处理以简化匹配
以正确的层叠顺序应用规则
样式表层叠顺序
特异性

特异性这里详细记一下

某个样式的属性声明可能会出现在多个样式表中,同一个样式表中也可能出现多次,那么他们的重要性为(优先级从低到高):

1.浏览器声明

2.用户普通声明

3.作者普通声明

4.作者重要声明

5.用户重要声明

同样顺序的声明,按照特异性排序

特异性是 a-b-c-d

a:声明来自style,标记为1,否则为0

b:选择器中ID属性的个数

c:选择器中class属性和伪类的个数

d:选择器中元素名称和伪类的个数

规则排序

渐进式处理

这里不详细叙述。

布局-layout

layout是一个比较重的环节,不仅仅是layout消耗时间,而是layout后一定会引发paint。

HTML采用流式布局,意味着可以遍历一次就计算出来所有位置,从左到右,从上到下。有些特殊的如 表格需要多次遍历。

每一个节点都有一个layout/reflow方法,是一个递归过程。

Dirty位系统

为了避免细小改动都要进程整体布局,浏览器采用了Dirty位的设计。如果某个节点发生了改变,则将其与子代标注为dirty。还有一种标记,children are dirty,表示节点至少有一个子代需要重新布局。

布局分全局和增量两种

全局指必须重新布局,增量是只布局dirty节点(可能还需要布局其他元素)。全局布局是同步的,增量布局一般是异步的。

布局优化,如果只是位置改变或者缩放等,可以直接从缓存取原大小。某些子树修改,不必全局重新layout,如input,不会每输入一次就来一次全局更新。

Paint

paint的内容比较少,只是调用节点的paint方法。paint也有全局绘制和增量绘制两种。

优化

webkit在paint时会缓存之前的矩形,只绘制新旧矩形的差异部分。而当元素有了变化,浏览器会尽量做最小的改动。

 

线程

Render engin是单线程。

loop

主线程是一个loop,事件循环。

 

CSS模型

可视化模型,指画布

框模型,由框大小,padding,margin,border组成,display指定

image046

position

普通  浮动 绝对

display:block  普通的矩形,拥有自己的区域

display:inline 没有自己的区域,位于容器block中

image061

block是垂直的,inline是水平的

分层

z-index表示了框的第三个维度

 

代码行数统计-cloc

cloc是一个很方便的代码行数统计工具,官网 http://cloc.sourceforge.net/;

安装

 npm install -g cloc            #https://www.npmjs.com/package/cloc
  sudo apt-get install cloc              # Debian, Ubuntu
  sudo yum install cloc                  # Red Hat, Fedora
  sudo pacman -S cloc                    # Arch
  sudo pkg install cloc                  # FreeBSD
  sudo port install cloc                 # Mac OS X with MacPorts

我这里使用的npm安装(需要安装node),npm install -g cloc安装好后,命令行敲cloc会给出使用指令,这里不一一详述,只给一个最简单的使用方法。

把要统计的文件打成zip包,然后 cloc  xx.zip,就可以了。

cloc

Node.js源码学习-模块

Node.js底层有三个重要部分

node

libuv 建立事件循环

v8 引擎以及JS/C++对象转换

node提供的模块 fs os net process等

libuv是一个神器,事件循环,适合状态机驱动的架构,单独学习。

v8的请看google的官方API文档。

今天主要看一下node的模块。

 

1.js2c.py

node内置了很多js文件,包括主程序 src/node.js和模块文件lib/*.js。js2py是把每一个js文件都生成一个源码数组,存放在node_natives.h中。

node_native

node_native.h中,每个js文件对应一个数组,源码以const char数组的形式存放。

 

node_native在映射完毕后,变成了这样一个数组。

2.node_javascript.cc

node_javascript.cc include了node_native.h,并提供两个方法:

MainSource(),只有一句

return OneByteString(env->isolate(), node_native, sizeof(node_native) - 1);

返回node_native映射的js文件,即node.js,转换成了一个v8::handle类型。node_native_1

 

 

图:node_native,node.js映射后的数组。

DefineJavaScript(env, target)

将所有js文件映射到Object型的target(不包括node.js)

target->Set(name, source)

综上,node_javascript.cc做了两件事,返回node.js的v8::handle,

返回lib/*.js的Object,{name: v8::handle, name: v8::handle .....}

现在所有js文件都被转换成了v8::handle。

3.node.cc

node.cc是nodejs的main文件,这里我们先认识一个node世界中的风云人物,process。

 

 

在node.cc的CreateEnvironment函数中,process出生了,下面我们从源码看看process是个什么?被赋予了哪些属性?

Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate);
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "process"));

Local<Object> process_object = process_template->GetFunction()->NewInstance();
env->set_process_object(process_object);

SetupProcessObject(env, argc, argv, exec_argc, exec_argv);

从上面这段代码可以看出process的原始属性,FunctionTemplate,很明显,是js里的一个function,function的name为process。

现在JS世界中已经多了一个叫process的function,那么它是在哪里成长的呢?

 

在node.cc的LoadEnvironment函数中,

Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
CHECK(f_value->IsFunction());
Local<Function> f = Local<Function>::Cast(f_value);
Local<Object> global = env->context()->Global();
Local<Value> arg = env->process_object();
f->Call(global, 1, &arg);

运行MainSource

拿到了node.js的返回f_value,即node.js函数,入参为process的闭包函数,

(function(process) {})

验明正身,执行node.js闭包函数,最后一句做JS的同学看着肯定很眼熟,这不就是

(function(process){}).call(global, process)

 

4.fs.js

这里通过一个fs模块的创建与加载,来展示process呼风唤雨的能力。

const SlowBuffer = require('buffer').SlowBuffer;
const util = require('util');
const pathModule = require('path');

const binding = process.binding('fs');
const constants = require('constants');
const fs = exports;
const Buffer = require('buffer').Buffer;
const Stream = require('stream').Stream;
const EventEmitter = require('events');
const FSReqWrap = binding.FSReqWrap;
const FSEvent = process.binding('fs_event_wrap').FSEvent;

首先规规矩矩的加载了几个模块,然后突然出现了binding

Binding在node.cc中实现

主要逻辑为,查看cache中是否有这个模块,如果没有就新建一个。

新建模块:如果存在builtin,则使用nm_context_register_func方法注册到cache中,如果是常量,cache中注册常量,如果是natives,绑定所有lib/*.js。

在fs中,process.binding找到了fs,是一个builtin模块。这个模块在node_file.cc中实现,最后通过NODE_MODULE_CONTEXT_AWARE_BUILTIN注册到builtin。

Binding函数完成了js上调用C++模块。

 

5.node.js

node.js中通过一个NativeModules来管理js模块,

function NativeModule(id) {
   this.filename = id + '.js';
   this.id = id;
   this.exports = {};
   this.loaded = false;
}

NativeModule._source = process.binding('natives');
NativeModule._cache = {};

 

process.binding('natives'),将所有内置js模块绑定到_source上。

之后可以通过require方法来拿到这些模块。

NativeModule.require = function(id) {
   if (id == 'native_module') {
      return NativeModule;
   }

   var cached = NativeModule.getCached(id);
   if (cached) {
      return cached.exports;
   }

   if (!NativeModule.exists(id)) {
      throw new Error('No such native module ' + id);
   }

   process.moduleLoadList.push('NativeModule ' + id);

   var nativeModule = new NativeModule(id);

   nativeModule.cache();
   nativeModule.compile();

   return nativeModule.exports;
};

先检查cache,如果cache没有,就在process.moduleLoadList中增加一个,然后缓存,执行.

缓存就是把模块放到_cache中,compile函数

NativeModule.prototype.compile = function() {
   var source = NativeModule.getSource(this.id);
   source = NativeModule.wrap(source);

   var fn = runInThisContext(source, {
      filename: this.filename,
      lineOffset: 0
   });
   fn(this.exports, NativeModule.require, this, this.filename);

   this.loaded = true;
};


 var ContextifyScript = process.binding('contextify').ContextifyScript;
 function runInThisContext(code, options) {
 var script = new ContextifyScript(code, options);
 return script.runInThisContext();
 }

通过C++模块contextify运行了这段js。

 

 

libuv学习

libuv 是重写了下libev,封装了windows和unix的差异性。
libuv的特点
非阻塞TCP套接字 socket?
非阻塞命名管道
UDP
定时器
子进程 fork?
通过 uv_getaddrinfo实现异步DNS
异步文件系统API uv_fs_*
高分辨率时间 uv_hrtime
正在运行程序路径查找 uv_exepath
线程池调度 uv_queue_work
TTY控制的ANSI转义代码 uv_tty_t
文件系统事件支持 inotify ReadDirectoryChangesW kqueue 马上回支持 uv_fs_event_t
进程间的IPC与套接字共享 uv_write2

事件驱动的风格:程序关注/发送事件,在事件来临时给出反应。
系统编程中,一般都是在处理I/O,而IO的主要障碍是网络读取,在读取的时候是阻塞掉的。标准的解决方案是使用多线程,每一个阻塞的IO操作被分配到一个线程。当线程block,处理器调度处理其他线程。
libuv使用了另一个方案,异步。操作系统提供了socket的事件,即使用socket,监听socket事件即可。

libuv简单使用 创建一个loop,关闭loop。
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

int main()
{
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
uv_loop_init(loop);

printf("hello, libuv");

uv_run(loop, UV_RUN_DEFAULT);

uv_loop_close(loop);
free(loop);

return 0;
}

如果只需要一个loop的话,调用uv_default_loop就可以了。
tips:Nodejs中使用了这个loop作为主loop。
uv_loop_t *loop = uv_default_loop();

uv_run(loop, UV_RUN_DEFAULT);

uv_loop_close(loop);

Error
初始化或同步函数,会在执行失败是返回一个负数,可以通过uv_strerror、uv_err_name获得这个错误的名字和含义
I/O函数的回调函数会被传递一个nread参数,如果nread小于0,也代表出现了错误。

Handle & Request
libuv的工作建立在事件的监听上,通常通过handle来实现,handle中uv_TYPE_t中的type指定了handle监听的事件。
在uv.h中可以找到handle和request的定义
/* Handle types. */
typedef struct uv_loop_s uv_loop_t;
typedef struct uv_handle_s uv_handle_t;
typedef struct uv_stream_s uv_stream_t;
typedef struct uv_tcp_s uv_tcp_t;
typedef struct uv_udp_s uv_udp_t;
typedef struct uv_pipe_s uv_pipe_t;
typedef struct uv_tty_s uv_tty_t;
typedef struct uv_poll_s uv_poll_t;
typedef struct uv_timer_s uv_timer_t;
typedef struct uv_prepare_s uv_prepare_t;
typedef struct uv_check_s uv_check_t;
typedef struct uv_idle_s uv_idle_t;
typedef struct uv_async_s uv_async_t;
typedef struct uv_process_s uv_process_t;
typedef struct uv_fs_event_s uv_fs_event_t;
typedef struct uv_fs_poll_s uv_fs_poll_t;
typedef struct uv_signal_s uv_signal_t;

/* Request types. */
typedef struct uv_req_s uv_req_t;
typedef struct uv_getaddrinfo_s uv_getaddrinfo_t;
typedef struct uv_getnameinfo_s uv_getnameinfo_t;
typedef struct uv_shutdown_s uv_shutdown_t;
typedef struct uv_write_s uv_write_t;
typedef struct uv_connect_s uv_connect_t;
typedef struct uv_udp_send_s uv_udp_send_t;
typedef struct uv_fs_s uv_fs_t;
typedef struct uv_work_s uv_work_t;

/* None of the above. */
typedef struct uv_cpu_info_s uv_cpu_info_t;
typedef struct uv_interface_address_s uv_interface_address_t;
typedef struct uv_dirent_s uv_dirent_t;

handle是持久化的对象。在异步操作中,相应的handle有许多关联的request。
request是短暂性的,通常只维持一个回调的时间,一般对应handle的一个IO操作。request用来在初始函数和回调函数中传递上下文。
例如uv_udp_t代表了一个udp的socket,每一个socket的写入完成后,都有一个uv_udp_send_t被传递。

handle的设置
uv_TYPE_init(uv_loop_t*, uv_TYPE_t);

一个idle handle的使用例子 观察下它的生命周期
#include <stdio.h>
#include <uv.h>

int counter = 0;

void wait_for(uv_idle_t * handle)
{
counter++;

if (counter > 10e6)
{
uv_idle_stop(handle);
}
}

int main()
{
uv_idle_t idler;

uv_idle_init(uv_default_loop(), &idler);
uv_idle_start(&idler, wait_for);

printf("Idle......");

uv_run(uv_default_loop(), UV_RUN_DEFAULT);

uv_loop_close(uv_default_loop());

return 0;
}

参数传递
handle和request都有一个data域,用来传递信息。uv_loop_t也有一个相似的data域。

文件系统
简单的文件读写是通过uv_fs_*函数族和与之相关的uv_fs_t结构体完成的。
系统的文件操作是阻塞的,所以libuv在线程池中调用这些函数,最后通知loop。

如果没有指定回调函数,文件操作是同步的,return libuv error code。
异步在传入回调函数时调用,return 0。

获得文件描述符
int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb);
关闭文件描述符
int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb)
回调函数
void callback(uv_fs_t* req);
还有uv_fs_read uv_fs_write uv_fs_t构成了基本的文件操作

流操作使用uv_stream_t,比基本操作方便不少
uv_read_start uv_read_stop uv_write
流操作可以很好的配合pipe使用
pipe相关函数
uv_pipe_init uv_pipe_open uv_close

文件事件
uv_fs_event_t uv_fs_event_init uv_fs_event_start

网络
uv_ip4_addr ip为 0.0.0.0表示绑定所有接口 255.255.255.255是一个广播地址,意味着数据将往所有的子网接口发送,端口号0表示由操作系统随机分配一个端口
tcp
TCP是面向连接的字节流协议,因此基于libuv的stream实现
uv_tcp_t
服务器端创建流程
uv_tcp_init 建立tcp句柄
uv_tcp_bind 绑定
uv_listen 建立监听,当有新的连接到来时,激活调用回调函数
uv_accept 接收链接
使用stream处理数据以及与客户端通信

客户端
客户端比较简单,只需要调用uv_tcp_connect

udp
UDP是不可靠连接,libuv基于uv_udp_t和uv_udp_send_t
udp的流程与tcp类似

libuv提供了一个异步的DNS解决方案,提供了自己的getaddrinfo
配置好主机参数addrinfo后使用uv_getaddrinfo即可

调用uv_interface_addresses获得系统的网络信息

未完待续。

来源:http://luohaha.github.io/Chinese-uvbook/source/introduction.html

使用gyp

GYP(Generate You Project),生成IDE项目的工具,使用Python脚本写成,配置文件为JSON格式。

使用gyp需要两个环境,python和gyp。gyp可以直接在这里下载

git clone https://chromium.googlesource.com/external/gyp

一般下载到build/gyp

使用python将我们的gyp文件加载并运行起来

import gyp // 载入gyp模块
import sys
import os

    args = sys.argv[1:]
args.append(os.path.join(os.path.abspath(uv_root), 'test.gyp'))

def run_gyp(args) :
rc = gyp.main(args) // gyp初始化
if rc != 0 :
print('Error running GYP')
sys.exit(rc)

args中可以添加工程的配置文件,大概格式如下:

{
    'target_defaults': {
    'conditions': [
        ['OS != "win"', {
            'defines': [
                '_LARGEFILE_SOURCE',
                '_FILE_OFFSET_BITS=64',
            ],
            'conditions': [
                ['OS=="solaris"', {
                    'cflags': [ '-pthreads' ],
                }],
                ['OS not in "solaris android"', {
                    'cflags': [ '-pthread' ],
                }],
            ],
        }],
    ],
        'xcode_settings': {
        'WARNING_CFLAGS': [ '-Wall', '-Wextra', '-Wno-unused-parameter' ],
            'OTHER_CFLAGS': [ '-g', '--std=gnu89', '-pedantic' ],
    }
},
    target: [
{
    'target_name': 'hello',
    'type': 'executable',
    'dependencies': [ 'libuv' ],
    'sources': [
    'hello.c',
],
    'conditions': [
    [ 'OS=="win"', {
        'sources': [
        ],
        'libraries': [ '-lws2_32' ]
    }, { # POSIX
    'defines': [ '_GNU_SOURCE' ],
    'sources': [
    'test/runner-unix.c',
    'test/runner-unix.h',
]
}],
['uv_library=="shared_library"', {
    'defines': [ 'USING_UV_SHARED=1' ]
}],
],
'msvs-settings': {
    'VCLinkerTool': {
        'SubSystem': 1, # /subsystem:console
    },
},
},
]
}

target_name:工程名

type: 工程类型

dependencies: 依赖文件夹

sources: 源文件

conditions:条件判断

msvs-settings:msvs额外设置

 

Linux服务器-windows远程开发

最近项目需要在Linux环境下,本人的本是windows,怎么办么?

答:申请一台服务器,linux环境,用ssh工具就可以开发了

不过服务器上只能用vim开发,开发起来惨不忍睹,怎么办?

答:远程开发

远程开发的原理很简单,就是把服务器上的文件夹映射到windows上,就可以像windows本地开发一样了。

首先在Linux服务器上安装samba,装好后给自己配置一个samba用户,具体方法请自行百度,参考链接:http://blog.csdn.net/i_chips/article/details/19191957。

window端打开计算机,有一个映射网络驱动器选项

samba_3

打开后有两个选项

samba_4

第一个是盘符,默认就行,红框所选的地方 写上要映射的服务器文件夹地址。

填好之前在samba上配置的账号/密码,就可以用了。

替代微软Visio的开源免费软件DIA Diagram Editor

DDIA Diagram Editor,功能强大和跨平台特性,原生支持简体中文界面。与Visio相比,DIA Diagram Editor安装包仅不足20MB,可以放在网盘或U盘中随身携带。初用者可能觉得Dia用法比较繁琐而麻烦,但是无法否认,它仍然是综合性能最佳的免费替代方案。

DIA Diagram Editor支持导出的流程图格式如下:EPS、SVG、DXF(Autocad格式)、CGM、WMF、PNG、JPEG、VDX(Microsoft Visio格式)。

dia_screenshot

项目地址:http://sourceforge.net/projects/dia-installer/

 

使用CMake(windows vs2015)

学习使用CMake,简单记录一下学到的东西:

CMake使用自己的语法对工程进行配置,方便在各个平台编译。

windows上,安装了CMake后,有gui界面,操作起来很方便。

1.打开gui,选择源码目录(对应${PROJECT_SOURCE_DIR})

2.选择生成目录,最好是${PROJECT_SOURCE_DIR}/build,防止生成文件跟源码搞到一起。

点击configure,偶尔需要两次。configure完工后,去build目录下已经可以看到VS工程文件,打开就可以慢慢调了。

 

CMake要求不多,编译目录下需要有CMakeList.txt,CMake根据CMakeList酌句执行。

这里贴一个用到的CMakeList.txt,并简单注释(目前理解还不够,仅仅是配置对应了vs的哪一项)

cmake_minimum_required(VERSION 2.8)
#工程名
project(projectName)
#输出
message("project source dir: ${PROJECT_SOURCE_DIR}")
#添加编译选项 -D为预编译选项
set(CMAKE_C_FLAGS "-fshort-wchar -fPIC -DHAVE_CONFIG_H -O0 -DNDEBUG")
set(CMAKE_CXX_FLAGS "-std=c++11 -fshort-wchar -frtti -fPIC -fexceptions -O0 -DNDEBUG" )
#同上
add_definitions(-D_WINDOWS_PLATFORM)

#下面是一些CMake的编程语法 set用的最多,就是设置变量 类似于 +=
set(DIR_LIST src src/utils
)

foreach(DIR ${DIR_LIST})
#message("dir:${DIR}")
#查找当前目录下所有的源文件并保存在SRC中
aux_source_directory(${DIR} SRC)
#message("src:${SRC}")
set(SRC_LIST ${SRC_LIST} ${SRC})
endforeach(DIR)

set(SRC_LIST ${SRC_LIST} 
demo/windows/main.cpp
)
#message("srclist:${SRC_LIST}")

#add_subdirectory(${PROJECT_SOURCE_DIR}/deps/curlcpp)

#添加到头文件
include_directories(
${PROJECT_SOURCE_DIR}/demo/windows/include
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src/
)

#附加库目录
link_directories(${PROJECT_SOURCE_DIR}/demo/windows/libs/)
#使用SRC_LIST中的文件生成可执行文件hello
add_executable(hello ${SRC_LIST} )
#附加依赖项
target_link_libraries(hello OpenGL32 GLU32)

下面是一篇极好的CMake学习文档,CMake practice

 

Sqlite常规操作 add update select

用到了数据库,花了好长时间才搞定,把遇到的问题记录一下,sqlite语法:

select,最简单的语句

select * from table where condition

add

add中有两个需求

1.没有表的时候创建表

create table if not exists

2.如果存在则更新,不存在就插入

insert or replace into table ()values()

这里需要配合 primary key,用unique会报错

3.update

update table set x = x where condition

 

多次遇到 near syntax error,这些肯定是语法拼写错误,请仔细检查。

 

小米Note(MIUI V7)查看程序访问网络流量排行

小米Note(MIUI V7) 连接上WIFI后,显示网络下载一直很高,不清楚是哪个应用在访问网络,网上搜索了一下,找到如何查看网络流量的方法。

  • 桌面上点击“安全中心”

desktop

  • 选择“流量剩...”这个水滴状的按钮

safe_center

  • 选择“流量排行”

flow_list

  • 点击右上角的切换图标来显示WIFI跟3G,4G上网排行

select_network

  • 详细的流量访问,排行信息

net_app_lists