无需申请自动送彩金68_白菜送彩金网站大全[无需申请秒送彩金]
做最好的网站
来自 无需申请自动 2019-11-03 04:29 的文章
当前位置: 无需申请自动送彩金68 > 无需申请自动 > 正文

到页面加载完成的过程中都发生了什么事情,深

理解SVG坐标系统和变换: transform属性

2015/09/23 · HTML5 · SVG

原文出处: SaraSoueidan   译文出处:Blueed@Ivan_z3   

SVG元素可以通过缩放,移动,倾斜和旋转来变换-类似HTML元素使用CSS transform来变换。然而,当涉及到坐标系时这些变换所产生的影响必然有一定差别。在这篇文章中我们讨论SVG的transform属性和CSS属性,包括如何使用,以及你必须知道的关于SVG坐标系变换的知识。

这是我写的SVG坐标系统和变换部分的第二篇。在第一篇中,包括了任何要理解SVG坐标系统基础的需要知道的内容;更具体的是, SVG viewport, viewBox 和 preserveAspectRatio 属性。

  • 理解SVG坐标系和变换(第一部分)-viewport,viewBox,和preserveAspectRatio
  • 理解SVG坐标系和变换(第二部分)-transform属性
  • 理解SVG坐标系和变换(第三部分)-建立新视窗

这一部分我建议你先阅读第一篇,如果没有,确保你在阅读这篇之前已经读了第一篇。

从输入 URL 到页面加载完成的过程中都发生了什么事情?

2015/10/03 · HTML5, JavaScript · 6 评论 · HTTP, 浏览器

原文出处: 百度FEX/吴多益(@吴多益)   

背景  本文来自于之前我发的一篇微博:

图片 1

不过写这篇文章并不是为了帮大家准备面试,而是想借这道题来介绍计算机和互联网的基础知识,让读者了解它们之间是如何关联起来的。

为了便于理解,我将整个过程分为了六个问题来展开。

JavaScript 深入之闭包

2017/05/21 · JavaScript · 闭包

原文出处: 冴羽   

transform属性值

tranform属性用来对一个元素声明一个或多个变换。它输入一个带有顺序的变换定义列表的<transform-list>值。每个变换定义由空格或逗号隔开。给元素添加变换看起来如下:

有效地SVG变换有:旋转缩放移动, 和倾斜transform属性中使用的变换函数类似于CSS中transform属性使用的CSS变换函数,除了参数不同。

注意下列的函数语法定义只在transform属性中有效。查看section about transforming SVGs with CSS properties获取关于CSS变换属性中使用的语法信息。

第一个问题:从输入 URL 到浏览器接收的过程中发生了什么事情?

定义

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 函数能够访问的自由变量

举个例子:

var a = 1; function foo() { console.log(a); } foo();

1
2
3
4
5
6
7
var a = 1;
 
function foo() {
    console.log(a);
}
 
foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。

那么,函数 foo foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……

还真是这样的!

所以在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

咦,这怎么跟我们平时看到的讲到的闭包不一样呢!?

别着急,这是理论上的闭包,其实还有一个实践角度上的闭包,让我们看看汤姆大叔翻译的关于闭包的文章中的定义:

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

接下来就来讲讲实践上的闭包。

Matrix

你可以使用matrix()函数在SVG元素上添加一个或多个变换。matrix变换语法如下:

matrix(<a> <b> <c> <d> <e> <f>)

1
matrix(<a> <b> <c> <d> <e> <f>)

上述声明通过一个有6个值的变换矩阵声明一个变换。matrix(a,b,c,d,e,f)等同于添加变换matrix[a b c d e f]

如果你不精通数学,最好不要用这个函数。对于那些精通的人,你可以在这里阅读更多关于数学的内容。因此这个函数很少使用-我将忽略来讨论其他变换函数。

从触屏到 CPU

首先是「输入 URL」,大部分人的第一反应会是键盘,不过为了与时俱进,这里将介绍触摸屏设备的交互。

触摸屏一种传感器,目前大多是基于电容(Capacitive)来实现的,以前都是直接覆盖在显示屏上的,不过最近出现了 3 种嵌入到显示屏中的技术,第一种是 iPhone 5 的 In-cell,它能减小了 0.5 毫米的厚度,第二种是三星使用的 On-cell 技术,第三种是国内厂商喜欢用的 OGS 全贴合技术,具体细节可以阅读这篇文章。

当手指在这个传感器上触摸时,有些电子会传递到手上,从而导致该区域的电压变化,触摸屏控制器芯片根据这个变化就能计算出所触摸的位置,然后通过总线接口将信号传到 CPU 的引脚上。

以 Nexus 5 为例,它所使用的触屏控制器是 Synaptics S3350B,总线接口为 I²C,以下是 Synaptics 触摸屏和处理器连接的示例:图片 2

左边是处理器,右边是触摸屏控制器,中间的 SDA 和 SCL 连线就是 I²C 总线接口。

分析

让我们先写个例子,例子依然是来自《JavaScript权威指南》,稍微做点改动:

var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo();

1
2
3
4
5
6
7
8
9
10
11
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
 
var foo = checkscope();
foo();

首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。

另一个与这段代码相似的例子,在《JavaScript深入之执行上下文》中有着非常详细的分析。如果看不懂以下的执行过程,建议先阅读这篇文章。

这里直接给出简要的执行过程:

  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

了解到这个过程,我们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

以上的代码,要是转换成 PHP,就会报错,因为在 PHP 中,f 函数只能读取到自己作用域和全局作用域里的值,所以读不到 checkscope 下的 scope 值。(这段我问的PHP同事……)

然而 JavaScript 却是可以的!

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }

1
2
3
fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

