dream

一个菜鸟程序员的成长历程

0%

stream

stream的结果态

当前使用的结果态方法是collect。主要作用是收集。看一下源码。作为结果态方法,不再返回stream类型的对象。接受一个Collector类型的对象作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
//声明一个变量
A container;
//判断是否并行流。短路,直接走else
if (isParallel()
&& (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
&& (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
container = collector.supplier().get();
BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
forEach(u -> accumulator.accept(container, u));
}
else {
//调用evaluate方法
container = evaluate(ReduceOps.makeRef(collector));
}
return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
? (R) container
: collector.finisher().apply(container);
}

先看一下ReduceOps 类的 makeRef方法。构造一个结果态对象,对引用类型的值执行可变的计算,规约。

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

/** Constructs a TerminalOp that implements a mutable reduce on reference values.
形参:
collector – a Collector defining the reduction
返回值:
a ReduceOp implementing the reduction
*/
public static <T, I> TerminalOp<T, I> makeRef(Collector<? super T, I, ?> collector) {

//参数校验不为空并且获取到collector的 supplier
Supplier<I> supplier = Objects.requireNonNull(collector).supplier();
// 获取到 collector的 accumulator
BiConsumer<I, ? super T> accumulator = collector.accumulator();
// 获取到 collector的 combiner
BinaryOperator<I> combiner = collector.combiner();
// 内部类
class ReducingSink extends Box<I>
implements AccumulatingSink<T, I, ReducingSink> {
@Override
public void begin(long size) {
state = supplier.get();
}

@Override
public void accept(T t) {
accumulator.accept(state, t);
}

@Override
public void combine(ReducingSink other) {
state = combiner.apply(state, other.state);
}
}
//创建一个 ReduceOp 的对象
return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}

@Override
public int getOpFlags() {
return collector.characteristics().contains(Collector.Characteristics.UNORDERED)
? StreamOpFlag.NOT_ORDERED
: 0;
}
};
}

看一下ReduceOp类。他根据指定的流类型创建ReduceOp对象。 使用指定的supplier创建 accumulating sinks。 对流进行预估并将结果发送至 accumulating sinks。然后执行计算操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

private static abstract class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
implements TerminalOp<T, R> {
private final StreamShape inputShape;

/**
* Create a {@code ReduceOp} of the specified stream shape which uses
* the specified {@code Supplier} to create accumulating sinks.
*
* @param shape The shape of the stream pipeline
*/
ReduceOp(StreamShape shape) {
//这里 shape 是 REFERENCE 也就是引用类型
inputShape = shape;
}
}

创建完结果态对象以后,将结果态对象传入evaluate方法。预估管道的结果态操作并产生一个结果。

  • container = evaluate(ReduceOps.makeRef(collector));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Evaluate the pipeline with a terminal operation to produce a result.
*
* @param <R> the type of result
* @param terminalOp the terminal operation to be applied to the pipeline.
* @return the result
*/
//这里的参数就是上面刚生成的结果态对象。
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
//断言类型是否是引用类型
//getOutputShape是ReferencePipeline类的方法,直接返回的就是 REFERENCE, 刚才生成的结果态的shape也是 REFERENCE
assert getOutputShape() == terminalOp.inputShape();
//判断是否已经消费 如果已经消费了 抛出非法状态异常。
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
//标记为已消费
linkedOrConsumed = true;
//如果是并行流调用结果态的并行流方法,否则调用顺序流方法。
return isParallel()
? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
: terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}

调用顺序流处理方法之前,参数先调用了sourceSpliterator方法。而在调用sourceSpliterator之前,还调用了结果态的getOpFlags方法。

getOpFlags方法在创建结果态的时候增加的,代码在下面,一起回顾一下。这个方法很简单,就是看collector的characteristics是否无序。

  • 如果无序,返回StreamOpFlag.NOT_ORDERED标志,即32
  • 否则返回0

我们这里返回的是0,因为list并不需要有序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//创建一个 ReduceOp 的对象
return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}

@Override
public int getOpFlags() {
return collector.characteristics().contains(Collector.Characteristics.UNORDERED)
? StreamOpFlag.NOT_ORDERED
: 0;
}
};

