2018年9月

对大多数开发者来说,与 RxJS 的初次接触是通过库的形式,就像 Angular。一些函数会返回 stream ,要使用它们就得把注意力放在操作符上。

有些时候,混用响应式和非响应式代码似乎很有用。然后大家就开始热衷流的创造。不论是在编写异步代码或者是数据处理时,流都是一个不错的方案。

RxJS 提供很多方式来创建流。不管你遇到的是什么情况,都会有一个完美的创建流的方式。你可能根本用不上它们,但了解它们可以节省你的时间,让你少码一些代码。

我把所有可能的方法,按它们的主要目的,放在四个分类当中:

  • 流式化现有数据
  • 生成数据
  • 使用现有 API 进行交互
  • 选择现有的流,并结合起来

注意:示例用的是 RxJS 6,可能会以前的版本有所不同。已知的区别是你导入函数的方式不同了。

RxJS 6

import {of, from} from 'rxjs';

of(...);
from(...);

RxJS < 6

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/from';

Observable.of(...);
Observable.from(...);

//或

import { of } from 'rxjs/observable/of';
import { from } from 'rxjs/observable/from';

of(...);
from(...);

流的图示中的标记:

  • | 表示流结束了
  • X 表示流出现错误并被终结
  • ... 表示流的走向不定

流式化已有数据

你有一些数据,想把它们放到流中。有三种方式,并且都允许你把调度器当作最后一个参数传入(你如果想深入了解调度器,可以看看我的 上一篇文章)。这些生成的流都是静态的。

of

如果只有一个或者一些不同的元素,使用 of

of(1,2,3)
  .subscribe();
// 结果
// 1 2 3 |

from

如果有一个数组或者 可迭代的对象 ,而且你想要其中的所有元素发送到流中,使用 from。你也可以用它来把一个 promise 对象变成可观测的。

const foo = [1,2,3];

from(foo)
  .subscribe();
// 结果
// 1 2 3 |

pairs

流式化一个对象的键/值对。用这个对象表示字典时特别有用。

const foo = { a: 1, b: 2};

pairs(foo)
  .subscribe();
// 结果
// [a,1] [b,2] |

那么其他的数据结构呢?

也许你的数据存储在自定义的结构中,而它又没有实现 可迭代的对象 接口,又或者说你的结构是递归的、树状的。也许下面某种选择适合这些情况:

  1. 先将数据提取到数组里
  2. 使用下一节将会讲到的 generate 函数,遍历所有数据
  3. 创建一个自定义流(见下一节)
  4. 创建一个迭代器

稍后会讲到选项 2 和 3 ,因此这里的重点是创建一个迭代器。我们可以对一个 可迭代的对象 调用 from 创建一个流。 可迭代的对象 是一个对象,可以产生一个迭代器(如果你对细节感兴趣,参考 这篇 mdn 文章)。

创建一个迭代器的简单方式是 生成函数 generator function 。当你调用一个生成函数时,它返回一个对象,该对象同时遵循 可迭代的对象 接口和 迭代器 接口。

// 自定义的数据结构
class List {
  add(element) ...
  get(index) ...
  get size() ...
  ...
}

function* listIterator(list) {
  for (let i = 0; i<list.size; i++) {
    yield list.get(i);
  }
}

const myList = new List();
myList.add(1);
myList.add(3);

from(listIterator(myList))
  .subscribe(console.log);
// 结果
// 1 3 |    

调用 listIterator 函数时,返回值是一个 可迭代的对象 / 迭代器 。函数里面的代码在调用 subscribe 前不会执行。

生成数据

你知道要发送哪些数据,但想(或者必须)动态生成它。所有函数的最后一个参数都可以用来接收一个调度器。他们产生静态的流。