所以,让我们再看一遍实践角度上闭包的定义:

  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  2. 在代码中引用了自由变量

在这里再补充一个《JavaScript权威指南》英文原版对闭包的定义:

This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.

闭包在计算机科学中也只是一个普通的概念,大家不要去想得太复杂。

Translation

要移动SVG元素,你可以用translate()函数。translate函数的语法如下:

CSS

translate(<tx> [<ty>])

1
translate(<tx> [<ty>])

translate()函数输入一个或两个值得来声明水平和竖直移动值。tx代表x轴上的translation值;ty代表y轴上的translation值。

ty值是可选的,如果省略,默认值为0txty值可以通过空格或者逗号分隔,它们在函数中不代表任何单位-它们默认等于当前用户坐标系单位。

下面的例子把一个元素向右移动100个用户单位,向下移动300个用户单位。

<circle cx="0" cy="0" r="100" transform="translate(100 300)" />

1
<circle cx="0" cy="0" r="100" transform="translate(100 300)" />

上述代码如果以translate(100, 300)用逗号来分隔值的形式声明一样有效。

CPU 内部的处理

移动设备中的 CPU 并不是一个单独的芯片,而是和 GPU 等芯片集成在一起,被称为 SoC(片上系统)。

前面提到了触屏和 CPU 的连接,这个连接和大部分计算机内部的连接一样,都是通过电气信号来进行通信的,也就是电压高低的变化,如下面的时序图:图片 3

在时钟的控制下,这些电流会经过 MOSFET 晶体管,晶体管中包含 N 型半导体和 P 型半导体,通过电压就能控制线路开闭,然后这些 MOSFET 构成了 CMOS,接着再由 CMOS 实现「与」「或」「非」等逻辑电路门,最后由逻辑电路门上就能实现加法、位移等计算,整体如下图所示(来自《计算机体系结构》):图片 4

除了计算,在 CPU 中还需要存储单元来加载和存储数据,这个存储单元一般通过触发器(Flip-flop)来实现,称为寄存器。

以上这些概念都比较抽象,推荐阅读「How to Build an 8-Bit Computer」这篇文章,作者基于晶体管、二极管、电容等原件制作了一个 8 位的计算机,支持简单汇编指令和结果输出,虽然现代 CPU 的实现要比这个复杂得多,但基本原理还是一样的。

另外其实我也是刚开始学习 CPU 芯片的实现,所以就不在这误人子弟了,感兴趣的读者请阅读本节后面推荐的书籍。

必刷题

接下来,看这道刷题必刷,面试必考的闭包题:

var data = []; for (var i = 0; i 3; i ) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();

1
2
3
4
5
6
7
8
9
10
11
var data = [];
 
for (var i = 0; i  3; i ) {
  data[i] = function () {
    console.log(i);
  };
}
 
data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: [...], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

data[0]Context = { Scope: [AO, globalContext.VO] }

1
2
3
data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

var data = []; for (var i = 0; i 3; i ) { data[i] = (function (i) { return function(){ console.log(i); } })(i); } data[0](); data[1](); data[2]();

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = [];
 
for (var i = 0; i  3; i ) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
 
data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: [...], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = { Scope: [AO, 匿名函数Context.AO globalContext.VO] }

1
2
3
data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = { AO: { arguments: { 0: 1, length: 1 }, i: 0 } }

1
2
3
4
5
6
7
8
9
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。

data[1] 和 data[2] 是一样的道理。

Scaling

你可以通过使用scale()函数变换来向上或者向下缩放来改变SVG元素的尺寸。scale变换的语法是:

CSS

scale(<sx> [<sy>])

1
scale(<sx> [<sy>])

scale()函数输入一个或两个值来声明水平和竖直缩放值。sx代表沿x轴的缩放值,用来水平延长或者拉伸元素;sy代表沿y轴缩放值,用来垂直延长或者缩放元素。

sy值是可选的,如果省略默认值等于sxsxsy可以用空格或者逗号分隔,它们是无单位值。

下面例子把一个元素的尺寸根据最初的尺寸放大两倍:

XHTML

<rect width="150" height="100" transform="scale(2)" x="0" y="0" />

1
<rect width="150" height="100" transform="scale(2)" x="0" y="0" />

下列例子把一个元素缩放到最初宽度的两倍,并且把高度压缩到最初的一半:

XHTML

<rect width="150" height="100" transform="scale(2 0.5)" x="0" y="0" />

1
<rect width="150" height="100" transform="scale(2 0.5)" x="0" y="0" />

上述例子使用逗号分隔的值例如scale(2, .5)仍然有效。

这里需要注意当SVG元素缩放时,整个坐标系被缩放,导致元素在视窗中重新定位,现在不用担心这些,我们会在下一节中讨论细节。

从 CPU 到操作系统内核

前面说到触屏控制器将电气信号发送到 CPU 对应的引脚上,接着就会触发 CPU 的中断机制,以 Linux 为例,每个外部设备都有一标识符,称为中断请求(IRQ)号,可以通过 /proc/interrupts 文件来查看系统中所有设备的中断请求号,以下是 Nexus 7 (2013) 的部分结果:

shell@flo:/ $ cat /proc/interrupts CPU0 17: 0 GIC dg_timer 294: 1973609 msmgpio elan-ktf3k 314: 679 msmgpio KEY_POWER

1
2
3
4
5
shell@flo:/ $ cat /proc/interrupts
            CPU0
  17:          0       GIC  dg_timer
294:    1973609   msmgpio  elan-ktf3k
314:        679   msmgpio  KEY_POWER

因为 Nexus 7 使用了 ELAN 的触屏控制器,所以结果中的 elan-ktf3k 就是触屏的中断请求信息,其中 294 是中断号,1973609 是触发的次数(手指单击时会产生两次中断,但滑动时会产生上百次中断)。