接下来看sourceSpliterator方法。作用如下:

  • 如果是一个顺序流或无状态并行流,返回初始化的spliterator对象。
  • 如果是一个有状态并行流,返回一个spliterator对象,包含所有最近状态操作的计算结果。
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
/**
* Get the source spliterator for this pipeline stage. For a sequential or
* stateless parallel pipeline, this is the source spliterator. For a
* stateful parallel pipeline, this is a spliterator describing the results
* of all computations up to and including the most recent stateful
* operation.
*/
@SuppressWarnings("unchecked")
//参数是0
private Spliterator<?> sourceSpliterator(int terminalFlags) {
// Get the source spliterator of the pipeline
//声明 spliterator = null
Spliterator<?> spliterator = null;
//判断 sourceStage指针的sourceSpliterator是否为null
//sourceStage指针指向头节点,所以是判断 头节点的 sourceSpliterator是否为null。
//在最开始初始化的时候,头节点的 sourceSpliterator 指向了一个 ArrayListSpliterator 对象,所以这里不是null
//不是null的话我们会取出 头节点的 sourceSpliterator
if (sourceStage.sourceSpliterator != null) {
// 本地变量 spliterator = 头节点的 ArrayListSpliterator 对象 里面包含我们的源数据 list。
spliterator = sourceStage.sourceSpliterator;
// 删除头节点的 sourceSpliterator
sourceStage.sourceSpliterator = null;
}
//这里在初始化的时候并没有 初始化 头节点的 sourceSupplier ,所以这里是null,并不会走到这里
else if (sourceStage.sourceSupplier != null) {
spliterator = (Spliterator<?>) sourceStage.sourceSupplier.get();
sourceStage.sourceSupplier = null;
}
else {
throw new IllegalStateException(MSG_CONSUMED);
}

//判断是否是并行流,触发短路。
if (isParallel() && sourceStage.sourceAnyStateful) {
// Adapt the source spliterator, evaluating each stateful op
// in the pipeline up to and including this pipeline stage.
// The depth and flags of each pipeline stage are adjusted accordingly.
int depth = 1;
for (@SuppressWarnings("rawtypes") AbstractPipeline u = sourceStage, p = sourceStage.nextStage, e = this;
u != e;
u = p, p = p.nextStage) {

int thisOpFlags = p.sourceOrOpFlags;
if (p.opIsStateful()) {
depth = 0;

if (StreamOpFlag.SHORT_CIRCUIT.isKnown(thisOpFlags)) {
// Clear the short circuit flag for next pipeline stage
// This stage encapsulates short-circuiting, the next
// stage may not have any short-circuit operations, and
// if so spliterator.forEachRemaining should be used
// for traversal
thisOpFlags = thisOpFlags & ~StreamOpFlag.IS_SHORT_CIRCUIT;
}

spliterator = p.opEvaluateParallelLazy(u, spliterator);

// Inject or clear SIZED on the source pipeline stage
// based on the stage's spliterator
thisOpFlags = spliterator.hasCharacteristics(Spliterator.SIZED)
? (thisOpFlags & ~StreamOpFlag.NOT_SIZED) | StreamOpFlag.IS_SIZED
: (thisOpFlags & ~StreamOpFlag.IS_SIZED) | StreamOpFlag.NOT_SIZED;
}
p.depth = depth++;
p.combinedFlags = StreamOpFlag.combineOpFlags(thisOpFlags, u.combinedFlags);
}
}

//直接到这里,由于传进来的是0,所以直接返回了
if (terminalFlags != 0) {
// Apply flags from the terminal operation to last pipeline stage
// 合并结果态操作到最后一个流操作的标志
combinedFlags = StreamOpFlag.combineOpFlags(terminalFlags, combinedFlags);
}

//直接到这里返回 头节点的 ArrayListSpliterator
return spliterator;
}

回到上面evaluate方法的这段代码terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));

  • 第一个参数是this
  • 第二个参数是刚才返回的 头节点的 ArrayListSpliterator
  • 复习一下ArrayListSpliterator的属性
    • list = list
    • index = 0
    • fence = -1
    • expectedModCount = 0

接下来走到了 结果态 对象的 evaluateSequential 方法。

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
@Override
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
//调用stream流的wrapAndCopyInto方法
//第一个参数是 makeSink 方法生成的 ReducingSink 对象,第二个参数是 ArrayListSpliterator
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}

//makeSink是上面生成 结果态 对象的时候在 ReduceOp 对象里面加的
@Override
public ReducingSink makeSink() {
// 返回了一个 ReducingSink 对象。
return new ReducingSink();
}

// 这个类,提供了三个方法。等用到的时候说。
class ReducingSink extends Box<I>
implements AccumulatingSink<T, I, ReducingSink> {
@Override
public void begin(long size) {
state = supplier.get();
}

@Override
public void accept(T t) {
accumulator.accept(state, t);
}

@Override
public void combine(ReducingSink other) {
state = combiner.apply(state, other.state);
}
}

看一下流的wrapAndCopyInto方法。这个方法在 AbstractPipeline 类里面。

  • 第一个参数是 ReducingSink 对象
  • 第二个参数是 ArrayListSpliterator 对象
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
@Override
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
//调用过来 copyInfo 方法,第一个参数又调用了 wrapSink方法
copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
return sink;
}

//参数是 ReducingSink 对象
@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
//参数校验
Objects.requireNonNull(sink);

// for循环 p = 当前节点,如果p的深度 > 0, 就循环,然后p = p的前一个节点
// 也就是从当前节点往前遍历,一直到头节点。
// 当前节点按照我们写的就是 filter 节点。
for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
// 调用每个节点的 opWrapSink 方法,传入前一个节点的标志位和 sink 对象。第一次是 ReducingSink
// 调用 filter 节点的结束后 把包装好的 sink链 也就是 ChainedReference 对象赋值给 sink。
// 调用 map 节点的时候,传入的sink变成了 filter 返回的 ChainedReference 对象。
sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink<P_IN>) sink;
}

回顾一下 filter 节点的 opWrapSink 方法 。这是在中间态节点生成的时候,创建无状态对象的时候添加的。

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
return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) {
//增加了opWrapSink方法,第一个参数是标志 = 90,第二个是 ReducingSink 对象
//这个方法会在结果态的时候调用
Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
//创建一个 Sink.ChainedReference类的对象并返回。传入 ReducingSink 对象
//简单来说就是包装 sink 对象,并创建出一个 sink 执行链。
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
//这里重写了 ChainedReference 的 begin 方法
public void begin(long size) {
//调用 ReducingSink 的 begin 并传入 -1
downstream.begin(-1);
}

//重写了 accept 方法
@Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};

static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
protected final Sink<? super E_OUT> downstream;

//这里的 downstream = ReducingSink 赋值给对象的 downstream 属性。
public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}

@Override
public void begin(long size) {
downstream.begin(size);
}

@Override
public void end() {
downstream.end();
}

@Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}

看一下现在的 sink 链

stream5.png

回顾一下 map 节点的 opWrapSink 方法 。这是在中间态节点生成的时候,创建无状态对象的时候添加的。

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

return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {

//增加了opWrapSink方法,第一个参数是标志 = 95,第二个是 ChainedReference 对象
//这个方法会在结果态的时候调用
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
//再次创建一个 Sink.ChainedReference类的对象并返回。传入一个 ChainedReference 对象
return new Sink.ChainedReference<P_OUT, R>(sink) {

//这里重写了 accpet方法
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};

static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
protected final Sink<? super E_OUT> downstream;

//这里的 downstream = ChainedReference 赋值给对象的 downstream 属性。
public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}

@Override
public void begin(long size) {
downstream.begin(size);
}

@Override
public void end() {
downstream.end();
}

@Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}

看一下现在的 sink 链。