范围(range

从初始值开始,发送一系列数字,直到完成了指定次数的迭代。

range(10, 2)  // 从 10 开始,发送两个值
  .subscribe();
// 结果
// 10 11 |

间隔(interval) / 定时器(timer

有点像范围,但定时器是周期性的发送累加的数字(就是说,不是立即发送)。两者的区别在于在于定时器允许你为第一个元素设定一个延迟。也可以只产生一个值,只要不指定周期。

interval(1000) // 每 1000ms = 1 秒 发送数据
  .subscribe()
// 结果
// 0  1  2  3  4 ...
delay(5000, 1000) // 和上面相同,在开始前先等待 5000ms

delay(5000)
.subscribe(i => console.log("foo");
// 5 秒后打印 foo

大多数定时器将会用来周期性的处理数据:

interval(10000).pipe(
  flatMap(i => fetch("https://server/stockTicker")
).subscribe(updateChart)

这段代码每 10 秒获取一次数据,更新屏幕。

生成(generate

这是个更加复杂的函数,允许你发送一系列任意类型的对象。它有一些重载,这里你看到的是最有意思的部分:

generate(
  0,           // 从这个值开始
  x => x < 10, // 条件:只要值小于 10,就一直发送
  x => x*2     // 迭代:前一个值加倍
).subscribe();
// 结果
// 1 2 4 8 |

你也可以用它来迭代值,如果一个结构没有实现 可迭代的对象 接口。我们用前面的列表例子来进行演示:

const myList = new List();
myList.add(1);
myList.add(3);

generate(
  0,                  // 从这个值开始
  i => i < list.size, // 条件:发送数据,直到遍历完整个列表
  i => ++i,           // 迭代:获取下一个索引
  i => list.get(i)    // 选择器:从列表中取值
).subscribe();
// 结果
// 1 3 |

如你所见,我添加了另一个参数:选择器。它和 map 操作符作用类似,将生成的值转换为更有用的东西。

空的流

有时候你要传递或返回一个不用发送任何数据的流。有三个函数分别用于不同的情况。你可以给这三个函数传递调度器。emptythrowError 接收一个调度器参数。

empty

创建一个空的流,一个值也不发送。

empty()
  .subscribe();
// 结果
// |

never

创建一个永远不会结束的流,仍然不发送值。

never()
  .subscribe();
// 结果
// ...

throwError

创建一个流,流出现错误,不发送数据。

throwError('error')
  .subscribe();
// 结果
// X

挂钩已有的 API

不是所有的库和所有你之前写的代码使用或者支持流。幸运的是 RxJS 提供函数用来桥接非响应式和响应式代码。这一节仅仅讨论 RxJS 为桥接代码提供的模版。

你可能还对这篇出自 Ben Lesh全面的文章 感兴趣,这篇文章讲了几乎所有能与 promises 交互操作的方式。

from

我们已经用过它,把它列在这里是因为,它可以封装一个含有 observable 对象的 promise 对象。

from(new Promise(resolve => resolve(1)))
  .subscribe();
// 结果
// 1 |

fromEvent

fromEvent 为 DOM 元素添加一个事件监听器,我确定你知道这个。但你可能不知道的是,也可以通过其它类型来添加事件监听器,例如,一个 jQuery 对象。

const element = $('#fooButton'); // 从 DOM 元素中创建一个 jQuery 对象

from(element, 'click')
  .subscribe();
// 结果
// clickEvent ...

fromEventPattern

要理解为什么有 fromEvent 了还需要 fromEventPattern,我们得先理解 fromEvent 是如何工作的。看这段代码:

from(document, 'click')
  .subscribe();

这告诉 RxJS 我们想要监听 document 中的点击事件。在提交过程中,RxJS 发现 document 是一个 EventTarget 类型,因此它可以调用它的 addEventListener 方法。如果我们传入的是一个 jQuery 对象而非 document,那么 RxJs 知道它得调用 on 方法。

这个例子用的是 fromEventPattern ,和 fromEvent 的工作基本上一样:

function addClickHandler(handler) {
  document.addEventListener('click', handler);
}

function removeClickHandler(handler) {
  document.removeEventListener('click', handler);
}

fromEventPattern(
  addClickHandler,
  removeClickHandler,
)
.subscribe(console.log);

// 等效于
fromEvent(document, 'click')

RxJS 自动创建实际的监听器( handler )你的工作是添加或者移除监听器。fromEventPattern 的目的基本上是告诉 RxJS 如何注册和移除事件监听器。

现在想象一下你使用了一个库,你可以调用一个叫做 registerListener 的方法。我们不能再用 fromEvent,因为它并不知道该怎么处理这个对象。

const listeners = [];

class Foo {
  registerListener(listener) {
    listeners.push(listener);
  }

  emit(value) {
    listeners.forEach(listener => listener(value));
  }
}

const foo = new Foo();

fromEventPattern(listener => foo.registerListener(listener))
  .subscribe();

foo.emit(1);
// 结果
// 1 ...

当我们调用 foo.emit(1) 时,RxJS 中的监听器将被调用,然后它就能把值发送到流中。

你也可以用它来监听多个事件类型,或者结合所有可以通过回调进行通讯的 API,例如,WebWorker API:

const myWorker = new Worker('worker.js');

fromEventPattern(
  handler => { myWorker.onmessage = handler },
  handler => { myWorker.onmessage = undefined }
)
.subscribe();
// 结果
// workerMessage ...

bindCallback

它和 fromEventPattern 相似,但它能用于单个值。就在回调函数被调用时,流就结束了。用法当然也不一样 —— 你可以用 bindCallBack 封装函数,然后它就会在调用时魔术般的返回一个流:

function foo(value, callback) {
  callback(value);
}

// 没有流
foo(1, console.log); //prints 1 in the console

// 有流
const reactiveFoo = bindCallback(foo); 
// 当我们调用 reactiveFoo 时,它返回一个 observable 对象

reactiveFoo(1)
  .subscribe(console.log); // 在控制台打印 1
// 结果
// 1 |

websocket

是的,你完全可以创建一个 websocket 连接然后把它暴露给流:

import { webSocket } from 'rxjs/webSocket'; 

let socket$ = webSocket('ws://localhost:8081');

// 接收消息
socket$.subscribe(
  (msg) => console.log('message received: ' + msg),
  (err) => console.log(err),
  () => console.log('complete') * );

// 发送消息
socket$.next(JSON.stringify({ op: 'hello' }));

把 websocket 功能添加到你的应用中真的很简单。websocket 创建一个 subject。这意味着你可以订阅它,通过调用 next 来获得消息和发送消息。

ajax

如你所知:类似于 websocket,提供 AJAX 查询的功能。你可能用了一个带有 AJAX 功能的库或者框架。或者你没有用,那么我建议使用 fetch(或者必要的话用 polyfill),把返回的 promise 封装到一个 observable 对象中(参考稍后会讲到的 defer 函数)。

定制流

有时候已有的函数用起来并不是足够灵活。或者你需要对订阅有更强的控制。

主题(Subject

Subject 是一个特殊的对象,它使得你的能够把数据发送到流中,并且能够控制数据。Subject 本身就是一个可观察对象,但如果你想要把流暴露给其它代码,建议你使用 asObservable 方法。这样你就不能意外调用原始方法。

const subject = new Subject();
const observable = subject.asObservable();

observable.subscribe();

subject.next(1);
subject.next(2);
subject.complete();
// 结果
// 1 2 |

注意在订阅前发送的值将会“丢失”:

const subject = new Subject();
const observable = subject.asObservable();

subject.next(1);

observable.subscribe(console.log);

subject.next(2);
subject.complete();
// 结果
// 2

除了常规的 Subject,RxJS 还提供了三种特殊的版本。

AsyncSubject 在结束后只发送最后的一个值。

const subject = new AsyncSubject();
const observable = subject.asObservable();

observable.subscribe(console.log);

subject.next(1);
subject.next(2);
subject.complete();
// 输出
// 2

BehaviorSubject 使得你能够提供一个(默认的)值,如果当前没有其它值发送的话,这个值会被发送给每个订阅者。否则订阅者收到最后一个发送的值。

const subject = new BehaviorSubject(1);
const observable = subject.asObservable();

const subscription1 = observable.subscribe(console.log);

subject.next(2);
subscription1.unsubscribe();
// 输出
// 1
// 2
const subscription2 = observable.subscribe(console.log);

// 输出
// 2

ReplaySubject 存储一定数量、或一定时间或所有的发送过的值。所有新的订阅者将会获得所有存储了的值。

const subject = new ReplaySubject();
const observable = subject.asObservable();

subject.next(1);

observable.subscribe(console.log);

subject.next(2);
subject.complete();
// 输出
// 1
// 2

你可以在 ReactiveX 文档(它提供了一些其它的连接) 里面找到更多关于 Subject 的信息。Ben LeshOn The Subject Of Subjects 上面提供了一些关于 Subject 的理解,Nicholas Jamiesonin RxJS: Understanding Subjects 上也提供了一些理解。

可观察对象

你可以简单地用 new 操作符创建一个可观察对象。通过你传入的函数,你可以控制流,只要有人订阅了或者它接收到一个可以当成 Subject 使用的观察者,这个函数就会被调用,比如,调用 nextcompleterror

让我们回顾一下列表示例:

const myList = new List();
myList.add(1);
myList.add(3);

new Observable(observer => {
  for (let i = 0; i<list.size; i++) {
    observer.next(list.get(i));
  }

  observer.complete();
})
.subscribe();
// 结果
// 1 3 |

这个函数可以返回一个 unsubcribe 函数,当有订阅者取消订阅时这个函数就会被调用。你可以用它来清楚或者执行一些收尾操作。

new Observable(observer => {
  // 流式化

  return () => {
                 //clean up
               };
})
.subscribe();

继承可观察对象

在有可用的操作符前,这是一种实现自定义操作符的方式。RxJS 在内部扩展了 可观察对象Subject 就是一个例子,另一个是 publisher 操作符。它返回一个 ConnectableObservable 对象,该对象提供额外的方法 connect

实现 Subscribable 接口

有时候你已经用一个对象来保存状态,并且能够发送值。如果你实现了 Subscribable 接口,你可以把它转换成一个可观察对象。Subscribable 接口中只有一个 subscribe 方法。

interface Subscribable<T> {  subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void), error?: (error: any) => void, complete?: () => void): Unsubscribable}

结合和选择现有的流

知道怎么创建一个独立的流还不够。有时候你有好几个流但其实只需要一个。有些函数也可作为操作符,所以我不打算在这里深入展开。推荐看看 Max NgWizard K 所写的一篇 文章,它还包含一些有趣的动画。

还有一个建议:你可以通过拖拽元素的方式交互式的使用结合操作,参考 RxMarbles

ObservableInput 类型

期望接收流的操作符和函数通常不单独和可观察对象一起工作。相反,它们实际上期望的参数类型是 ObservableInput,定义如下:

type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>;

这意味着你可以传递一个 promises 或者数组却不需要事先把他们转换成可观察对象。

defer

主要的目的是把一个 observable 对象的创建延迟(defer)到有人想要订阅的时间。在以下情况,这很有用:

  • 创建可观察对象的开销较大
  • 你想要给每个订阅者新的可观察对象
  • 你想要在订阅时候选择不同的可观察对象
  • 有些代码必须在订阅之后执行

最后一点包含了一个并不起眼的用例:Promises(defer 也可以返回一个 promise 对象)。看看这个用到了 fetch API 的例子:

function getUser(id) {
  console.log("fetching data");
  return fetch(`https://server/user/${id}`);
}

const userPromise = getUser(1);
console.log("I don't want that request now");

// 其它地方
userPromise.then(response => console.log("done");
// 输出
// fetching data
// I don't want that request now
// done

只要流在你订阅的时候执行了,promise 就会立即执行。我们调用 getUser 的瞬间,就发送了一个请求,哪怕我们这个时候不想发送请求。当然,我们可以使用 from 来把一个 promise 对象转换成可观察对象,但我们传递的 promise 对象已经创建或执行了。defer 让我们能够等到订阅才发送这个请求:

const user$ = defer(() => getUser(1));

console.log("I don't want that request now");

// 其它地方
user$.subscribe(response => console.log("done");
// 输出
// I don't want that request now
// fetching data
// done

iif

iif 包含了一个关于 defer 的特殊用例:在订阅时选择两个流中的一个:

iif(
  () => new Date().getHours() < 12,
  of("AM"),
  of("PM")
)
.subscribe();
// 结果
// AM before noon, PM afterwards

引用该文档:

实际上 iif 能够轻松地用 defer 实现,它仅仅是出于方便和可读性的目的。

onErrorResumeNext

开启第一个流并且在失败的时候继续进行下一个流。错误被忽略掉。

const stream1$ = of(1, 2).pipe(
  tap(i => { if(i>1) throw 'error'}) //fail after first element
);

const stream2$ = of(3,4);

onErrorResumeNext(stream1$, stream2$)
  .subscribe(console.log);
// 结果
// 1 3 4 |

如果你有多个 web 服务,这就很有用了。万一主服务器开启失败,那么备份的服务就能自动调用。

forkJoin

它让流并行运行,当流结束时发送存在数组中的最后的值。由于每个流只有最后一个值被发送,它一般用在只发送一个元素的流的情况,就像 HTTP 请求。你让请求并行运行,在所有流收到响应时执行某些任务。

function handleResponses([user, account]) {
  // 执行某些任务
}

forkJoin(
  fetch("https://server/user/1"),
  fetch("https://server/account/1")
)
.subscribe(handleResponses);

merge / concat

发送每一个从可观察对象源中发出的值。

merge 接收一个参数,让你定义有多少流能被同时订阅。默认是无限制的。设为 1 就意味着监听一个源流,在它结束的时候订阅下一个。由于这是一个常见的场景,RxJS 为你提供了一个显示的函数:concat

merge(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2)),
  timer(0, 1000).pipe(mapTo("Stream 3"), take(2)),
  2 //two concurrent streams
)
.subscribe();

// 只订阅流 1 和流 2

// 输出
// Stream 1 -> after 1000ms
// Stream 2 -> after 1200ms
// Stream 1 -> after 2000ms

// 流 1 结束后,开始订阅流 3

// 输出
// Stream 3 -> after 0 ms
// Stream 2 -> after 400 ms (2400ms from beginning)
// Stream 3 -> after 1000ms

merge(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2))
  1
)
// 等效于
concat(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2))
)