为了简化这里不考虑优先级问题,以 ARMv7 架构的处理器为例,当中断发生时,CPU 会停下当前运行的程序,保存当前执行状态(如 PC 值),进入 IRQ 状态),然后跳转到对应的中断处理程序执行,这个程序一般由第三方内核驱动来实现,比如前面提到的 Nexus 7 的驱动源码在这里 touchscreen/ektf3k.c。

这个驱动程序将读取 I²C 总线中传来的位置数据,然后通过内核的 input_report_abs 等方法记录触屏按下坐标等信息,最后由内核中的input 子模块将这些信息都写进 /dev/input/event0 这个设备文件中,比如下面展示了一次触摸事件所产生的信息:

130|shell@flo:/ $ getevent -lt /dev/input/event0 [ 414624.658986] EV_ABS ABS_MT_TRACKING_ID 0000835c [ 414624.659017] EV_ABS ABS_MT_TOUCH_MAJOR 0000000b [ 414624.659047] EV_ABS ABS_MT_PRESSURE 0000001d [ 414624.659047] EV_ABS ABS_MT_POSITION_X 000003f0 [ 414624.659078] EV_ABS ABS_MT_POSITION_Y 00000588 [ 414624.659078] EV_SYN SYN_REPORT 00000000 [ 414624.699239] EV_ABS ABS_MT_TRACKING_ID ffffffff [ 414624.699270] EV_SYN SYN_REPORT 00000000

1
2
3
4
5
6
7
8
9
130|shell@flo:/ $ getevent -lt /dev/input/event0
[  414624.658986] EV_ABS       ABS_MT_TRACKING_ID   0000835c
[  414624.659017] EV_ABS       ABS_MT_TOUCH_MAJOR   0000000b
[  414624.659047] EV_ABS       ABS_MT_PRESSURE      0000001d
[  414624.659047] EV_ABS       ABS_MT_POSITION_X    000003f0
[  414624.659078] EV_ABS       ABS_MT_POSITION_Y    00000588
[  414624.659078] EV_SYN       SYN_REPORT           00000000
[  414624.699239] EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[  414624.699270] EV_SYN       SYN_REPORT           00000000

深入系列

JavaScript深入系列目录地址:。

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript 深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象
  5. JavaScript 深入之作用域链
  6. JavaScript 深入之从 ECMAScript 规范解读 this
  7. JavaScript 深入之执行上下文

    1 赞 1 收藏 评论

图片 5

Skew

SVG元素也可以被倾斜,要倾斜一个元素,你可以使用一个或多个倾斜函数skewX 和 skewY

skewX(<skew-angle>) skewY(<skew-angle>)

1
2
skewX(<skew-angle>)
skewY(<skew-angle>)

函数skewX声明一个沿x轴的倾斜;函数skewY声明一个沿y轴的倾斜。

倾斜角度声明是无单位角度的默认是度。

注意倾斜一个元素可能会导致元素在视窗中重新定位。在下一节中有更多细节。

从操作系统 GUI 到浏览器

前面提到 Linux 内核已经完成了对硬件的抽象,其它程序只需要通过监听 /dev/input/event0 文件的变化就能知道用户进行了哪些触摸操作,不过如果每个程序都这么做实在太麻烦了,所以在图像操作系统中都会包含 GUI 框架来方便应用程序开发,比如 Linux 下著名的 X。

但 Android 并没有使用 X,而是自己实现了一套 GUI 框架,其中有个 EventHub 的服务会通过 epoll 方式监听 /dev/input/ 目录下的文件,然后将这些信息传递到 Android 的窗口管理服务(WindowManagerService)中,它会根据位置信息来查找相应的 app,然后调用其中的监听函数(如 onTouch 等)。

就这样,我们解答了第一个问题,不过由于时间有限,这里省略了很多细节,想进一步学习的读者推荐阅读以下书籍。

Rotation

你可以使用rotate()函数来旋转SVG元素。这个函数的语法如下:

CSS

rotate(<rotate-angle> [<cx> <cy>])

1
rotate(<rotate-angle> [<cx> <cy>])

rotate()函数对于给定的点和 旋转角度值执行旋转。不像CSS3中的旋转变换,不能声明除degress之外的单位。角度值默认无单位,默认单位是度。

可选的cxcy值代表无单位的旋转中心点。如果没有设置cxcy,旋转点是当前用户坐标系的原点(查看第一部分如果你不知道用户坐标系是什么。)

在函数rotate()中声明旋转中心点一个快捷方式类似于CSS中设置transform: rotate()transform-origin。SVG中默认的旋转中心是当前使用的用户坐标系的左上角,这样也许你无法创建想要的旋转效果,你可以在rotate()中声明一个新的中心点。如果你知道元素在SVG画布中的尺寸和定位,你可以把它的中心设置为旋转中心。

下面的例子是以当前用户坐标系中的(50,50)点为中心进行旋转一组元素:

XHTML

<g id="parrot" transform="rotate(45 50 50)" x="0" y="0"> <!-- elements making up a parrot shape --> </g>

1
2
3
<g id="parrot" transform="rotate(45 50 50)" x="0" y="0">
    <!-- elements making up a parrot shape -->
</g>

然而,如果你想要一个元素围绕它的中心旋转,你也许想要像CSS中一样声明中心为50% 50%;不幸的是,在rotate()函数中这样做是不允许的-你必须用绝对坐标。然而,你可以在CSS的transform属性中使用transform-origin属性。这篇文章后面会讨论更多细节。

