工作中的遇到的一些小知识 7

DNS

DNS作为用来映射域名和IP地址的分布式数据库,使用TCP/UDP端口号53。其中每一级域名限制63个字符,总长度不超过253个字符。

DNS系统中,常见的资源记录类型有:

  • A(Address)记录:最重要的记录,用于将特定主机名映射到对应主机的IPv4地址上
  • MX记录:用于将特定邮箱地址映射到对应邮箱服务器上
  • CNAME(Canonical Name Record)别名记录:用于将某个别名指向某个A记录上
  • AAAA记录:和A记录对应,用于将特定主机名映射到对应主机的IPv6地址上

实现上,全球范围近1000台根域名服务器分为13组,编号A到M,剩下的Internet DNS命名空间被委托给其他DNS服务器。DNS系统也有各种各样的DNS软件所支持,其中最普遍的是BIND(Berkeley Internet Name Domain)。在查询时,有两种实现方式:递归迭代,客户端使用递归,DNS服务器间使用迭代。如查询shenlvmeng.github.io(忽略本地host和DNS缓存和路由器DNS缓存):

  • 客户端发送查询报文query shenlvmeng.github.io到边缘DNS服务器(一般是ISP的DNS服务器),DNS先检查缓存,若存在记录则直接返回结果
  • 若不存在或记录已过期,则:
    • DNS服务器向根域名服务器发送同样的查询报文,根服务器返回顶级域.io的权威域名服务器地址
    • DNS服务器向.io域的权威域名服务器发送查询报文,得到二级域.github.io的权威域名服务器地址
    • DNS服务器向.github.io域的权威域名服务器发送查询报文,得到主机shenlvmeng的A记录,存入自身缓存,设置TTL,返回给客户端

最初的DNS域名使用字符仅限于ASCII字符的子集,2008年后,ICANN通过决议,可以使用其他语言作为顶级域名的字符,如“xxx.中国”。使用punnycode码的IDNA系统,可以将unicode字符映射为有效的DNS字符集,有效避免IDN欺骗(即使用长得很像的不同字符作为钓鱼网站)。

域名的所有者和IP也可以通过查找WHOIS域名数据库查询。对于大多数根域名,由ICANN维护,WHOIS的细节由控制那个域的域注册机构维护。

域名污染

指一些刻意制造或无意制造的域名服务器数据包,指向错误的IP地址。这种错误有可能是域名服务器错误工作带来,也有可能是刻意为之。

对于GFW来说,它会对所有经过它的在UDP端口53上的域名查询进行IDS入侵检测,一旦发现与黑名单关键词相匹配的域名查询请求,会伪装成目标域名的解析服务器返回虚假的查询结果。

  • 系统默认会从使用ISP提供的域名查询服务器去查询国外的权威服务器时,便被GFW污染,缓存虚假的IP地址。
  • 由于TCP连接的机制可靠,理论上无法对TCP协议的域名查询进行污染,理论上可以通过TCP协议查询真实的IP地址。但其实,对于真实的IP地址,会有其他方式封锁,或对查询行为使用连接重置进行拦截
  • 通常情况下,设置的NDS服务主要使用海外的DNS服务,所以都需要穿过GFW,不过一些小型的DNS有技术手段回避GFW污染,从而能够访问国外被封锁网站

ISP域名劫持还包含一些互联网提供商劫持部分域名,转到自己制定的网站,已提供自己的广告。

DNS记录

在DNS的分布式数据库中,不同记录类型有不同的用途。下面介绍了一些常见的记录类型。

  • A记录,传回一个32位的IPv4地址,映射主机名到IP
  • AAAA记录,传回一个128位的IPv6地址,映射主机名到IP
  • CNAME:一个主机名的别名,只能指向一个域名,不能指向IP地址,可以保证原域名地址映射IP地址修改时,别名也能同步修改。CNAME意为真实名称,所以应当读作alias.com的“CNAME“是real.com。为保证效率,应当避免CNAME指向其他CNAME。
  • DNAME,和CNAME类似,不过不是映射域名,而是把域名下的整个解析子树映射到另一域名。如把alias.comDNAME到real.com后,不影响alias.com的原有的解析设置。而xxx.alias.com都会被映射到xxx.real.com
  • MX(邮件交换)记录,将邮箱后缀(@后的部分)映射到类型为A或者AAAA的地址记录,原则上禁止映射到CNAME上
  • NS记录,委托DNS区域使用已提供的权威域名服务器
  • SRV记录,进行服务定位