stream6.png

现在来到 copyInto 方法。这个方法两个参数

  • 第一个是包装好的 sink 链。
  • 第二个是 ArrayListSpliterator 对象
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
@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
//参数校验
Objects.requireNonNull(wrappedSink);

//getStreamAndOpFlags方法获取标志位 154
//isKnown返回false
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
// getExactSizeIfKnown = 7 因为list的大小是 7
// 调用了 sink 的 begin。现在的 sink 是 map 的 sink,没有重写 begin
// 所以 begin 是 downstream.begin(size); size = 7
// 而map sink的 downstream指向了 filter 的 sink,所以调用了filter sink 的 begin
// filter 重写了,是 downstream.begin(-1); 调用了 ReducingSink 的 begin
// ReducingSink 的 begin 是给 state 赋值 supplier.get()的值 是 0
wrappedSink.begin(spliterator.getExactSizeIfKnown());
// 循环剩余的数据,传入 sink 链
spliterator.forEachRemaining(wrappedSink);
// 执行结束方法
// 先调用 map sink, 然后 filter sink 然后 collect 的end
wrappedSink.end();
}
else {
copyIntoWithCancel(wrappedSink, spliterator);
}
}

//判断这个标志是否在流上设置,是否在操作上设置。是否在流和操作的组合上设置。
boolean isKnown(int flags) {
// 154 & 50331648 == 16777216
// 返回 false
return (flags & preserve) == set;
}

//如果能获取到size 就返回 size,不然返回 -1
default long getExactSizeIfKnown() {
return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}

//获取大小
public long estimateSize() {
return (long) (getFence() - index);
}

看循环方法

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
public void forEachRemaining(Consumer<? super E> action) {
//声明变量
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
//参数校验,空指针
if (action == null)
throw new NullPointerException();

//list 是我们的数据 不为空并且取出一个元素也不为空
if ((lst = list) != null && (a = lst.elementData) != null) {
//把大小赋值给hi = 7
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
// mc = 7
mc = expectedModCount;

// index >=0 并且 hi <= list的数量
// i = 0 index = hi = 7
if ((i = index) >= 0 && (index = hi) <= a.length) {
//开始循环 0 < 7, 执行完循环后 ++i, i = 1
for (; i < hi; ++i) {
//取出一个元素
@SuppressWarnings("unchecked") E e = (E) a[i];
//调用 sink 链的 accpet方法 传入 该元素。
action.accept(e);
}
//循环完以后判断一下是否全部都处理完了 完成就返回
if (lst.modCount == mc)
return;
}
}
//抛出异常
throw new ConcurrentModificationException();
}


//map的 accpet方法
@Override
public void accept(P_OUT u) {
//先进行处理 mapper.apply相当于调用了我们map传入的方法
//将结果沿着sink链传播
downstream.accept(mapper.apply(u));
}

//filter的 accept 方法
@Override
public void accept(P_OUT u) {
//predicate.test就相当于调用了我们 filter 的时候传入的方法。
//看过滤结果是否成功,如果成功继续沿着sink链流动。
//不成功则不流动。
if (predicate.test(u))
downstream.accept(u);
}

//结果态的collect的 accept 方法
class ReducingSink extends Box<I> implements AccumulatingSink<T, I, ReducingSink> {
@Override
public void begin(long size) {
// 初始化
state = supplier.get();
}

@Override
public void accept(T t) {
// 执行累加器的 accpet
// 这个就是 state.add(t) 具体后面会讲
accumulator.accept(state, t);
}
}