扩展学习

  • 《计算机体系结构》
  • 《计算机体系结构:量化研究方法》
  • 《计算机组成与设计:硬件/软件接口》
  • 《编码》
  • 《CPU自制入门》
  • 《操作系统概念》
  • 《ARMv7-AR 体系结构参考手册》
  • 《Linux内核设计与实现》
  • 《精通Linux设备驱动程序开发》

坐标系变化

现在我们已经讨论了所有可能的SVG变换函数,我们深入挖掘视觉部分和对SVG元素添加每个变换的效果。这是SVG变换最重要的部分。因此它们被称为“坐标系统变换”而不仅仅是“元素变换”。

在这个说明中,transform属性被定义成两个在被添加的元素上建立新用户空间(当前坐标系)之一-viewBox属性是创建新用户空间的两个属性中的另一个。所以到底是什么意思呢?

这个行为类似于在HTML元素上添加CSS变换-HTML元素坐标系发生了变换,当你把变换组合使用时最明显。虽然在很多方面很相似,HTML和SVG的变换还是有一些不同。

主要的不同是坐标系。HTML元素的坐标系建立在元素自身智商。然而,在SVG中,元素的坐标系最初是当前坐标系或使用中的用户空间。

当你在一个SVG元素上添加transform属性,元素获取当前使用的用户坐标系的一个“副本”。你可以当做给发生变换的元素创建一个新“层”,新层上是当前用户坐标系的副本(the viewBox)。

然后,元素新的当前坐标系被在transform属性中声明的变换函数改变,因此导致元素自身的变换。这看起来好像是元素在变换后的坐标系中重新绘制。

要理解如何添加SVG变换,让我们从可视化的部分开始。下面图片是我们要研究的SVG画布。

图片 6

鹦鹉和小狗使我们要变换的元素(组<g>)。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <g id="parrot"> <!-- shapes and paths forming the parrot --> </g> <g id="dog"> <!-- shapes and paths forming the dog --> </g> </svg>

1
2
3
4
5
6
7
8
<svg width="800" height="800" viewBox="0 0 800 600">
    <g id="parrot">
        <!-- shapes and paths forming the parrot -->
    </g>
    <g id="dog">
        <!-- shapes and paths forming the dog -->
    </g>
</svg>

灰色坐标是通过viewBox建立的画布的初始坐标系。为了方便起见,我将不改变初始坐标系-我用一个和视窗相同尺寸的viewBox,如你在上述代码中看到的一样。

现在我们建立了画布和初始用户空间,让我们开始变换元素。首先让我们把鹦鹉向左移动150单位,向下移动200个单位。

当然,鹦鹉是由若干路径和形状组成的。只要把transform属性添加到包含它们的组<g>上就行了;这会对整个形状和路径添加变换,鹦鹉会作为一个整体进行变换。查看 article on structuring and grouping SVGs获取更多信息。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <g id="parrot" transform="translate(150 200)"> <!-- shapes and paths forming the parrot --> </g> <!-- ... --> </svg>

1
2
3
4
5
6
<svg width="800" height="800" viewBox="0 0 800 600">
    <g id="parrot" transform="translate(150 200)">
        <!-- shapes and paths forming the parrot -->
    </g>
    <!-- ... -->
</svg>

下面图片展示了上述变换后的结果。鹦鹉的半透明版本是变换前的初始位置。图片 7

SVG中的变换和HTML元素上CSS中的一样简单直观。我们之前提到在元素上添加transform属性时会在元素上创建一个新的当前用户坐标系。下面图片显示了初始坐标系的“副本”,它在鹦鹉元素发生变换时被建立。注意观察鹦鹉当前坐标系是如何变换的。图片 8

这里需要注意的非常重要的一点是建立在元素上的新的当前坐标系是初始用户坐标系的复制,在里面元素的位置得以保持。这意味着它不是建立在元素边界盒上,或者新的当前坐标系的尺寸受制于元素的尺寸。这就是HTML和SVG坐标系之间的区别。

建立在变换元素上的新当前坐标系不是建立在元素边界盒上,或者新的当前坐标系的尺寸受制于元素的尺寸。

我们把小狗变换到画布的右下方时会更加明显。试想我们想要把小狗向右移动50单位,向下移动50单位。这就是狗的最初的坐标以及新的当前坐标系(也因为狗改变)会如何显示。注意小狗的新的坐标系统的原点不在狗边界盒子的左上角。另外注意狗和它新的坐标系看起来它们好像移动到画布新的一层上。图片 9

现在我们试一试其他事情。不再移动,试着缩放。我们将鹦鹉放大到两倍尺寸:

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <g id="parrot" transform="scale(2)"> <!-- shapes and paths forming the parrot --> </g> <!-- ... --> </svg>

1
2
3
4
5
6
<svg width="800" height="800" viewBox="0 0 800 600">
    <g id="parrot" transform="scale(2)">
        <!-- shapes and paths forming the parrot -->
    </g>
    <!-- ... -->
</svg>

放缩SVG元素和放缩HTML元素的结果不一样。缩放后SVG元素的在视窗中的位置随着缩放改变。下面图片展示了把鹦鹉放大到两倍时的结果。注意初始位置和尺寸,以及最终位置和尺寸。图片 10

从上面图片中我们可以注意到不只鹦鹉的尺寸(宽和高)变成了两倍,鹦鹉的坐标(xy)也乘以了缩放因子(这里是两倍)。

这个结果的原因我们之前已经提到了:元素当前坐标系发生变化,鹦鹉在新系统中绘制。所以,在这个例子中,当前坐标系被缩放。这个效果类似于使用viewBox = "0 0 400 300",等于“放大”了坐标系,因此把里面的内容放大到双倍尺寸(如果你还没有读过请查看这个系列的第一部分)。

所以,如果我们把坐标系变换形象化来展现当前变换系统中的鹦鹉,我们会得到以下结果:图片 11