// 输出
// Stream 1 -> after 1000ms
// Stream 1 -> after 2000ms
// Stream 2 -> after 3200ms
// Stream 2 -> after 4400ms

zip / combineLatest

mergeconcat 一个接一个的发送所有从源流中读到的值,而 zipcombineLatest 是把每个流中的一个值结合起来一起发送。zip 结合所有源流中发送的第一个值。如果流的内容相关联,那么这就很有用。

zip(
  interval(1000),
  interval(1200),
)
.subscribe();
// 结果
// [0, 0] [1, 1] [2, 2] ...

combineLatest 与之类似,但结合的是源流中发送的最后一个值。直到所有源流至少发送一个值之后才会触发事件。这之后每次源流发送一个值,它都会把这个值与其他流发送的最后一个值结合起来。

combineLatest(
  interval(1000),
  interval(1200),
)
.subscribe();
// 结果
// [0, 0] [1, 0] [1, 1] [2, 1] ...

两个函数都让允许传递一个选择器函数,把元素结合成其它对象而不是数组:

zip(
  interval(1000),
  interval(1200),
  (e1, e2) -> e1 + e2
)
.subscribe();
// 结果
// 0 2 4 6 ...

race

选择第一个发送数据的流。产生的流基本是最快的。

race(
  interval(1000),
  of("foo")
)
.subscribe();
// 结果
// foo |