所有的记录都有一个有效期(TTL,time-to-live),时间耗尽后,所包含的信息必须从权威服务器上得到更新。

useStateuseEffect实现思路

这两个React hooks中引入的特性背后是基于闭包 + 数组索引实现的,下面是一个实现的demo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// useState
import React from "react";
import { render } from "react-dom";

const values: any[] = [];
let cursor = 0;

function useState<T>(initialState: T): [T, (newState: T) => void] {
const currentCursor = cursor;
values[currentCursor] = values[currentCursor] || initialState;
const setFunc = (newState: T) => {
values[currentCursor] = newState;
// 触发重新渲染
renderApp();
}
cursor++;
return [values[currentCursor], setFunc];

}

const App: React.FC<{}> = () => {
const [num, setNum] = useState<number>(0);
return (
<div onClick={() => setNum(num + 1)}>{num}</div>
);
}

function renderApp() {
render(<App />, document.getElementById("root"));
// 重置计数器
cursor = 0;
}

renderApp();

// useEffect
import React from "react";
import { render } from "react-dom";

const deps: any[][] = [];
let cursor = 0;

function useEffect(cb: () => void, dep: any[]) {
const currCursor = cursor;
if (!deps[currCursor]) {
deps[currCursor] = dep;
cursor++;
cb();
return;
}

dep.some((d, index) => {
if (d !== deps[currCursor][index]) {
deps[currCursor][index] = d;
cursor++;
cb();
return true;
}
return false;
});
}

const App: React.FC<{}> = () => {
useEffect(() => {
setTimeout(() => console.log('ok'), 1000);
}, []);

return (
<div>ok</div>
);
}

function renderApp() {
render(<App />, document.getElementById("root"));
cursor = 0;
}

renderApp();

实际使用中,是把useStateuseEffect这样的hooks放在memorizedState数组中,共用一个cursor。类似下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let memoizedState = [];
let cursor = 0; // 当前下标

function useState(initialValue) {
memoizedState[cursor] = memoizedState[cursor] || initialValue;
const currentCursor = cursor;
function setState(newState) {
memoizedState[currentCursor] = newState;
render();
}
return [memoizedState[cursor++], setState];
}

function useEffect(callback, depArray) {
const hasNoDeps = !depArray;
const deps = memoizedState[cursor];
const hasChangedDeps = deps
? !depArray.every((el, i) => el === deps[i])
: true;
if (hasNoDeps || hasChangedDeps) {
callback();
memoizedState[cursor] = depArray;
}
cursor++;
}

其中memorized数组也是hooks一定要在top level调用的原因。

在React实际实现上,hooks是以链表的形式存储,通过next属性链接到下一个hook。同时每一个memorized数组都会绑定到一个fiber上,从而在再次渲染时更新对应节点。让hooks之间互不干扰。

proxy简单了解与应用

Vue 3.0在重构后使用了ES6的proxy新特性来跟踪数据字段的更新。它可以封装一个目标对象,为之添加一个代理,返回一个Proxy对象。

1
new Proxy(target, handler)

其中handler即代理配置对象,也是Proxy对象的“魔力”所在。对于一个空的handler,返回的Proxy近似于target本身。在handler上定义任何handler函数的集合,都会让返回Proxy对象有不同的表现。

handler有下面一些可选的属性:

  • apply 监听函数调用的钩子
    • apply: function(target, thisArg, argumentsList) {}
  • construct 监听使用new调用的钩子
    • construct: function(target, argumentsList, newTarget) {}
  • defineProperty 类似Object.defineProperty
    • defineProperty: function(target, property, descriptor) {}
  • get 监听属性访问
    • get: function(target, property, receiver) {}
  • deleteProperty 监听delete操作
    • deleteProperty: function(target, property) {}
  • getOwnPropertyDescriptor 监听Object.getOwnPropertyDescriptor
  • getPrototypeOf() 类似Object.getPrototypeOf
  • has 监听in操作
    • has: function(target, prop) {}
  • isExtensible 监听Object.isExtensible
  • ownKeys 监听Object.getOwnPropertyNamesObject.getOwnPropertySymbols
  • preventExtensions 监听Object.preventExtensions
  • set 监听属性设置
    • set: function(target, property, value, receiver) {}
  • setPrototypeOf 类似Object.setPrototypeOf

利用上面的handler已经可以实现很丰富的功能,immer的produce就有借助proxy来实现。