鹦鹉的新的当前坐标系统被缩放,同时“放大”鹦鹉。注意,在它当前的坐标系中,鹦鹉没有重新定位-只有缩放坐标系统才会导致它在视窗中重定位。鹦鹉在新的缩放后的系统中按初始的xy坐标被重绘。

让我们尝使用不同因子在两个方向上缩放鹦鹉。如果我们添加transform="scale(2 0.5)缩放鹦鹉,我们把宽度变为两倍高度为原来的一半。效果和添加viewBox="0 0 400 1200"类似。图片 12

注意一下鹦鹉在倾斜后的坐标系中的位置,并且把它和初始系统(半透明的鹦鹉)中的位置做比较:xy位置坐标保持不变。

在SVG中倾斜元素也导致元素被“移动”,因为它当前的坐标系统被倾斜了。

试想我们使用skewX函数沿x轴给一只狗增加一个倾斜变化。我们在垂直方向上把狗倾斜了25度。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <!-- ... --> <g id="dog" transform="skewX(25)"> <!-- shapes and paths forming the dog --> </g> </svg>

1
2
3
4
5
6
<svg width="800" height="800" viewBox="0 0 800 600">
    <!-- ... -->
    <g id="dog" transform="skewX(25)">
        <!-- shapes and paths forming the dog -->
    </g>
</svg>

下列图片展示了对小狗添加倾斜变换的结果。图片 13

注意到狗的位置对比初始位置也改变了,因为它的坐标系也被倾斜了。

下面的图片展示了同样角度的情况下使用skewY()而不是skewX倾斜狗的情况:图片 14

最后,让我们尝试旋转鹦鹉。旋转默认的中心是当前用户坐标系的左上角。新的建立在旋转元素上的当前系统也被旋转了。在下面的例子中,我们将把鹦鹉旋转45度。旋转方向为顺时针。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <g id="parrot" transform="rotate(45)"> <!-- shapes and paths forming the parrot --> </g> <!-- ... --> </svg>

1
2
3
4
5
6
<svg width="800" height="800" viewBox="0 0 800 600">
    <g id="parrot" transform="rotate(45)">
        <!-- shapes and paths forming the parrot -->
    </g>
    <!-- ... -->
</svg>

添加上述变换的结果如下:图片 15

你很可能想要围绕默认坐标系原点之外的点来旋转一个元素。在transform属性中使用rotate()函数,你可以声明这个点。试想在这个例子中我们想按照它自己的中心旋转这个鹦鹉。根据鹦鹉的宽、高以及位置,我精确计算出它的中心在(150,170)。这个鹦鹉可以围着它的中心旋转。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <g id="parrot" transform="rotate(45 150 170)"> <!-- shapes and paths forming the parrot --> </g> <!-- ... --> </svg>

1
2
3
4
5
6
<svg width="800" height="800" viewBox="0 0 800 600">
    <g id="parrot" transform="rotate(45 150 170)">
        <!-- shapes and paths forming the parrot -->
    </g>
    <!-- ... -->
</svg>

在这个时候,这只鹦鹉会被旋转并且看起来如下:图片 16

我们说变换添加在坐标系上,因此,元素最终被影响并且发生变换。那么究竟如何改变旋转中心工作在坐标系的原点(0,0)的点呢?

当你改变中心或者旋转时,坐标系被变换或者旋转特定角度,然后再次根据声明的旋转中心产生特定变换。在这个例子中:

XHTML

<g id="parrot" transform="rotate(45 150 170)">

1
<g id="parrot" transform="rotate(45 150 170)">

被浏览器当成一系列的移动和旋转等同于:

XHTML

<g id="parrot" transform="translate(150 170) rotate(45) translate(-150 -170)">

1
<g id="parrot" transform="translate(150 170) rotate(45) translate(-150 -170)">

当前坐标系变换到你想要的中心店。然后旋转声明的角度。最终系统被负值变换。上述添加到系统的变换如下:图片 17

在我们进行下一部分讨论嵌套和组合变换前,我想请大家注意建立在变换元素上的当前用户坐标系是独立于建立在其他变换元素之上的其他坐标系的。下列图片展示了建立在狗和鹦鹉上的两个坐标系,以及它们之间是如何保持独立的。图片 18

另外注意每个当前坐标系仍然处于在外层<svg>容器中使用viewBox属性建立的画布的主要坐标系中。任何添加到viewBox上的变换会影响整个画布以及所有里面的元素,不管它们是否建立了自己的坐标系。

例如,以下是把整个画布的用户空间从viewBox="0 0 800 600"改成 viewBox="0 0 600 450"的结果。整个画布被缩放,保持任何添加到独立元素上的变换。图片 19

第二个问题:浏览器如何向网卡发送数据?

嵌套和组合变换

很多时候你可能想要在一个元素上添加多个变换。添加多个变换意味着“组合”变换。

当变换组合时,最重要的是意识到,和HTML元素变换一样,当这个系统发生了之前的变换后在添加下一个变换到坐标系中。

例如,如果你要在一个元素上添加旋转,接下来移动,移动变换会根据新的坐标系统,而不是初始的没有旋转时的系统。

下面了例子就是做了这件事。我们先添加旋转,然后沿x轴使用transform="rotate(45 150 170) translate(200)"把鹦鹉移动200个单位。图片 20

取决于最终的位置和变换,你可以根据需要组合变换。总是记住坐标系。

注意当你倾斜一个元素-以及它的坐标系统-坐标系统不再是最初的那个,坐标系不再会按照最初的来计算-它将会是倾斜后的坐标系。简单来说,这意味着坐标系原点不再是90度的角,新的坐标会根据新的角度来计算。