由于 of 立即产生一个值,因此它是最快的流,然而这个流就被选中了。

总结

已经有很多创建可观察对象的方式了。如果你想要创造响应式的 API 或者想用响应式的 API 结合传统 API,那么了解这些方法很重要。

我已经向你展示了所有可用的方法,但它们其实还有很多内容可以讲。如果你想更加深入地了解,我极力推荐你查阅 文档 或者阅读相关文章。

RxViz 是另一种值得了解的有意思的方式。你编写 RxJS 代码,产生的流可以用图形或动画进行显示。


via: https://blog.angularindepth.com/the-extensive-guide-to-creating-streams-in-rxjs-aaa02baaff9a

作者:Oliver Flaggl 译者:BriFuture 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux DNS 查询剖析(第一部分)中,我们介绍了:

  • nsswitch
  • /etc/hosts
  • /etc/resolv.conf
  • pinghost 查询方式的对比

而在 Linux DNS 查询剖析(第二部分),我们介绍了:

  • systemd 和对应的 networking 服务
  • ifupifdown
  • dhclient
  • resolvconf

剖析进展如下:

linux-dns-2 (2)

(大致)准确的关系图

很可惜,故事还没有结束,还有不少东西也会影响 DNS 查询。在第三部分中,我将介绍 NetworkManagerdnsmasq,简要说明它们如何影响 DNS 查询。

1) NetworkManager

在第二部分已经提到,我们现在介绍的内容已经偏离 POSIX 标准,涉及的 DNS 解析管理部分在各个发行版上形式并不统一。

在我使用的发行版 (Ubuntu)中,有一个名为 NetworkManager 可用 available 服务,它通常作为一些其它软件包的依赖被安装。它实际上是 RedHat 在 2004 年开发的一个服务,用于帮助你管理网络接口。

它与 DNS 查询有什么关系呢?让我们安装这个服务并找出答案:

$ apt-get install -y network-manager

对于 Ubuntu,在软件包安装后,你可以发现一个新的配置文件:

$ cat /etc/NetworkManager/NetworkManager.conf
[main]
plugins=ifupdown,keyfile,ofono
dns=dnsmasq

[ifupdown]
managed=false

看到 dns=dnsmasq 了吧?这意味着 NetworkManager 将使用 dnsmasq 管理主机上的 DNS。

2) dnsmasq

dnsmasq 程序是我们很熟悉的程序:只是 /etc/resolv.conf 之上的又一个间接层。

理论上,dnsmasq 有多种用途,但主要被用作 DNS 缓存服务器,缓存到其它 DNS 服务器的请求。dnsmasq 在本地所有网络接口上监听 53 端口(标准的 DNS 端口)。

那么 dnsmasq 运行在哪里呢?NetworkManager 的运行情况如下:

$ ps -ef | grep NetworkManager
root     15048     1  0 16:39 ?        00:00:00 /usr/sbin/NetworkManager --no-daemon

但并没有找到 dnsmasq 相关的进程:

$ ps -ef | grep dnsmasq
$

令人迷惑的是,虽然 dnsmasq 被配置用于管理 DNS,但其实并没有安装在系统上!因而,你需要自己安装它。

安装之前,让我们查看一下 /etc/resolv.conf 文件的内容:

$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.0.2.2
search home

可见,并没有被 NetworkManager 修改。

如果安装 dnsmasq

$ apt-get install -y dnsmasq

然后启动运行 dnsmasq

$ ps -ef | grep dnsmasq
dnsmasq  15286     1  0 16:54 ?        00:00:00 /usr/sbin/dnsmasq -x /var/run/dnsmasq/dnsmasq.pid -u dnsmasq -r /var/run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5

然后,/etc/resolv.conf 文件内容又改变了!

root@linuxdns1:~# cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1
search home

运行 netstat 命令,可以看出 dnsmasq 在所有网络接口上监听 53 端口:

$ netstat -nlp4
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address     Foreign Address State   PID/Program name
tcp        0      0 127.0.0.1:53      0.0.0.0:*       LISTEN  15286/dnsmasq 
tcp        0      0 10.0.2.15:53      0.0.0.0:*       LISTEN  15286/dnsmasq
tcp        0      0 172.28.128.11:53  0.0.0.0:*       LISTEN  15286/dnsmasq
tcp        0      0 0.0.0.0:22        0.0.0.0:*       LISTEN  1237/sshd
udp        0      0 127.0.0.1:53      0.0.0.0:*               15286/dnsmasq
udp        0      0 10.0.2.15:53      0.0.0.0:*               15286/dnsmasq  
udp        0      0 172.28.128.11:53  0.0.0.0:*               15286/dnsmasq  
udp        0      0 0.0.0.0:68        0.0.0.0:*               10758/dhclient
udp        0      0 0.0.0.0:68        0.0.0.0:*               10530/dhclient
udp        0      0 0.0.0.0:68        0.0.0.0:*               10185/dhclient

3) 分析 dnsmasq

在目前的情况下,所有的 DNS 查询都会使用 127.0.0.1:53 这个 DNS 服务器,下一步会发生什么呢?

我再次查看 /var/run 目录,可以发现一个线索:resolvconf 目录下 resolv.conf 文件中的配置也相应变更,变更为 dnsmasq 对应的 DNS 服务器:

$ cat /var/run/resolvconf/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1
search home

同时,出现了一个新的 dnsmasq 目录,也包含一个 resolv.conf 文件:

$ cat /run/dnsmasq/resolv.conf
nameserver 10.0.2.2

(LCTT 译注:这里依次提到了 /var/run/run,使用 Ubuntu 16.04 LTS 验证发现,/var/run 其实是指向 /run/ 的软链接)

该文件包含我们从 DHCP 获取的 nameserver

虽然可以推导出这个结论,但如何查看具体的调用逻辑呢?

4) 调试 dnsmasq

我经常思考 dnsmasq (在整个过程中)的功能定位。幸运的是,如果你将 /etc/dnsmasq.conf 中的一行做如下调整,你可以获取大量 dnsmasq 状态的信息:

#log-queries

修改为:

log-queries

然后重启 dnsmasq

接下来,只要运行一个简单的命令:

$ ping -c1 bbc.co.uk

你就可以在 /var/log/syslog 中找到类似的内容(其中 [...] 表示行首内容与上一行相同):

Jul  3 19:56:07 ubuntu-xenial dnsmasq[15372]: query[A] bbc.co.uk from 127.0.0.1
[...] forwarded bbc.co.uk to 10.0.2.2
[...] reply bbc.co.uk is 151.101.192.81
[...] reply bbc.co.uk is 151.101.0.81
[...] reply bbc.co.uk is 151.101.64.81
[...] reply bbc.co.uk is 151.101.128.81
[...] query[PTR] 81.192.101.151.in-addr.arpa from 127.0.0.1
[...] forwarded 81.192.101.151.in-addr.arpa to 10.0.2.2
[...] reply 151.101.192.81 is NXDOMAIN

可以清晰看出 dnsmasq 收到的查询、查询被转发到了哪里以及收到的回复。

如果查询被缓存命中(或者说,本地的查询结果还在 存活时间 time-to-live TTL 内,并未过期),日志显示如下:

[...] query[A] bbc.co.uk from 127.0.0.1
[...] cached bbc.co.uk is 151.101.64.81
[...] cached bbc.co.uk is 151.101.128.81
[...] cached bbc.co.uk is 151.101.192.81
[...] cached bbc.co.uk is 151.101.0.81
[...] query[PTR] 81.64.101.151.in-addr.arpa from 127.0.0.1

如果你想了解缓存中有哪些记录,可以向 dnsmasq 进程 id 发送 USR1 信号,这样 dnsmasq 会将缓存记录导出并写入到相同的日志文件中:

$ kill -SIGUSR1 $(cat /run/dnsmasq/dnsmasq.pid)

(LCTT 译注:原文中命令执行报错,已变更成最接近且符合作者意图的命令)

导出记录对应如下输出:

Jul  3 15:08:08 ubuntu-xenial dnsmasq[15697]: time 1530630488
[...] cache size 150, 0/5 cache insertions re-used unexpired cache entries.
[...] queries forwarded 2, queries answered locally 0
[...] queries for authoritative zones 0
[...] server 10.0.2.2#53: queries sent 2, retried or failed 0
[...] Host             Address         Flags      Expires
[...] linuxdns1        172.28.128.8    4FRI   H
[...] ip6-localhost    ::1             6FRI   H
[...] ip6-allhosts     ff02::3         6FRI   H
[...] ip6-localnet     fe00::          6FRI   H
[...] ip6-mcastprefix  ff00::          6FRI   H
[...] ip6-loopback     :               6F I   H
[...] ip6-allnodes     ff02:           6FRI   H
[...] bbc.co.uk        151.101.64.81   4F         Tue Jul  3 15:11:41 2018
[...] bbc.co.uk        151.101.192.81  4F         Tue Jul  3 15:11:41 2018
[...] bbc.co.uk        151.101.0.81    4F         Tue Jul  3 15:11:41 2018
[...] bbc.co.uk        151.101.128.81  4F         Tue Jul  3 15:11:41 2018
[...]                  151.101.64.81   4 R  NX    Tue Jul  3 15:34:17 2018
[...] localhost        127.0.0.1       4FRI   H
[...] <Root>           19036   8   2   SF I
[...] ip6-allrouters   ff02::2         6FRI   H

在上面的输出中,我猜测(并不确认,? 代表我比较无根据的猜测)如下:

  • 4 代表 IPv4
  • 6 代表 IPv6
  • H 代表从 /etc/hosts 中读取 IP 地址
  • I ? “永生”的 DNS 记录 ? (例如,没有设置存活时间数值 ?)
  • F
  • R
  • S
  • N
  • X

(LCTT 译注:查看 dnsmasq 的源代码 cache.c 可知,4 代表 IPV46 代表 IPV6C 代表 CNAMES 代表 DNSSECF 代表 FORWARDR 代表 REVERSEI 代表 IMMORTALN 代表 NEGX 代表 NXDOMAINH 代表 HOSTS。更具体的含义需要查看代码或相关文档)

dnsmasq 的替代品

NetworkManager 配置中的 dns 字段并不是只能使用 dnsmasq,可选项包括 nonedefaultunbounddnssec-triggered 等。使用 none 时,NetworkManager 不会改动 /etc/resolv.conf;使用 default 时,NetworkManager 会根据当前的 活跃连接 active connections 更新 resolv.conf;使用 unbound 时,NetworkManager 会与 unbound 服务通信;dnssec-triggered 与 DNS 安全相关,不在本文讨论范围。

第三部分总结

第三部分到此结束,其中我们介绍了 NetworkManager 服务及其 dns=dnsmasq 的配置。

下面简要罗列一下我们已经介绍过的全部内容:

  • nsswitch
  • /etc/hosts
  • /etc/resolv.conf
  • /run/resolvconf/resolv.conf
  • systemd 及对应的 networking 服务
  • ifupifdown
  • dhclient
  • resolvconf
  • NetworkManager
  • dnsmasq

via: https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/

作者:ZWISCHENZUGS 译者:pinewall 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在组织内建立联系遇到问题了吗?你或许是采用了错误的策略。

职业社交网络 —— 在同事或专业人员之间建立人际联系 —— 可以采用多种形式、在产业内跨组织进行。建立职业网络需要花费时间和精力,并且当某位成员加入或离开一个组织时,此人的网络通常需要被在一个新的工作环境中重建。

职业社交网络在不同组织中起相似作用 —— 信息共享、导师制、机会、工作利益和其他作用 —— 然而传统组织与开放组织在组织内构建特定联系的方法原因可能不尽相同。这些差异有其影响:同事联系方式、如何建立信任、组织内多元化的程度和种类以及建立合作的能力,所有这些因素都是相互关联的,而且它们参与并塑造了人们所建立的社交网络。

一个开放的组织对包容性的强调可以使社交网络在解决商业问题上比传统等级制组织更加高效。这种观念在开源的思考中有很久的历史。例如,在 《教堂与集市》 The Cathedral and the Bazaar 中,埃里克·雷蒙德写道:“许多年前社会学家发现,相比一个随机选择的观察者的观点,许多同等专业的(或是同等无知的)观察家的普遍观点是可靠得多的预言。”所以让我们了解社交网络的结构和目的如何影响各类组织的价值观。

传统组织中的社交网络

当我在传统组织工作并要描述我为工作做了什么时,人们问我的第一件事就是我与其他人(通常是总监级的领导)的关系。“你在希拉手下吗?”他们会这么问。“你为马尔科姆工作吗?”这意味着以一种上下级的视角看待传统组织的作用;当试图安排工作或雇员时,人们想要从上下级的角度理解网络结构。

换言之,在传统组织中社交网络依赖于等级制结构,因此他们彼此跟随。事实上,甚至弄清一个雇员在关系网中处于怎样的位置也算得上是一种“上下级组织”式的担忧。

然而并非所有潜在等级制都是如此。它还视相关人员而定。对于上下级网络的关注会决定雇员在网络中的“价值”,因为网络本身是一个持续的权力关系的系统,它会根据人不同水平的价值给予他们不同的定位。它淡化了个人的能力和技能的重要性。因此,一个人在传统组织的联系促使其能力具有前瞻性,为人所知,有影响力并在其事业中起到支持作用。

相比传统等级制组织,一个开放的组织对包容性的强调能使网络解决商业问题更加高效。