最后,回到开始的collect方法中。evaluate会返回我们刚才一系列操作以后收集到的满足条件的list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
//声明一个变量
A container;
//判断是否并行流。短路,直接走else
if (isParallel()
&& (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
&& (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
container = collector.supplier().get();
BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
forEach(u -> accumulator.accept(container, u));
}
else {
//调用evaluate方法
container = evaluate(ReduceOps.makeRef(collector));
}
//判断标志位,collector.characteristics() = IDENTITY_FINISH
// 所以这里直接返回 收集的 list
return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
? (R) container
: collector.finisher().apply(container);
}

php yii2框架前端加载css和js文件的方法

这两天有一个以前的项目是用yii2框架写的,前后端没有做分离,现在需要用vue接手后续的前端开发。

把vue的项目放到yii2里面,这时候遇到一个加载静态资源的问题,原来html的引用方式不管用了。

后来看到yii2官方文档里面,需要改一下引用方式。

改成下面这样就可以了。

1
2
$this->registerCssFile("@web/static_vue/css/index.css")
$this->registerJsFile("@web/static_vue/js/index.js")

所有都使用这两个php代码进行引入,引入后就可以了。

wsl

wsl是可以在windows里面运行linux的一个软件。是微软官方发行的。

安装go

先去到安装目录/usr/local/src

从go官网下载go tar包。

1
sudo wget https://golang.org/dl/go1.16.3.linux-amd64.tar.gz

然后解压

1
sudo tar -zxvf go1.16.3.linux-amd64.tar.gz

接下来需要配置环境变量。可以设置在/etc/profile文件里面也可以设置在其他地方,我用的是zsh,所以我的环境变量配置在~/.zshrc文件里面。

执行vim ~/.zshrc。然后添加变量信息

1
2
export GOROOT="/usr/local/src/go"
export PATH="$PATH:$GOROOT/bin"

接下来重新加载一下配置文件

1
source ~/.zshrc

现在go就安装好了。可以执行go version查看go的版本。

创建第一个go 项目

我把项目放在~/go目录下。所以把这个目录添加到配置文件里面。因为这个目录也相当于$HOME/go。所以我们直接添加。

执行vim ~/.zshrc。然后添加变量信息

1
export GOPATH="$HOME/go"

接下来重新加载一下配置文件

1
source ~/.zshrc

在项目目录下面创建第一个文件。

1
vim ~/go/hello.go

添加下面的代码

1
2
3
4
5
6
7
8
9

package main

import "fmt"

func main () {
fmt.Printf("hello world\n")

}

进行编译

1
go build ~/go/hello.go

编译完成出现hello文件,直接执行。

1
~/go/hello

就可以看到输出了。

wsl

wsl是可以在windows里面运行linux的一个软件。是微软官方发行的。

安装php

从php官网下载php tar包。

1
sudo wget https://www.php.net/distributions/php-7.4.12.tar.gz

然后解压

1
sudo tar -zxvf php-7.4.12.tar.gz

接下来需要安装一些扩展来支持php。

1
sudo apt-get install gcc make pkg-config libxml2-dev libssl-dev libsqlite3-dev libcurl4-openssl-dev libonig-dev zlib1g-dev libffi-dev libpng-dev libzip-dev

不安装上面的扩展会导致接下来报错。

切换目录

1
cd ./php-7.4.12

执行configure,注意这里prefix一定要是/usr/local/php7,要不然找不到配置文件php.ini。这里有这个坑。

1
sudo ./configure --enable-fpm --with-mysql --with-pear --with-zip --enable-sockets --enable-soap --with-pdo-mysql  --enable-gd --enable-ftp --with-ffi  --with-zlib  --with-curl --with-openssl --enable-mbstring --prefix=/usr/local/php7 --with-config-file-path=/usr/local/php7 --with-external-gd --with-webp  --with-jpeg  --with-xpm  --with-freetype  --enable-bcmath

执行完上面一步如果没有错误就可以了。

接下来执行make

1
sudo make && sudo make install

复制一些配置文件

1
2
sudo cp /usr/local/src/php7/etc/php-fpm.conf.default /usr/local/src/php7/etc/php-fpm.conf
sudo cp /usr/local/src/php7/etc/php-fpm.d/www.conf.default /usr/local/src/php7/etc/php-fpm.d/www.conf

修改php-fpm的用户

1
sudo vim /usr/local/src/php7/etc/php-fpm.d/www.conf

找到 usergroup 这两个参数,原来的值是 nobody改成www-data,然后保存退出。

建立软连接或者环境变量。我们要配置全局的环境变量有两种方式。

  • 在环境变量目录里面增加软连接
  • 把php目录增加到环境变量里面

我采用的是软连接的方式。

1
2
3
sudo ln -s /usr/local/php7/bin/php /usr/local/bin/php
sudo ln -s /usr/local/php7/bin/phpize /usr/local/bin/phpize
sudo ln -s /usr/local/php7/sbin/php-fpm /usr/local/bin/php-fpm

接下来就可以全局使用php命令了

php-fpm启动,重启方法

启动

1
sudo php-fpm

重启 先找到进程 然后发送USR2信号

1
2
3
ps -aux | grep php

sudo kill -USR2 进程id

安装nginx

访问nginx的官网进行下载。

复制下载地址。比如我下载的1.19.5,直接下载

1
sudo wget https://nginx.org/download/nginx-1.19.5.tar.gz

然后解压

1
sudo tar -zxvf nginx-1.19.5.tar.gz

接下来需要安装一些扩展来支持php。

1
sudo apt-get install libpcre3 libpcre3-dev

切换目录

1
cd ./nginx-1.19.5

执行安装

1
2
sudo ./configure --with-file-aio --with-http_ssl_module --with-http_v2_module --with-http_realip_module --prefix=/usr/local/src/nginx
sudo make && sudo make install

好了,安装完成。

同意,建立软连接。

1
sudo ln -s /usr/local/src/nginx/sbin/nginx /usr/local/bin/nginx

接下来启动nginx看看效果

1
sudo nginx

访问localhost就可以看到效果了。

配置网站

接下来配置一下网站。

修改nginx.conf

1
sudo vim /usr/local/src/nginx/conf/nginx.conf

在http块里面加上这句话,引入其他的配置文件

1
include     conf.d/*.conf;

然后我们创建这个目录

1
sudo mkdir /usr/local/src/nginx/conf/conf.d

配置我们的网站文件

1
sudo vim /usr/local/src/nginx/conf/conf.d/test.com.conf

复制下面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
server_name test.com;
root "/home/wwwroot/test/public/";
location / {
index index.php index.html error/index.html;
autoindex off;
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
break;
}
}
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
include fastcgi_params;
}
}

在修改hosts文件就好了。

redis扩展

如果要装redis扩展,那么手动下载

1
http://pecl.php.net/package/redis

找到tar包下载

1
sudo wget http://pecl.php.net/get/redis-5.3.2.tgz

解压缩

1
sudo tar -zxvf redis-5.3.2.tgz

进去执行phpize

1
2
cd ./redis-5.3.2
sudo phpize

然后编译

1
2
sudo ./configure --with-php-config=/usr/local/php7/bin/php-config
sudo make && sudo make install

接下来会出现下面的目录

1
/usr/local/php7/lib/php/extensions/no-debug-non-zts-20190902

修改我们的php.ini

1
sudo vim /usr/local/php7/lib/php.ini

修改下面这个

1
extension_dir=/usr/local/php7/lib/php/extensions/no-debug-non-zts-20190902

增加redis扩展

1
extension="redis.so"

重启nginx和php-fpm就好了。

如何生成ssh-key

打开命令终端,或者使用git bash都可以。

打开以后先查看你之前是否生成过ssh-key。生成之后会在目录~/.ssh/下面出现两个文件id_rsa私钥和id_rsa.pub公钥。

cd ~/.ssh/
ls

如果没看到这两个文件,那么开始执行生成指令。

ssh-keygen -t rsa -C “你的邮箱” //这里一般使用github的邮箱

运行之后,会出现提示让你输入一些东西,这里不需要管,不需要输入,直接回车即可。

一直回车。直到指令执行完。

再次查看,发现已经生成了两个文件。接下来使用的时候只需要把公钥给到别人就可以了。

Amdahl定律

Gene Amdal,计算领域的早期先锋之一,对提升系统某一部分性能所带来的效果做出了简单却有见地的观察。

这个观察被称为Amdahl定律。该定律的主要思想是,当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。

若系统执行某应用程序需要时间为Told。假设系统某部分执行所需时间与该时间的比例为a,而该部分性能提升比例为k。即该部分初始所需执行时间为aTold,现在所需时间为(aTold)/k。因此,总的执行时间应为:

Tnew = (1 - a)Told + (aTold)/k = Told [(1 - a) + a/k]

由此,可以计算加速比 S = Told / Tnew

S = 1 / [(1 - a) + a/k]

例子

考虑这样一种情况,系统的某个部分初始耗时比例为60%(a = 0.6),其加速比例因子为3,也就是性能提升了300%。则我们可以获得的整体系统加速比为:

1 / [(1 - 0.6) + 0.6 / 3] = 1 / (0.4 + 0.2) = 1 / 0.6 = 1.66666666 约等于 1.67倍

可以看到虽然我们优化的部分提升了3倍性能,但是整体性能只提升了1.67倍。

虽然我们对系统的一个主要部分做了重大改进,但是获得的加速比却明显小于这部分的加速比。这就是Amdahl定律的主要观点–要想显著加速整个系统,必须提升系统中相当大的部分的速度

练习题1.1

假设你要把土豆从爱达荷州送到明尼苏达州,全程2500公里。在限速范围内,你估计平均速度为100公里/小时,整个行程需要25个小时。

A:新闻说蒙大拿州取消了限速,这使得行程有1500公里速度可以达到150公里/小时,那么加速比是多少?

答:根据题目可知:

1
2
3
4
5
6
7
a = 1500 / 2500 = 0.6 k = 1.5 求S

公式 S = 1 / [(1 - a) + a/k] 代入:

S = 1 / (0.4 + 0.4)
S = 1 / 0.8
S = 1.25

加速比是 1.25倍

B:你可以购买道具,想让加速比达到1.67倍,那么你必须以多快的速度通过蒙大拿州?

答:根据题目可知:

1
2
3
4
5
6
7
8
S = 1.67 a = 0.6 求k

1.67 = 1 / (0.4 + 0.6 / k)
1.67 * (0.4 + 0.6 / k) = 1
0.668 + 1.002 / k = 1
1.002 / k = 0.332
k = 1.002 / 0.332
k = 3.02

也就是蒙大拿州的速度必须达到 100 * 3.02 = 302公里/小时才行。

练习题1.2

公司说下个版本的软件性能将提升2倍。这个任务分配给你,你已经确认只有80%的系统可以进行改进,那么,这部分需要改进多少才可以达到要求?

根据题目可以知道:

1
2
3
4
5
6
7
8
9
10
11
a = 0.8, S = 2,求k

公式 S = 1 / [(1 - a) + a/k] 代入:
2 = 1/ [(1 - 0.8) + 0.8/k]
2 = 1 / (0.2 + 0.8 / k)
2 (0.2 + 0.8 / k) = 1
0.4 + 1.6 / k = 1
1.6 / k = 0.6
1.6 = 0.6k
16 / 6 = k
k = 2.67

所以我们需要改进这部分至少2.67倍才能达到要求。

计算机系统漫游

计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。。虽然系统的 具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有 相似的硬件和软件组件,它们又执行着相似的功能。

第一个c程序

一般第一个程序都是输出hello world,这里我们使用c语言输出一个hello world。后面在来讲这里面都发生了什么。

1
2
3
4
5
6
7
#include <stdio.h>

int main(void)
{
printf("hello world\n");
return 0;
}

最终程序都会被转成2进制代码,一般都根据ASCII码来转换,下图是上面的代码根据ASCII码转换成的二进制代码。

coursera

程序被翻译成不同格式

计算机最后能执行的是二进制文件,所以需要把c文件转换成二进制文件,这是经过几个步骤的转换,而不是一次性转换成的。当然了,我们使用gcc编译的时候他是一下子执行了所有步骤的。

分为4个阶段

  • hello.c 经过 预处理器(cpp) 输出 hello.i (修改了的源程序)
  • hello.i 经过 编译器(ccl) 输出 hello.s (汇编程序)
  • hello.s 经过 汇编器(as) 输出 hello.o (可重定位目标程序)
  • hello.o 和 引入的其他库的文件 经过 连接器(ld) 输出 hello.exe (可执行程序)

预处理阶段

预处理器根据以字符#开头的命令,修改原始的c程序。把你引入的文件插入到原始文件中。生成新的hello.i文件。

编译阶段

编译器把hello.i这个c程序文件编译成汇编程序文件。生成新的hello.s文件。

汇编阶段

汇编器把hello.s这个汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序。生成新的hello.o文件,它包含的17个字节是函数main的指令编码。

链接阶段

我们的c程序调用了别的函数,调用了printf这个输出函数,这个函数是标准c库里面的函数,这个函数存在于printf.o这个预先编译好的文件里面,而我们要把这两个文件合并到一起,链接器就负责这种合并。最后得到一个可执行程序 hello.exe

系统的硬件组成

总线

总线贯穿整个计算机系统,负责在各个部件之间传递数据。通常总线被设计成传送定长的字节块,也就是字(word)。字中的字节数是一个基本的系统参数,现在大多数机器都是8个字节(64)位的了,4个字节(32)位的机器已经很少见了。

I/O设备

I/O(输入/输出)设备是系统和外部连接的通道。下图包括四个I/O设备。分别是

  • 作为用户输入的键盘
  • 作为用户输入的鼠标
  • 作为用户输出的显示器
  • 存储数据和程序的磁盘
    最开始,可执行程序就是存储在磁盘上面的。

每个I/O设备都通过适配器或者控制器和I/O总线相连。控制器和适配器的区别主要在于封装方式上面。控制器是I/O设备本身或者系统的主印制电路板(通常称作主板)上的芯片组。而适配器是一块插在主板插槽上的卡

主存

主存是一个临时存储设备,在处理器执行程序时,用来存放数据。

从物理上来说,主存是一组动态随机存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址,这些地址是从零开始的

处理器

中央处理单元(CPU),简称处理器。是解释或执行存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备或寄存器,称为程序计数器(PC)。在任何时刻,PC都指向主存中的某条指令。

从电脑开机开始,PC就指向一条指令,执行指令后执行下一条指令,不断运行。

这样的简单操作不多,都围绕着主存,寄存器文件和算术/逻辑运算单元(ALU)进行。下面是一些简单操作的例子。

  • 加载:从主存复制一个字节到寄存器。
  • 存储:从寄存器复制一个字节到主存。以覆盖原来的值。
  • 操作:把两个寄存器的内容复制到ALU做运算,将结果存在一个寄存器中。
  • 跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,覆盖PC原来的值。

coursera

运行hello程序

程序通过shell进行执行。

./hello

这个时候系统把这个字符通过键盘逐一读入寄存器,然后再放入主存。

当我们按下回车,这个时候系统开始执行hello的内容,把hello world从磁盘读入主存。利用直接存储器存取技术(DMA),可以直接从磁盘读入主存,而不需要经过寄存器。

把程序内容读入主存后,开始执行main函数的内容,把hello world从主存复制到寄存器,最后显示在屏幕上面。

高速缓存

这个运行过程说明程序多次在主存和寄存器之间复制移动代码。这些重复操作如果能变得更快,那么整个程序就能变得更快。这就是高速缓存的作用。

根据机械原理,较大的设备比较小的设备速度慢,而快速设备的造价也远高于低速设备。比如,磁盘的容量可以比主存大1000倍,但是主存的速度可能比磁盘大1000万倍。同样的,寄存器比主存的速度也要更快。

针对这些速度的差异,系统的设计者采用了高速缓存设备,作为暂时的集结区域,存放处理器近期可能会需要的信息。系统有L1,L2,L3三级缓存。L1最快最小,L3最慢最大,

计算机网络和因特网

什么是因特网

什么是因特网?回答这个问题有两种方式:其一,从具体构成上看:可以分成基本硬件软件组件。其二,我们能够根据为分布式应用提供服务的联网基础设施来描述因特网。

因特网是网络的网络,是通信技术计算机技术紧密结合的产物。是互连的自治的

  • 自治:无主从关系
  • 互连:互联互通

具体构成描述

因特网是世界范围的计算机网络。互联了世界的计算机网络。在之前计算设备多是电脑,发展到现在,加入了手机,电视,平板,汽车等设备。这些都被称为主机(host)端系统(end system)

端系统通过通信链路分组交换机连接到一起。通信链路由同抽电缆铜线,光纤等物理媒体组成。不同物理媒体的传输速率不同,传输速率以(比特/秒度量)。

端系统要发送的时候,把发送信息分段,每段和首部字节包裹到一起称为一个分组,把分组通过网络从发送端系统发送到接收端系统。

分组交换机分成路由器链路层交换机。路由器用于网络核心,链路层交换机用于接入网。

端系统通过因特网服务提供商ISP接入,包括家庭ISP,公司ISP等。每个ISP由多个分组交换机和多个通信链路组成。

  • 从范围分:局域网,城域网,广域网
  • 从拓扑结构分:星型,主线型,树形,网状。
  • 从交换网络分:电路交换,报文交换,分组交换。

协议

协议控制着网络之间计算机的通信。不同的协议完成不同的通信任务。

协议的三要素:

  • 语法:数据与控制信息的结构和格式
  • 语义:需要发出何种控制信息,完成何种动作何种响应。差错控制
  • 时序:事件顺序,速度匹配

问问题

当你发出你好的时候,发送了一条请求报文。当对方回复你好,你有什么事吗的时候,回复了一条响应报文。

这时候你问问题你知道天安门在哪里吗。发送了一条请求报文。对方回复天安门在这里呀,......。回复了一条响应报文。

网络边缘

我们的手机,电视,智能设备这些端系统联网,都处于网络边缘。端系统也叫做主机,可以分成客户端和服务端。

接入网

接入网是链接网络边缘的端系统到边缘路由器的物理链路。边缘路由器是端系统到任何其他远程端系统的路径上的第一台路由器。

网络核心

网络核心是网络之网络,无数的路由器和交换机相互连接在一起。

三种交换方式:

  • 电路交换,建立连接-通信-释放链接,独占信道,不应对突发性。不用的时候浪费。通过多路复用技术来实现共享物理链路。
  • 报文交换,发送整个报文。 时间 M/R * h h是跳步数。发送时间长,和跳步数成正比,并且随着报文越大,路由器存储也需要越大。
  • 分组交换,报文切成一个个分组,一个分组过一个路由器的时间是 L/R 。整个报文的时间是 M/R + nL/R ,n是路由器数量。

多路复用技术:

  • 频分多路复用(FDM)根据不同的频率划分
  • 时分多路复用(TDM)根据不同的时间划分
  • 波分多路复用(WDM)根据不同的波划分
  • 码分多路复用(CDM)每个手机分配码片,通过码片加密传输,再解密。用于手机网络。

分组交换

报文:包含从源主机到目的主机传输的任何东西。源主机将长报文划分为较小的一个个分组。分组通过通信链路和分组交换机(路由器和链路层交换机)传送。

分组以最大传输速率传输。

1
2
传输时间 = 分组长度 / 传输速率
传输时间 = L / R

例子

一个报文大小7.5Mbps。一个分组1500bits。总共有5000个分组。传输速率R = 1.5Mbps。会经过3段链路,2个路由器。

报文交换时间 = M / R = 7.5 / 1.5 = 5s 3段链路总共就是 5 * 3 = 15s。

分组交换时间 = L / R = 1500 / (1.5 * 106) = 0.001s
5000个分组就是 5000 * 0.001s = 5s,再加上2个路由器,总共是 5.002s

分组交换公式:M/R + nL/R n=路由器数量
报文交换公式:hM/R h=链路数量(跳步数)

计算机网络的性能

速率数据率或称数据传输速率比特率。单位时间(秒)传输信息(比特)量。

  • b/s(bps)
  • kb/s(kbps)
  • Mb/s(Mb/s)
  • Gb/s(Gb/s)
  • k = 103, M = 106, G = 109

带宽原本指信号具有的频带宽度,即最高频率与最低频率之差,单位是赫兹(HZ)。
网络的带宽通常是数字信道所能传输的最高数据率,单位b/s。

延迟/时延

分组交换为什么会丢包和时延?

1
2
路由器的缓存队列满了,在接到分组就会丢弃,产生丢包。
分组进行排队的时间延迟。

dproc :节点处理延迟

  • 差错检测
  • 确定输出链路
  • 通常 < msec

dqueue:排队延迟,在路由器里面排队

  • 等待输出链路可用
  • 取决于路由器拥塞程度
  • a:平均分组到达速率
  • La/R流量强度 = 0 平均排队延迟很小
  • La/R = 1平均排队延迟很大
  • La/R > 1超出服务能力

dtrans:传输延迟

  • L:分组长度(bits)
  • R:链路带宽(bps)
  • dtrans = L/R

dprop:传播延迟

  • d:物理链路长度
  • s:信号传播速度(2 * 108 m/sec)
  • dprop = d/s

时延带宽积

1
2
3
时延带宽积 = 传播时延 * 带宽
= d<sub>prop</sub> * R(btis)
= 以比特为单位的链路长度,也就是链路里面有多少个比特

分组丢失 丢包

  • 队列缓存容量有限,队列满了
  • 分组到达已满队列将丢弃
  • 丢弃分组可能由前序节点或源重发(也可能不重发)

丢包率 = 丢包数 / 已发分组总数

吞吐量/率

吞吐量表示在发送端与接收端之间传送数据速率(b/s)
即时吞吐量是给定时刻的速率
平均吞吐量是一段时间的平均速率

1
吞吐量取较小的一段链路的带宽吞吐量。

计算机网络的体系结构

实体:表示任何可发送或接收信息的硬件或软件。
协议:控制两个对等实体通信的规则的集合,协议是“水平的”。
实体需要使用下层服务,对上层提供服务,遵循本层协议,实现本层功能。服务是“垂直的”
下层实现对上层服务是“透明”的

OSI7层结构

从功能上描述网络结构:分层结构

  • 应用层 http https ftp 报文
  • 表示层
  • 会话层
  • 传输层 tcp upd 报文段
  • 网络层 ip 数据报
  • 数据链路层 mac 帧
  • 物理层

每层进行数据封装,增加头信息,也就是控制信息

  • 地址:发送和接收
  • 差错检测编码:差错检测或纠正
  • 协议控制:附加信息,优先级,服务质量,安全控制
    构造协议数据单元(PDU)
物理层功能
  • 接口特性
  • 比特编码
  • 数据率
  • 比特同步
    • 时钟同步
  • 传输模式
    • 单工 只能单向通信
    • 半双工 可以双向通信,但只能交替进行
    • 全双工
数据链路层

物理链路直接相连的两个节点之间的数据传输。

第一章课后复习题

  1. “主机”和“端系统”之间有什么不同?列举几种不同类型的端系统。web服务器是一种端系统吗?

没什么不同,主机即端系统。
手机是端系统,ipad是端系统,智能音箱是端系统。
web服务器也是一种端系统。

  1. “协议”一词常被用于描述外交关系。维基百科是怎样描述外交协议的。

直接去维基百科看

  1. 标准对于协议为什么重要?

如果都使用不同的协议,那么就没法交互了。所以需要标准来统一协议。

  1. 列出6种接入技术,将它们分为住宅接入,公司接入或广域无线网络接入。

同轴电缆 住宅接入
混合光纤同轴电缆(HFC)住宅接入
FTTH 住宅接入,公司接入
双绞线 住宅接入
WIFI 住宅接入,公司接入,广域无线网络接入
4G 广域无线网络接入

  1. HFC传输速率在用户间是专用的还是共享的?在下行HFC信道中,可能出现碰撞吗?为什么?

共享的。
不会,在下行信道,所有的分组从头到尾有同一个源发出,因此不会发生冲突。

  1. 以太LAN的传输速率是多少

用户10Mbps,100Mbps,服务器1Gbps,10Gbps

  1. 能够运行以太网的一些物理媒体是什么?

同轴电缆,光纤,双绞铜线

  1. 拨号调制解调器,HFC,DSL和FTTH都用于住宅接入,对于这些技术中的每一种,给出传输速率的范围,并讨论有关带宽是共享的还是专用的

拨号是专用的
HFC,DSL,FTTH都是用户共享的

  1. 假定在发送主机和接收主机间只有一台分组交换机。发送主机和交换机间以及交换机和接收主机间的传输速率分别是R1和R2。假设该交换机使用存储转发分组交换方式,发送一个长度为L的分组的端到端总时延是什么?(忽略排队时延,传播时延和节点处理时延)

从发送主机到交换机的传输时延是 L/R1
从交换机到接受主机的传输时延是 L/R2
端到端总时延 = L/R1 + L/R2

  1. 与分组交换网络相比,电路交换网络有哪些优点?在电路交换网络中,TDM比FDM有哪些优势?

电路交换网络独占带宽,速率更加稳定,建立好链接好只需要传输数据,不需要拥塞控制,流量控制,丢包等问题。

FDM是划分频率,TDM是划分时隙。发生丢失数据的话,TDM只会丢失一个时隙的数据,而FDM可能是大部分。

  1. 假定用户共享一条2Mbps链路。同时假定当每个用户传输时连续以1Mbps传输,但每个用户仅传输20%的时间。
    a. 当使用电路交换时,能够支持多少用户?
    b. 作为该题的遗留问题,假定使用分组交换。为什么如果两个或更少的用户同时传输的话,在链路前面基本没有排队时延?为什么如果3个用户同时传输的话,将会有排队时延?
    c. 求出某指定用户正在传输的概率。
    d. 假定现在有3个用户。求出在任何给定时间,所有3个用户在同时传输的概率。求出队列增长的时间比率。

a: 电路交换可以支持两个用户。
b: 因为两个用户每个用户1Mbps传输速率,两个刚好2Mbps占满了链路,当3个的时候,对于交换机来说输入速率就变成了3Mbps,而输出还是2Mbps,所以会产生排队。
C: 20%
d: 0.2 * 0.2 * 0.2 = 0.008

  1. 为什么在等级结构相同级别的两个ISP通常互相对等?某IXP是如何挣钱的?

因为互相对等可以直接连接,节省从上层ISP的时间和钱。
IXP通过流量对ISP收费

  1. 某些内容提供商构建了自己的网络。描述谷歌的网络。内容提供商构建这些网络的动机是什么?

绕过顶层ISP,直接和接入ISP互联,减少向顶层ISP的付费,并且可以对网络有更多的控制和操作。

  1. 考虑从某源主机跨越一条固定路由向某目的主机发送一分组。列出端到端时延组成部分。这些时延中的哪些是固定的,哪些是变化的?

总时延 = 节点处理时延 + 排队时延 + 传输时延 + 传播时延
变化的是节点处理时延 + 排队时延
固定的是传输时延 + 传播时延

  1. 访问在配套Web网站上有关传输时延与传播时延的Java小程序。在可用速率、传播时延和可用的分组长度之中找出一种组合,使得该分组的第一个比特到达接收方之前发送方结束了传输。找出另一种组合,使得发送方完成传输之前,该分组的第一个比特到达了接收方。

传输时延 < 传播时延的时候,传输完了第一个比特还没有到达接收方。
传输时延 > 传播时延的时候,传播完了还没传输完,也就是第一个比特到达接收方还在传输。

  1. 一个长度为1000字节的分组经距离2500km的链路传播,传播速率为2.5x10^8m/s并且传输速率为2Mbps,它需要多长时间?更为一般地,一个长度为L的分组经距离为d的链路传播,传输速率为s并且传播速率为Rbps,它需要用多长时间?该时延与传输速率相关吗?

传输时延 = 1000 / 2000000 = 0.0005s
传播时延 = 2500 / 250000 = 0.01s
需要 0.0105s 忽略节点处理时延和传播时延
L/s + d/Rbps
相关

  1. 假定主机A要向主机B发送一个大文件。从主机A到主机B的路径上有3段链路,其速率分别为R1 = 500kbps,R2 = 2Mbps,R3 = 1Mbps。
    a. 假定该网络中没有其他流量,该文件传送的吞吐量是多少?
    b. 假定该文件为4MB。传输该文件到主机B大致需要多长时间?
    c. 重复(a)和(b),只是这时R2减小到100kbps。

a: 吞吐量取决于最小的速率也就是500kbps。
b: 4000 * 8 / 500 = 64s
c: 吞吐量 = 100kbps 传输时间 = 4000 * 8 / 100 = 320s

  1. 假定端系统A要向端系统B发送一个大文件。在一个非常高的层次上,描述端系统怎样从该文件生成分组。当这些分组之一到达某分组交换机时,该交换机使用分组中的什么信息来决定将该分组转发到哪一条路上?因特网中的分组交换为什么可以与驱车从一个城市到另一个城市并沿途询问方向相类比?

报文-段-数据报-帧
使用目的ip地址和路由转发协议决定
路由转发协议使用了转发表,查询转发表和问路类似,一个路由器和一个城市类似

  1. 访问配套Web站点的排队和丢包Java小程序。最大发送速率和最小的传输速率是什么?对于这些速率,流量强度是多少?用这些速率运行该Java小程序并确定出现丢包要花费多长时间?然后第二次重复该实验,再次确定出现丢包花费多长时间。这些值有什么不同?为什么会有这种现象?

  1. 列出一个层次能执行的5个任务。这些任务中的一个(或两个)可能由两个(或更多)层次执行吗?

传输层 tcp协议:差错检测,分组重传,流量控制,拥塞控制,建立连接

可能,传输层有差错检测,网络层也有差错检测。

  1. 因特网协议栈中的5个层次有哪些?在这些层次中,每层的主要任务是什么?

应用层:完成自己的应用功能 http,icmp,ftp应用执行
传输层:完成端到端传输 tcp udp协议 确定源和目的端口号 进程传输
网络层:完成网络的端到端传输 IP协议 源和目的ip地址 主机传输
数据链路层:完成数据的端到端传输 MAC协议 到下一个节点的传输
物理层:完成比特流的端到端传输 实际物理传输

  1. 什么是应用层报文?什么是传输层报文段?什么是网络层数据报?什么是链路层帧?

应用层报文就是数据报文
传输层:增加源和目的端口号封装成段
网络层:增加源和目的ip地址,封装成数据报
链路层:增加MAC地址,封装成帧

  1. 路由器处理因特网协议栈中的哪些层次?链路层交换机处理的是哪些层次?主机处理的是哪些层次?

主机处理所有层次
路由器处理物理层,链路层,网络层三层
交换机处理物理层,链路层两层

  1. 病毒和蠕虫之间有什么不同?

病毒:需要用户交互才能感染设备
蠕虫:不需要用户交互

  1. 描述如何产生一个僵尸网络,以及僵尸网络是怎样被用于DDoS攻击的。

恶意软件控制网络设备产生僵尸网络
恶意软件控制僵尸网络不断攻击服务器,发送大量分组,创建大量链接,让正常请求被服务器忽略或拒绝。

  1. 假定Alice和Bob经计算机网络相互发送分组。假定Trudy将自己安置在网络中,使得她能够俘获由Alice发送的所有分组,并发送她希望给Bob的东西;她也能俘获Bob发送的所有分组,并发送她希望给Alice的东西。列出在这种情况下Trudy能够做的某些恶意的事情。

假扮成Alice或者Bob发送恶意软件
盗取Alice和Bob的信息