当变换元素的子元素也需要变换时会发生变换嵌套。添加到子元素上的变换会累积父元素上添加的变换和它本身的变换。

所以,效果上来说,嵌套变化类似于组合:唯一区别是不像在一个元素上添加一系列的变化,它自动从父元素上获得变换,最后执行添加在它自身的变换,就像我们在上面添加的变换一样-一个接一个。

这对于你想要根据另外一个元素变换一个元素时尤其有用。例如,试想你想要给小狗的尾巴设定一个动画。这个尾巴是#dog组的后代。

XHTML

<svg width="800" height="800" viewBox="0 0 800 600"> <!-- ... --> <g id="dog" transform="translate(..)"> <!-- shapes and paths forming the dog --> <g id="head"> <!-- .. --> </g> <g id="body" transform="rotate(.. .. ..)"> <path id="tail" d="..." transform="rotate(..)"> <!-- animateTransform here --> </path> <g id="legs"> <!-- ... --> </g> </g> </g> </svg>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<svg width="800" height="800" viewBox="0 0 800 600">
    <!-- ... -->
    <g id="dog" transform="translate(..)">
        <!-- shapes and paths forming the dog -->
        <g id="head">
            <!-- .. -->
        </g>
        <g id="body" transform="rotate(.. .. ..)">
            <path id="tail" d="..." transform="rotate(..)">
                <!-- animateTransform here -->
            </path>
            <g id="legs">
                <!-- ... -->
            </g>
        </g>
    </g>
</svg>

试想我们变换dog组;围绕某一点把它的身体旋转一定角度,然后我们想要再把尾巴旋转一定角度。

当尾巴被旋转后,它从祖先(#body)身上“继承”了变换坐标系,也从祖先(#dog)身上继承了变换坐标系,然后旋转(和#body组一样的旋转)然后在发生自身的旋转。这里添加的一系列变换的效果类似于我们之前在上述组合变换例子中解释的。

所以,你看,在#tail上嵌套变换实际上和组合变换有一样的效果。

从浏览器到浏览器内核

前面提到操作系统 GUI 将输入事件传递到了浏览器中,在这过程中,浏览器可能会做一些预处理,比如 Chrome 会根据历史统计来预估所输入字符对应的网站,比如输入了「ba」,根据之前的历史发现 90% 的概率会访问「www.baidu.com 」,因此就会在输入回车前就马上开始建立 TCP 链接甚至渲染了,这里面还有很多其它策略,感兴趣的读者推荐阅读 High Performance Networking in Chrome。

接着是输入 URL 后的「回车」,这时浏览器会对 URL 进行检查,首先判断协议,如果是 http 就按照 Web 来处理,另外还会对这个 URL 进行安全检查,然后直接调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法。

在浏览器内核中会先查看缓存,然后设置 UA 等 HTTP 信息,接着调用不同平台下网络请求的方法。

需要注意浏览器和浏览器内核是不同的概念,浏览器指的是 Chrome、Firefox,而浏览器内核则是 Blink、Gecko,浏览器内核只负责渲染,GUI 及网络连接等跨平台工作则是浏览器实现的

使用CSS属性变换SVGs

在SVG2中,transform属性简称transform属性;因为SVG变换已经被引入CSS3变换规范中。后者结合了SVG变化,CSS2 2D变换和CSS 3D变换规范,并且把类似transform-origin 和 3D transformations引入了SVG。

声明在CSS变换规范中的CSS变换属性可以被添加到SVG元素上。然而,transform属性函数值需要遵循CSS规范中的语法声明:函数参数必须逗号隔开-空格隔开是不允许的,但是你可以在逗号前后引用一两个空格;rotate()函数不接受<cx><cy>值-旋转中心使用transform-origin属性声明。另外,CSS变换函数接受角度和坐标单位,例如角度的rad(radians)和坐标的px,em等。

使用CSS来旋转一个SVG元素看起来如下:

CSS

#parrot { transform-origin: 50% 50%; /* center of rotation is set to the center of the element */ transform: rotate(45deg); }

1
2
3
4
#parrot {
    transform-origin: 50% 50%; /* center of rotation is set to the center of the element */
    transform: rotate(45deg);
}

SVG元素也可以使用CSS 3D变换在三维空间中变换。依然要注意坐标系,然而,不同于建立在HTML元素上的坐标系。这意味着3D旋转看起来也不同除非改变旋转中心。

CSS

#SVGel { transform: perspective(800px) rotate3d(1, 1, 0, 45deg); }

1
2
3
#SVGel {
    transform: perspective(800px) rotate3d(1, 1, 0, 45deg);
}

因为通过CSS来变换SVG元素非常类似于通过CSS来变换HTML元素-语法层面-在这篇文章中我将跳过这个部分。

另外,在写这篇文章的时候,在一些浏览器中实现一些特性是不可能的。因为浏览器支持改变很快,我建议你实验一下这些属性来决定哪些可以工作哪些不可以,决定什么现在可以用什么不可以。

注意一旦CSS变换可以完全实现在SVG上,我依然建议你使用CSS变换函数语法即使你用transform属性的形式添加变换。也就是说,上面提到的transform属性函数的语法还是有效的。

HTTP 请求的发送

因为网络的底层实现是和内核相关的,所以这一部分需要针对不同平台进行处理,从应用层角度看主要做两件事情:通过 DNS 查询 IP、通过 Socket 发送数据,接下来就分别介绍这两方面的内容。

动画transform

SVG变换可以变成动画,就像CSS变换一样。如果你使用CSS transform属性来产生SVG变换,你可以像在HTML元素上创建CSS变换动画一样使用CSS动画把这些变换变成动画。