传统组织的正式结构以特定方式决定着雇员的社交网络 —— 有些可能是优点,有些可能是缺点,这取决于具体环境——例如:

  • 要更快速地了解“谁是谁”并看到人们如何关联是较为便捷的(通常这在特定层级内建立信任网络)。
  • 通常,这种对关系的进一步的理解意味着会有更少的过剩工作(在一个特定网络中项目有清晰的相应的归属者)和过多交流(人们知道谁对交流什么负责)。
  • 相关人员会感到在一个权力结构中感到束手无策,或好像他们不能“闯入”权力结构中,这些结构有时(或更多时候)因为裁员并不起作用。
  • 完成大量的工作和努力是困难的,并且合作会很艰难。
  • 权力转让缓慢;一个人的参与能力更多地决定于等级结构所创造的网络的结盟而非其他因素(比如个人能力),减少了被看做社区和成员利益的东西。
  • 竞争似乎更加清晰;理解“谁在竞争什么”通常发生在一个公认的、被限定了的等级结构中(权力网络中职位的缺乏增进了竞争因此竞争会更激烈)。
  • 当更严格的网络决定了灵活性的限度时,适应能力会受损。网络的“夙愿”和合作的限度也会以同样的方式受影响。
  • 在严格的网络中,方向明确,并且领导人通常靠过度指导来进行管理,在这里,破坏更容易发生。
  • 当社交网络不那么灵活时,风险下降;人们知道什么需要发生,怎样发生,何时发生(但是考虑到在一个组织中工作的广度,这不见得总是“坏事”;一些工作的职能需要较小的风险,例如:人力资源管理 HR),企业并购和法律工作等。
  • 在网络中的信任是更大的,尤其当受雇者是正式网络的一部分的时候(当某人不是网络的一份子时,被排斥的人可能特别难管理或改正)。

开放组织中的社交网络

尽管开放组织必定会有等级结构,但他们并不根据那个网络运作。他们的职业网络结构更加灵活(或者说是“随时随地”)。

在一个开放的组织中,当我描述我做了什么工作时,几乎没人问我“我为谁而干?”一个开放的组织更多的以伙伴为中心,而不是以领导为中心。开放的价值观比如包容和特定的治理系统比如强人治理有助于此;那并不是你了解谁而是你了解什么,你怎样使用(比如:“自底而上的设计”)。在一个开放的组织当中,我并不感觉我在为展示自己的价值而奋斗;我的想法有内在的价值。有时我必须示范说明为何使用我的观点比使用别人的更加有用——但是那意味着我正在同事的社区里面诊疗我自己(包括领导层),而不是单独被自上而下的领导层诊疗。

如此说来,开放的组织并不基于网络评估员工,而是基于他们对作为个人的同事的了解。这个人有想法吗?她会努力通过利用开放组织的价值实现那些想法吗(领导它们)(也就是说,在开放组织中分享那些观点并且实践以将他人囊括并透明公开的工作等等)?

开放组织也会以特定的方式构造社交网络(这种方式同样可能会视个人的目的性和渴望程度而很有益或很有害),这包括:

  • 人们会对他们的网络、声望、技能和事业更加负责。
  • 竞争(为了资源、权力、晋升等)会因这些组织天性更具合作性而变得更少。最好的结果是协商,而不是单赢,并且竞争会磨练创意,而不会在人与人之间筑篱设笆。
  • 权力是更加流动和有活力的,在人与人之间流动(但这同时也意味着可能有对可说明性或者责任的误解,而且活动可能会因为没有明晰的主人翁意识而不被完成)。
  • 信任是“一次一同事”地被建立起来的而不会借助社交网络,在网络中,人是被定位着的。
  • 网络在多样的运转和事件中会自配置,一有机会便会反应性地自启(这帮助了更新但却会造成混乱,因为谁在决策、谁在“受控”是不那么明确的)。
  • 执行速度在混乱的环境中会下降,因为所做之事、做事方式和处事时间需要在制定目标和涵养好整以暇的员工方面上的领导力。
  • 灵活的社交网络同样会增加变革和风险;创意会流通得更快而且更神奇,并且执行会更加自信。
  • 信任建立在同事合作之上(它本该如此!),而不是在对架构的尊重之上。

让它有效

如果你正在考虑从一种组织架构转变为另一种,当你在构建并维持你的职业社交网络时思考一下如下所述内容。

来自传统组织的小建议

  • 对决策的架构和管控不是坏事;运作中的框架需要明晰透明,而且决策者需要考虑他们的决定。
  • 在执行上突出需要经理提供关注,还需要有在滤出任何让人分心或混乱的事务的同时仍能提供足够的来龙去脉的能力。
  • 已经确立的网络帮助了一大批人同步工作并且能管控风险。

来自开放组织的小建议

  • 能力强的领导人是那些可以根据多样的风格和对同事、团队的不同偏好提供不同层次的透明度和指导,同时又不会构建出不灵活的网络的人。
  • 伟大的想法比已建立的组织会赢得更多。
  • 人们对他们的名声会更加负责任。
  • 创意和信息的流转是变革的关键。松散组织中的关系网络可以使这两种元素生发的频度更高、幅度更广。

via: https://opensource.com/open-organization/18/6/building-professional-social-networks-openly

作者:Heidi Hess von Ludewig 选题:lujun9972 译者:ZenMoore 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

作者巧妙地指出了函数式编程 不可变 immultable 的特点。当你需要改变一个对象的属性的时候……嗯,销毁了再创建一个就是了。

这里的老板想来份变量(改变全局变量的值),而所谓 副作用 side effect ,指的是在这种编程范式下,函数内部与外部互动可能会产生运算以外的其他结果,或导致程序出现不可预料的行为。

或许,事件驱动的编程范式会好一点……?


via: http://turnoff.us/geek/functional-world/

作者:Daniel Stori 译者&点评:ItsLucas 校对&合成 :Bestony

本文由 LCTT 原创编译,Linux中国 荣誉推出

得到我的 awk 秘籍。

我管理着一个个人网站,用手工编辑网站上的网页。由于网站上的页面并不多,这种方法对我很适合,可以让我对网站代码的细节一清二楚。

最近我升级了网站的设计样式,我决定把所有的普通引号都转换成“花引号”,即在打印材料中使用的那种引号:用 “” 来代替 ""。

手工修改所有的引号太耗时了,因此我决定将这个转换所有 HTML 文件中引号的过程自动化。不过通过程序或脚本来实现该功能需要费点劲。这个脚本需要知道何时将普通引号转换成花引号,并决定使用哪种引号(LCTT 译注:左引号还是右引号,单引号还是双引号)。

有多种方法可以转换引号。Greg Pittman 写过一个 Python 脚本 来修正文本中的花引号。而我自己使用 GNU awk (gawk) 来实现。

下载我的 awk 秘籍。免费下载

开始之前,我写了一个简单的 gawk 函数来评估单个字符。若该字符是一个引号,这该函数判断是输出普通引号还是花引号。函数查看前一个字符;若前一个字符是空格,则函数输出左花引号。否则函数输出右花引号。脚本对单引号的处理方式也一样。

function smartquote (char, prevchar) {
        # print smart quotes depending on the previous character
        # otherwise just print the character as-is

        if (prevchar ~ /\s/) {
                # prev char is a space
                if (char == "'") {
                        printf("&lsquo;");
                }
                else if (char == "\"") {
                        printf("&ldquo;");
                }
                else {
                        printf("%c", char);
                }
        }
        else {
                # prev char is not a space
                if (char == "'") {
                        printf("&rsquo;");
                }
                else if (char == "\"") {
                        printf("&rdquo;");
                }
                else {
                        printf("%c", char);
                }
        }
}

这个 gawk 脚本的主体部分通过该函数处理 HTML 输入文件的一个个字符。该脚本在 HTML 标签内部逐字原样输出所有内容(比如,<html lang="en">)。在 HTML 标签外,脚本使用 smartquote() 函数来输出文本。smartquote() 函数来评估是输出普通引号还是花引号。

function smartquote (char, prevchar) {
        ...
}

BEGIN {htmltag = 0}

{
        # for each line, scan one letter at a time:

        linelen = length($0);

        prev = "\n";

        for (i = 1; i <= linelen; i++) {
                char = substr($0, i, 1);

                if (char == "<") {
                        htmltag = 1;
                }

                if (htmltag == 1) {
                        printf("%c", char);
                }
                else {
                        smartquote(char, prev);
                        prev = char;
                }

                if (char == ">") {
                        htmltag = 0;
                }
        }

        # add trailing newline at end of each line
        printf ("\n");
}

下面是一个例子:

gawk -f quotes.awk test.html > test2.html

其输入为:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Test page</title>
  <link rel="stylesheet" type="text/css" href="/test.css" />
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width" />
</head>
<body>
  <h1><a href="/"><img src="logo.png" alt="Website logo" /></a></h1>
  <p>"Hi there!"</p>
  <p>It's and its.</p>
</body>
</html>

其输出为:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Test page</title>
  <link rel="stylesheet" type="text/css" href="/test.css" />
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width" />
</head>
<body>
  <h1><a href="/"><img src="logo.png" alt="Website logo" /></a></h1>
  <p>&ldquo;Hi there!&rdquo;</p>
  <p>It&rsquo;s and its.</p>
</body>
</html>

via: https://opensource.com/article/18/8/gawk-script-convert-smart-quotes

作者:Jim Hall 选题:lujun9972 译者:lujun9972 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

生产力应用在移动设备上特别受欢迎。但是当你坐下来做工作时,你经常在笔记本电脑或台式电脑上工作。假设你使用 Fedora 系统。你能找到帮助你完成工作的程序吗?当然!请继续阅读了解这些帮助你专注目标的程序。

所有这些程序都可以在 Fedora 系统上免费使用。当然,它们也维护了你的自由。 (许多还允许你使用你可能已经拥有帐户的现有服务。)

FocusWriter

FocusWriter 只是一个全屏文字处理器。该程序可以提高你的工作效率,因为它覆盖了屏幕其他地方。当你使用 FocusWriter 时,除了你和文本,别无它物。有了这个程序,你可以专注于你的想法,减少分心。

Screenshot of FocusWriter

FocusWriter 允许你调整字体、颜色和主题以最适合你的喜好。它还会记住你上一个文档和位置。此功能可让你快速重新专注于书写。

要安装 FocusWriter,请使用 Fedora Workstation 中的软件中心。或者在终端中使用 sudo 运行此命令:

sudo dnf install focuswriter

GNOME ToDo

你可以猜到这个独特的程序是为 GNOME 桌面环境设计的。因此,它非常适合你的 Fedora Workstation。ToDo 有一个简单的目的:它可以让你列出你需要完成的事情。

使用 ToDo,你可以为所有任务确定优先级并安排截止日期。你还可以根据需要构建任意数量的任务列表。ToDo 有大量提供了有用功能的扩展,以提高你的工作效率。这些包括 GNOME Shell 通知,以及带有 todo.txt 的列表管理。如果你有 Todoist 或者 Google 帐户,ToDo 甚至可以与它们交互。它可以同步任务,因此你可以跨设备共享。

要安装它,在软件中心搜索 ToDo,或在命令行运行:

sudo dnf install gnome-todo

Zanshin

如果你是使用 KDE 的生产力粉丝,你可能会喜欢 Zanshin。该行事历可帮助你规划跨多个项目的操作。它有完整的功能界面,可让你浏览各种任务,以了解下一步要做的最重要的事情。

Screenshot of Zanshin on Fedora 28

Zanshin 非常适合键盘操作,因此你可在钻研时提高效率。它还集成了众多 KDE 程序以及 Plasma 桌面。你可以将其与 KMail、KOrganizer 和 KRunner 一起使用。

要安装它,请运行以下命令:

sudo dnf install zanshin

via: https://fedoramagazine.org/3-cool-productivity-apps/

作者:Paul W. Frields 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出