SVGtransform属性可以用SVG<animateTransform>元素来做成动画。<animateTransform>元素是SVG中三个用来给不同的SVG属性设置动画的元素之一。

关于<animateTransform>元素的详细内容不在本片文章的讨论范围内。阅读我写的关于不同SVG动画元素的文章,包括<animateTransform>

DNS 查询

应用程序可以直接调用 Libc 提供的 getaddrinfo() 方法来实现 DNS 查询。

DNS 查询其实是基于 UDP 来实现的,这里我们通过一个具体例子来了解它的查找过程,以下是使用 dig trace fex.baidu.com 命令得到的结果(省略了一些):

; <<>> DiG 9.8.3-P1 <<>> trace fex.baidu.com ;; global options: cmd . 11157 IN NS g.root-servers.net. . 11157 IN NS i.root-servers.net. . 11157 IN NS j.root-servers.net. . 11157 IN NS a.root-servers.net. . 11157 IN NS l.root-servers.net. ;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 220 ms com. 172800 IN NS a.gtld-servers.net. com. 172800 IN NS c.gtld-servers.net. com. 172800 IN NS m.gtld-servers.net. com. 172800 IN NS h.gtld-servers.net. com. 172800 IN NS e.gtld-servers.net. ;; Received 503 bytes from 192.36.148.17#53(192.36.148.17) in 185 ms baidu.com. 172800 IN NS dns.baidu.com. baidu.com. 172800 IN NS ns2.baidu.com. baidu.com. 172800 IN NS ns3.baidu.com. baidu.com. 172800 IN NS ns4.baidu.com. baidu.com. 172800 IN NS ns7.baidu.com. ;; Received 201 bytes from 192.48.79.30#53(192.48.79.30) in 1237 ms fex.baidu.com. 7200 IN CNAME fexteam.duapp.com. fexteam.duapp.com. 300 IN CNAME duapp.n.shifen.com. n.shifen.com. 86400 IN NS ns1.n.shifen.com. n.shifen.com. 86400 IN NS ns4.n.shifen.com. n.shifen.com. 86400 IN NS ns2.n.shifen.com. n.shifen.com. 86400 IN NS ns5.n.shifen.com. n.shifen.com. 86400 IN NS ns3.n.shifen.com. ;; Received 258 bytes from 61.135.165.235#53(61.135.165.235) in 2 ms

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
; <<>> DiG 9.8.3-P1 <<>> trace fex.baidu.com
;; global options: cmd
.           11157   IN  NS  g.root-servers.net.
.           11157   IN  NS  i.root-servers.net.
.           11157   IN  NS  j.root-servers.net.
.           11157   IN  NS  a.root-servers.net.
.           11157   IN  NS  l.root-servers.net.
;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 220 ms
 
com.            172800  IN  NS  a.gtld-servers.net.
com.            172800  IN  NS  c.gtld-servers.net.
com.            172800  IN  NS  m.gtld-servers.net.
com.            172800  IN  NS  h.gtld-servers.net.
com.            172800  IN  NS  e.gtld-servers.net.
;; Received 503 bytes from 192.36.148.17#53(192.36.148.17) in 185 ms
 
baidu.com.      172800  IN  NS  dns.baidu.com.
baidu.com.      172800  IN  NS  ns2.baidu.com.
baidu.com.      172800  IN  NS  ns3.baidu.com.
baidu.com.      172800  IN  NS  ns4.baidu.com.
baidu.com.      172800  IN  NS  ns7.baidu.com.
;; Received 201 bytes from 192.48.79.30#53(192.48.79.30) in 1237 ms
 
fex.baidu.com.      7200    IN  CNAME   fexteam.duapp.com.
fexteam.duapp.com.  300 IN  CNAME   duapp.n.shifen.com.
n.shifen.com.       86400   IN  NS  ns1.n.shifen.com.
n.shifen.com.       86400   IN  NS  ns4.n.shifen.com.
n.shifen.com.       86400   IN  NS  ns2.n.shifen.com.
n.shifen.com.       86400   IN  NS  ns5.n.shifen.com.
n.shifen.com.       86400   IN  NS  ns3.n.shifen.com.
;; Received 258 bytes from 61.135.165.235#53(61.135.165.235) in 2 ms

可以看到这是一个逐步缩小范围的查找过程,首先由本机所设置的 DNS 服务器(8.8.8.8)向 DNS 根节点查询负责 .com 区域的域务器,然后通过其中一个负责 .com 的服务器查询负责 baidu.com 的服务器,最后由其中一个 baidu.com 的域名服务器查询 fex.baidu.com 域名的地址。

可能你在查询某些域名的时会发现和上面不一样,最底将看到有个奇怪的服务器抢先返回结果。。。

这里为了方便描述,忽略了很多不同的情况,比如 127.0.0.1 其实走的是 loopback,和网卡设备没关系;比如 Chrome 会在浏览器启动的时预先查询 10 个你有可能访问的域名;还有 Hosts 文件、缓存时间 TTL(Time to live)的影响等。

最后的话

学习SVGs一开始可能非常困惑,如果对于坐标系变换里的内容不是很清楚,尤其是如果你带着CSS HTML变换的背景知识,自然而然希望SVG元素和HTML元素的变换一样。

然而,一旦你意识到它们的工作方式,你能更好得控制SVG画布,并且轻易操纵元素。

这一系列的最后部分,我将讨论嵌套SVGs和建立新的viewports和viewboxes。敬请关注!

1 赞 1 收藏 评论

图片 21

通过 Socket 发送数据

有了 IP 地址,就可以通过 Socket API 来发送数据了,这时可以选择 TCP 或 UDP 协议,具体使用方法这里就不介绍了,推荐阅读 Beej’s Guide to Network Programming。

HTTP 常用的是 TCP 协议,由于 TCP 协议的具体细节到处都能看到,所以本文就不介绍了,这里谈一下 TCP 的 Head-of-line blocking 问题:假设客户端的发送了 3 个 TCP 片段(segments),编号分别是 1、2、3,如果编号为 1 的包传输时丢了,即便编号 2 和 3 已经到达也只能等待,因为 TCP 协议需要保证顺序,这个问题在 HTTP pipelining 下更严重,因为 HTTP pipelining 可以让多个 HTTP 请求通过一个 TCP 发送,比如发送两张图片,可能第二张图片的数据已经全收到了,但还得等第一张图片的数据传到。

为了解决 TCP 协议的性能问题,Chrome 团队去年提出了 QUIC 协议,它是基于 UDP 实现的可靠传输,比起 TCP,它能减少很多来回(round trip)时间,还有前向纠错码(Forward Error Correction)等功能。目前 Google Plus、 Gmail、Google Search、blogspot、Youtube 等几乎大部分 Google 产品都在使用 QUIC,可以通过 chrome://net-internals/#spdy 页面来发现。

虽然目前除了 Google 还没人用 QUIC,但我觉得挺有前景的,因为优化 TCP 需要升级系统内核(比如 Fast Open)。

浏览器对同一个域名有连接数限制,大部分是 6,我以前认为将这个连接数改大后会提升性能,但实际上并不是这样的,Chrome 团队有做过实验,发现从 6 改成 10 后性能反而下降了,造成这个现象的因素有很多,如建立连接的开销、拥塞控制等问题,而像 SPDY、HTTP 2.0 协议尽管只使用一个 TCP 连接来传输数据,但性能反而更好,而且还能实现请求优先级。

另外,因为 HTTP 请求是纯文本格式的,所以在 TCP 的数据段中可以直接分析 HTTP 的文本,如果发现。。。

Socket 在内核中的实现

前面说到浏览器的跨平台库通过调用 Socket API 来发送数据,那么 Socket API 是如何实现的呢?

以 Linux 为例,它的实现在这里 socket.c,目前我还不太了解,推荐读者看看 Linux kernel map,它标注出了关键路径的函数,方便学习从协议栈到网卡驱动的实现。

底层网络协议的具体例子

接下来如果继续介绍 IP 协议和 MAC 协议可能很多读者会晕,所以本节将使用 Wireshark 来通过具体例子讲解,以下是我请求百度首页时抓取到的网络数据:图片 22

最底下是实际的二进制数据,中间是解析出来的各个字段值,可以看到其中最底部为 HTTP 协议(Hypertext Transfer Protocol),在 HTTP 之前有 54 字节(0x36),这就是底层网络协议所带来的开销,我们接下来对这些协议进行分析。

在 HTTP 之上是 TCP 协议(Transmission Control Protocol),它的具体内容如下图所示:图片 23

通过底部的二进制数据,可以看到 TCP 协议是加在 HTTP 文本前面的,它有 20 个字节,其中定义了本地端口(Source port)和目标端口(Destination port)、顺序序号(Sequence Number)、窗口长度等信息,以下是 TCP 协议各个部分数据的完整介绍:

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Source Port | Destination Port | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Sequence Number | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Acknowledgment Number | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Data | |U|A|E|R|S|F| | | Offset| Reserved |R|C|O|S|Y|I| Window | | | |G|K|L|T|N|N| | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Checksum | Urgent Pointer | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Options | Padding | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | data | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|          Source Port          |       Destination Port        |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|                        Sequence Number                        |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|                    Acknowledgment Number                      |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|  Data |           |U|A|E|R|S|F|                               |
| Offset| Reserved  |R|C|O|S|Y|I|            Window             |
|       |           |G|K|L|T|N|N|                               |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|           Checksum            |         Urgent Pointer        |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|                    Options                    |    Padding    |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|                             data                              |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

具体各个字段的作用这里就不介绍了,感兴趣的读者可以阅读 RFC 793,并结合抓包分析来理解。

需要注意的是,在 TCP 协议中并没有 IP 地址信息,因为这是在上一层的 IP 协议中定义的,如下图所示:图片 24

IP 协议同样是在 TCP 前面的,它也有 20 字节,在这里指明了版本号(Version)为 4,源(Source) IP 为 192.168.1.106,目标(Destination) IP 为 119.75.217.56,因此 IP 协议最重要的作用就是确定 IP 地址。

因为 IP 协议中可以查看到目标 IP 地址,所以如果发现某些特定的 IP 地址,某些路由器就会。。。

但是,光靠 IP 地址是无法进行通信的,因为 IP 地址并不和某台设备绑定,比如你的笔记本的 IP 在家中是 192.168.1.1,但到公司就变成172.22.22.22 了,所以在底层通信时需要使用一个固定的地址,这就是 MAC(media access control) 地址,每个网卡出厂时的 MAC 地址都是固定且唯一的。

因此再往上就是 MAC 协议,它有 14 字节,如下所示:图片 25

当一台电脑加入网络时,需要通过 ARP 协议告诉其它网络设备它的 IP 及对应的 MAC 地址是什么,这样其它设备就能通过 IP 地址来查找对应的设备了。

最顶上的 Frame 是代表 Wireshark 的抓包序号,并不是网络协议

就这样,我们解答了第二个问题,不过其实这里面还有很多很多细节没介绍,建议大家通过下面的书籍进一步学习。

扩展学习

  • 《计算机网络:自顶向下方法与Internet特色》
  • 《计算机网络》
  • 《Web性能权威指南》

第三个问题:数据如何从本机网卡发送到服务器?

本文由无需申请自动送彩金68发布于无需申请自动,转载请注明出处:到页面加载完成的过程中都发生了什么事情,深

关键词: