Blog

Yoki


  • 首页

  • 归档

  • 搜索

shadowsocks了解一下

发表于 2018-05-09

有时候需要科学上网,这个时候 ss 就大显身手了

初步认识

  • ss 是一个能骗过防火墙的网络代理工具。把要传输的原数据经过加密后再传输,网络中的防火墙由于得不出要传输的内容是啥,就只好放行了。
  • 于是就完成了防火墙穿透,也就是所谓翻墙。

网络环境

  • 自由的网络环境下 ,本机访问其他互联网服务建立连接传输数据的时候,直接 req 过去,res 回来
  • 但是一般情况下,会有 GFW。req 过去的数据和 res 回来的数据都必须通过 GFW 的检查。
  • 如果发现传输受限的内容,就会拦截本次传输,就会导致本机无法访问远程服务。

ss 做什么

  • ss 所做的就是把传输的数据加密,防火墙得到的数据是加密后的数据,如果想要破解加密数据可能经过大量运算,所以防火墙只好放弃。
  • 放行这个请求,本机就可以访问到了远程服务

ss 准备什么

  • 一台在 GFW 之外的服务器,ip 是国外的,比如(vultr 的 vps)
  • 在本机安装 ss 客户端,用于加密传输数据
  • 服务器需要安装 ss 服务端,用于解密加密的传输数据,然后再把揭秘后的原数据发送到目标服务器

原理

由两部分组成,运行在本地的 ss-local 和运行在 GFW 之外的服务器的 ss-server。

ss-local

  • 在本机启动和监听着一个服务,本地软件的网络请求都先发送到 ss-local,然后按照用户配置的加密方法和密码加密传输数据,再转发到墙外的 ss-server
PAC 代理模式和全局代理模式
  • pac 模式是访问网站先匹配 pac list,如果有在里面才会访问,否则直接连接访问网站
  • 节省 ss 流量,提高国内的访问速度

ss-server

  • 在墙外服务器启动和监听一个服务,监听 ss-local 的请求。
  • 收到 ss-local 转发过来的数据后,会先根据用户配置的加密方法和密码对数据进行对称揭秘,以获得加密后的数据的原内容。
  • 同时还会解析 socks5 协议,读出本次请求真正的目标服务地址(例如 facebook,google 服务器地址),再把解密后的数据发过去
  • 当真正的目标服务返回了数据,比如谷歌返回了页面,ss-server 会把返回的数据加密返回给 ss-local。ss-local 收到数据后再解密,发给本机的软件。
  • 这是一个对称相反的过程。
    • ss-local 和 ss-server 都需要用对称加密算法对数据进行加密和解密,因此这两端的加密方法和密码必须配置为一样。
    • ss 提供一系列的标准可靠的对称算法供用户选择。如 rc4,aes,does。
    • 对数据加密后再传输的目的是为了混淆原数据,让途中的防火墙无法算出传输的原数据。

chrome 下 SwitchyOmega 配合使用

  • 下载 SwitchyOmega 插件,勾选系统代理,将流量全部转给 ss-local

推荐阅读

  • 本文节选自从零开始写一个 shadowsocks
  • 自建 ss 服务教程
  • 科学上网原理

nginx初步使用(-)

发表于 2018-05-08

在本地用 brew install nginx 之后,迫不及待的想要使用一下

  • mac 下默认监听 8080 端口,输入 localhost:8080 就可以看到 nginx 的欢迎页

更改默认配置文件

默认安装在/usr/local/etc/nginx 下面

位置

  • nginx.conf 是其默认配置文件,nginx -s reload 读取的就是这个
  • 如果想配置多个域名,可以在 nginx 目录下新建一个 sites-enabled 文件夹,然后再建一个以区分 http 和 https,在 nginx.conf 里include /usr/local/etc/nginx/sites-enabled/*
  • 我看 centos 系统里新建的文件夹是 conf.d
  • 域名配置文件一个最佳实践就是按照域名来写,比如 yoki.com.cn.conf

配置介绍

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
# user字段表明了Nginx服务是由哪个用户哪个群组来负责维护进程的,默认是nobody
# 我这里用了liyoujie用户,staff组来启动并维护进程
# 查看当前用户命令: whoami
# 查看当前用户所属组命令: groups ,当前用户可能有多个所属组,选第一个即可
user liyoujie staff;

# worker_processes字段表示Nginx服务占用的内核数量
# 为了充分利用服务器性能你可以直接写你本机最高内核
# 查看本机最高内核数量命令: sysctl -n hw.ncpu
worker_processes 4;

# error_log字段表示Nginx错误日志记录的位置
# 模式选择:debug/info/notice/warn/error/crit
# 上面模式从左到右记录的信息从最详细到最少
error_log /usr/local/var/logs/nginx/error.log debug;

# Nginx执行的进程id,默认配置文件是注释了
# 如果上面worker_processes的数量大于1那Nginx就会启动多个进程
# 而发信号的时候需要知道要向哪个进程发信息,不同进程有不同的pid,所以写进文件发信号比较简单
# 你只需要手动创建,比如我下面的位置: touch /usr/local/var/run/nginx.pid
pid /usr/local/var/run/nginx.pid;

events {
# 每一个worker进程能并发处理的最大连接数
# 当作为反向代理服务器,计算公式为: `worker_processes * worker_connections / 4`
# 当作为HTTP服务器时,公式是除以2
worker_connections 2048;
}

http {
# 关闭错误页面的nginx版本数字,提高安全性
server_tokens off;
include mime.types;
default_type application/octet-stream;

# 日志记录格式,如果关闭了access_log可以注释掉这段
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

# 关闭access_log可以让读取磁盘IO操作更快
# 当然如果你在学习的过程中可以打开方便查看Nginx的访问日志
access_log off;

sendfile on;

# 在一个数据包里发送所有头文件,而不是一个接一个的发送
tcp_nopush on;

# 不要缓存
tcp_nodelay on;

keepalive_timeout 65;

gzip on;
client_max_body_size 10m;
client_body_buffer_size 128k;

# 引入其他文件,一个是http服务的,一个是https服务的
# 由这里include可以知道,那些被include的文件都是server上下文开始的(并不是http上下文)
# default default-ssl
include /usr/local/etc/nginx/sites-enabled/*;
}

读《图解算法》03-快速排序

发表于 2018-05-06

分而治之(divide and conquer)

  • 缩写就是 D&C
  • 一种著名的递归式问题解决办法
  • 快速排序是一个重要的 D&C 算法

步骤

  • 找出基线条件,这种条件应该尽可能简单
  • 不断将问题分解(或者说缩小规模),直到符合基线条件

场景

给定一个数组[2,4,6],然后相加,并返回结果,不使用循环

  • 第一步找到基线条件,最简单的数组是怎样的?如果数组不包含一个元素或者只有一个元素,那么计算总和就容易了,这就是基线条件
  • 每次递归调用都必须离空数组更近
1
2
3
4
5
6
7
8
function sum(arr) {
if (arr.length == 1) {
return arr[0];
} else {
//别忘了递归记录了状态
return arr.shift() + sum(arr);
}
}

快速排序

使用快排对数组进行排序

  • 对于排序算法来说,最简单的数组是怎么样的?没错,就是根本不需要排序的一个元素的数组或者空数组

原理

[33,15,10]

  •  首先在数组中选择一个元素,这个元素被称为基准值(pivot),等等介绍如何选择合适的基准值,这里选择第一个元素 33
  • 找出比基准值小的元素和比他大的元素,这叫分区
  • 现在我有:一个由所有小于基准值的数字组成的子数组,基准值,一个由所有大于基准值的数字组成的子数组
  • 然后再对子数组进行排序,然后合并结果就能得到有序数组:quicksort([15,10])+[33]+quicksort([])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function quicksort(arr) {
if (arr.length < 2) {
//基线条件:为空或者包含一个元素的数组有序
return arr;
} else {
let pivot = arr[0]; //递归条件
let less = [],
greater = [];
//子数组
arr.forEach(item => {
if (item < pivot) {
less.push(item);
}
if (item > pivot) {
greater.push(item);
}
});
return [...quicksort(less), pivot, ...quicksort(greater)];
}
}

最糟情况和平均情况

  • 平均情况下是 O(n*logn)

    有一种算法叫合并排序也是这个,但是为什么不用这个呢,是因为大 O 表示法有个常量(也被叫做固定时间量)被省去了,如果两个算法的大 O 运行时间不一样,那么常量无关紧要,参考简单查找和二分查找。如果一样的话,那么常量的影响非常大。两个函数,后面那个我每读一个元素,睡一秒种,但是其实两个函数速度一样,但是前面所需的时间更少。同理可得,快排所需的时间更少,而且相对于遇上最糟情况,它遇上平均情况的可能性大得多。

  • 最糟糕就是与选择排序一样慢,O(n^2)

场景

  • 比如[1,2,3,4,5,6,7]
  • 如果从第一个数作为基准值,数组没有被切成两半,这将导致调用栈很长,栈长 n 个,而且最上面那层都涉及 n 个元素,然后类推 n-1 个元素,最终得出 n*n*1/2 个操作,去掉常数,也就是 n^2
  • 如果总是将中间的元素作为基准值,数组分成两半,最佳情况下,栈长为 logn 个,然后每次也是涉及 O(n)个元素(去掉常量),也就是 n*logn。而且最佳情况也是平均情况,只要每次随机地选择一个数组元素作为基准值,那么平均运行时间就是 O(nlogn)
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
function quicksort(arr) {
if (arr.length < 2) {
//基线条件:为空或者包含一个元素的数组有序
return arr;
} else {
//随机选择索引
let index = randomNum(0, arr.length - 1);
let pivot = arr[index]; //递归条件
let less = [],
greater = [];
//子数组
arr.forEach(item => {
if (item < pivot) {
less.push(item);
}
if (item > pivot) {
greater.push(item);
}
});
return [...quicksort(less), pivot, ...quicksort(greater)];
}
}
//随机数
function randomNum(minNum, maxNum) {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * minNum + 1, 10);
break;
case 2:
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
break;
default:
return 0;
break;
}
}

说一下冒泡排序

想象一下一个个冒泡上来,第一次遍历,第一个跟接下来的比较,如果比他大就换位置,一圈完事后。到第二个,跟接下来的换位置。

  • 运行时是 O(n^2)
1
2
3
4
5
6
7
8
9
10
11
12
13
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
console.log(arr);
return arr;
}

读《图解算法》03-递归和栈

发表于 2018-05-06

递归

概念

  • 函数调用自己
  • 需要有基线条件和递归条件

基线条件和递归条件

每个递归函数都有两部分:基线条件和递归条件。

  • 递归条件是指函数调用自己
  • 基线条件是指函数不再调用自己,从而避免无限循环
1
2
3
4
5
6
7
8
9
10
//倒计时
function countdown(i) {
console.log(i);
if (i <= 0) {
return; //基线条件
} else {
countdown(i - 1);
//递归条件;
}
}

栈

概念

  • 一种类似堆叠的盘子的数据结构

调用栈

  • 函数里面调用函数,形成一个栈结构
  • greet 里面调用 greet1 函数,计算机先为第一个函数调用分配一个内存块,再为第二个分配一个内存块(位于第一个上面),然后第二个从函数调用返回,此时栈顶的内存块被弹出,然后剩下第一个

递归调用栈

  • 特殊一点的调用栈,就是调用自己的
  • 栈中包含未完成的函数调用,这样栈就帮你跟踪了函数
  • 使用栈虽然方便,但也要付出代价,存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。

读《图解算法》02-数组,链表和选择排序

发表于 2018-05-06

数组和链表

当需要将数据存储到内存的时候,我们请求计算机提供存储空间,计算机给我们一个存储地址。需要存储多项数据的时候,有两种基本方式——数组和链表。

数组

想象画面

java 中一开始要确定数组的大小,不是没有道理的…

  • 两个人去电影院看电影,找到地方就座后,又来了一个朋友,但是你们下一个位置是别人占用了,你们三个是好朋友,没理由分开,只得挪去其他有三个位置的地方。
  • 所以来了新朋友是很麻烦的,但是有一种解决办法就是预先买了 10 个座位的,但是这样会浪费资源,要是超过 10 个还是转移(数组插入很麻烦)
  • 但是我想知道另外一个朋友坐哪里,这是很简单的,挨在一起(数组查找快)
  • 另一个朋友要走的话也很麻烦,因为剩下的朋友要往前来挨在一起坐,反正不管怎样大家是个整体,坐在一起就对了(数组删除很麻烦)

链表

链表和数组不一样,一定要顺序排着队这样。链表的元素可以存储在内存中的任何地方,当前元素存储存储着下一个元素的地址,从而使得一系列随机的内存地址串在了一起

想象画面

  • 寻宝游戏:前往第一个地址,那有宝箱,打开后是下一个宝箱的地址。
  • 但是我直接想要最后一个宝箱,是无法做到的,是要一个个查找直到倒数第二个才得到最后一个宝箱的位置(链表查找效率很低)
  • 移走一个宝箱很容易,直接把上一个宝箱的地址指向下一个的地址就可以了(链表删除很快)
  • 如果是五个人去看一部很火的电影,但是根本就无法坐一起,但是换做链表,就可以分开来做,所以这个来了新朋友一点也不麻烦(链表的优势就是插入)

运行时

这里是最差的情况,比如数组插入一个刚好就要移走(因为原来已经满了)

数组 链表
插入 O(n) O(1)
读取 O(1) O(n)
删除 O(n) O(1)
  • O(n)是线性时间,O(1)是常量时间

选择排序

场景

  • qq 音乐列表里面记录了每个音乐的播放次数,你要对它进行排序,从多到少
  • 一种办法是遍历这个列表,然后找到播放次数最多的,然后添加到一个新列表
  • 然后删除刚刚那个次数最多的,再继续这样找到第二多的
  • 需要检查的就越来越少

算法复杂度(运行时)

  • 第一次每个元素都查看一次,那就是执行 n 次
  • 第二次执行 n-1 次
  • 依次查看最后得出,要执行 n(n-1)(n-2)..2*1=n*1/2*n
  • 但是大 O 表示法省略常数,所以最后是 O(n^2)

读《图解算法》01-二分查找和大O表示法

发表于 2018-05-06

场景

电话本

  • 假设从电话本里面找 yoki 的电话,最常见的不是从开头开始找,而是从中间开始查找
  • 电话本是有序列表

1-100 猜想

以目标最少的次数猜到这个数字

简单查找

  • 如果从 1 开始猜,这叫简单查找,换句话说就是傻找
  • 如果是 99,那么得猜 99 次(临界点就是这个数)

二分查找

  • 如果从中间值开始猜
  • 那么临界点就是 99,最坏的情况下只用猜七次,50 错,75 错..这样猜

那么得出结论,对于 n 个元素,用二分查找最多需要 log2 n 步,简单查找最多需要 n 步

2^log2 n=n,log 叫对数运算,2^n 叫幂运算,他们互为逆运算

算法实现

注意二分查找法必须是有序的

大 O 表示法

指出的是最糟情况下的运行时间,也可以说是操作数

  • 这里用大 O 表示法讨论运行时间,都是讨论的最糟糕的临界值,比如简单查找 100 个元素,就是要看每一个元素。二分查找也是查看最远的,那么就只用查看 log100 个元素约为 7
  • log 时间这里的底数默认是 2,也就是默认是 log2
  • 其实我们是用幂运算的眼光来看,我们求的运行时其实就是指数

概念

  • 大 O 表示法指出了算法有多快。例如,假设列表包含 n 个元素,简单查找需要检查每个元素,因此需要执行 n 次查找,运行时间位 O(n)
  • O(n),单位不是秒,大 O 表示法指的并不是以秒为单位的速度,而是能够比较操作数,它指出了算法运行时间的增速
  • 所以 n 是操作数的意思
  • 谈论算法的速度,是随着输入的增加,其运行事件将以怎么样的速度增加

常见的大 O 运行时间

由快到慢的经常遇到的五种,可以自己想象一下坐标系图

  • O(log n),也叫对数时间,这样的算法包括二分查找
  • O(n),线性时间,包括简单查找
  • O(n*log n),包括快速排序(业界俗称快排),一种速度较快的快速排序
  • O(n^ 2),包括选择排序,一种速度较慢的排序算法
  • O(n!),包括旅行商问题的解决方案,一种非常慢的算法

旅行商问题

旅行商要前往 5 个城市,同时确保旅程最短,这样它要每个城市都去,然后计算总旅程,再挑选路线最短的

  • 5 个城市有 120 种不同的排列方式,因此需要 120 个操作
  • 这是一个非常非常慢的算法,但是这个问题也是计算机科学领域待解决的问题

js浮点数的坑

发表于 2018-05-06

浮点数在计算机中的表示

  • 日常中,32767 这个数用科学计数法可以写成 3.2767*10^4,3.2767 称为尾数(Mantissa),4 就是指数(exponent)
  • 浮点数在计算机中基于科学技术法来表示的,上面是我们日常用十进制来表示,计算机则是二进制,它的基数是 2 不是 10

一种浮点数格式

假设共有 14bits,5bits 表指数,8bits 表尾数,1bit 表示符号

  • 17=>10001=1.0001*2^4 类比十进制的 1.7*10^5

尾数部分

  • 因为尾数在十进制里面 0<尾数<10,在二进制里面就是 0<尾数<2,那么规定最高位只能是 1
  • 因为尾数默认是 1,所以这个 1 就不用保存了,可以节省一位提高精度。所以尾数部分本来是 10001,这样就只用存储 0001,去掉了 1
  • 需要注意的是,这里我们尾数的有效位数是 8 位,而 128.25=>10000000.01,需要 10 个有效位,而我们的模型中尾数部分是 8 位,算上隐含的最高位 1 也才 9 个有效位,所以 128.25 只能舍去末尾的 1,表示为 10000000.0,其实跟 128 相等。所以浮点数不能做精确比较就是这样的

指数部分

  • 但是指数部分是 4,换算成二进制也就是 100,如果以为是 100 就错了。
  • 因为 0.25=>0.01=1*10^-2,但是这样我们无法用指数表示负数。第一个符号位表示的是整个数的正负
  • 现在广泛采用的是偏移的指数。规定一个偏移值,比如偏移值是 16,实际的指数要加上这个偏移值才可以,这样比 16 大的就是正指数,小的就是负指数。
  • 要表示 0.25,如果偏移值 16,那么指数部分是 14,要表示 17 的指数部分,是 16+5=21,换成二进制就是 10101
1bit 5bits 8bits
sign bit exponent Mantissa
符号位 指数 尾数
0 10101 00010000

js 的浮点数标准

js 的 number 遵循 IEEE 754 标准,使用 64 位固定长度表示,也就是 64 位 double 双精度浮点数(类似的有 float 32 位单精度标准)

  • 大多数语言的小数默认都是遵循这个标准,所以 js 有的问题他们也有,包括 java,ruby,python
  • 64bits 分为三部分
    • 符号 S:1bit,0 表示正数,1 表示负数
    • 指数 E:中间的 11 位存储指数
    • 尾数 M:最后的 52 位是尾数
  • 因为指数 E 有 11 位,取值范围是 2^11=2048,也就是[0,2047],所以约定的偏移值是 1023,[0,1023]是负数

浮点误差

浮点数不是精确存储的

0.1 浮点误差(转成二进制无限循环)

  • 0.1 转成二进制==0.0001100110011001100(1100 循环)=1.100110011001100(1100 循环)x2^-4,因为尾数舍去首位的 1,存的是后面的数,但是最多也只能存 52 位,然后再把有误差的只有 52 位尾数的,转成十进制,就变成了 0.100000000000000005551115123126
  • 0.1+0.2=0.30000000000000004,那是因为把这两个转成二进制(这里就有误差了)后再运算,然后再转回十进制,正好是 0.30000000000000004

为什么 x=0.1 就能得到 0.1

  • 因为尾数的固定长度是 52 位,那么加上省略的一位,最多可以表示的数位 2^53=9007199254740992,对应科学计数尾数是 9.007199254740992,我们只用记住 2^53 就好了
  • 2^53 的长度是 16 位,这也是 js 最多能表示的精度
  • 所以可以近似用 toPrecision(16) 来做精度运算,超过的精度会自动凑整处理
  • Precision 是精确的意思
  • 所以 0.1 转成二进制然后再成十进制是 0.100000000000000005551115123126.toPrecision(16)=0.1000000000000000,去掉末尾的 0 正好是 0.1
  • 所以我们看到的 0.1 并不是 0.1
  • 可以用更高的精度解释:0.1.toPrecision(21) = 0.100000000000000005551

toPrecision vs toFixed

两者都能对多余数字取整,但是,前者是处理精度,精度是从左到右第一个不为 0 的数开始计算。后者是小数点指定位数取整,从小数点开始数起来

  • 有些用 toFixed 用来四舍五入,但是其实是有 bug 的,
  • 1.005.toFixed(2)=1.00 而不是 1.01
  • 因为 1.005 实际上是 1.00499999999999989,4 是进不了 1 的

解决浮点数方案

由于理论上用有限的空间来存储无限的小数是不可能保证精确的,但是我们可以处理一下得到我们精确的结果

数据展示类

  • 1.4000000000000001 这样的数据要展示的话,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示
  • parseFloat(1.4000000000000001.toPrecision(12)) === 1.4
  • 为什么选用 12 作为精度,因为这是一个经验之谈,大部分够用了

数据运算类

  • 要把小数转成整数后再运算

了解二进制转换

发表于 2018-05-03

十进制是为了人们方便记忆使用的,而计算机的世界里只有 0 和 1,也就是只有二进制

二进制

  • 想要了解计算机,首先要了解二进制

以下来自维基百科

二进制(binary)数是指用二进制记数系统,即以 2 为基数的记数系统表示的数字。这一系统中,通常用两个不同的符号 0(代表零)和 1(代表一)来表示。以 2 为基数代表系统是二进位制的。数字电子电路中,逻辑门的实现直接应用了二进制,因此现代的计算机和依赖计算机的设备里都用到二进制。每个数字称为一个位元(二进制位)或比特(Bit,Binary digit 的缩写)

二进制转换成十进制

以 2 为底数,指数是权重(换句话说是位数-1,权重从 0 开始)

  • 例如:11=>12^1+12^0=3

十进制转换成二进制

每一步除以进制数(2),直到商数为 0,然后取每一步的余数,再倒过来就是了

  • 例如 10=>10/2=5 余 0,5/2=2 余 1,2/2=1 余 0,1/2=0 余 1,然后再取倒就是 1010

如果是有小数的,比如 10.25,那么小数部分乘以 2,取它整数部分的结果,然后再用这个结果继续乘以 2 算,直到小数部分为 0

  • 例如 10.25 的小数部分是,0.25*2=0.5(取整数就是 0),0.5*2=1.0(取 1,然后终止,因为小数部分是 0),答案是 1010.01

浅谈Data URL

发表于 2018-05-01

Data URL 是什么

是一种提供让外置资源直接内嵌在页面中的方案。格式如下:

格式

1
data:[<mime type>][;charset=<charset>][;base64],<encoded data>
  • 第一部分是data:协议头,它标识这个内容为一个 dataurl 资源
  • 第二部分是 MIME 类型,表示这串内容的展现方式,比如 text/plain,就是文本展示;image/jpeg,就是以 jpeg 图片形式展示,同样浏览器会根据这个 MIME 类型来解析数据。
  • 第三部分是编码设置,默认编码是 charset=US-ASCII,可以在浏览器输入框分别输入下面内容:
1
2
3
4
5
6
7
8
// output: &auml;&frac12; &aring;&yen;&frac12; -> 使用默认的编码展示,故乱码
data:text/html,你好
// output: 你好 -> 使用 UTF-8 展示
data:text/html;charset=UTF-8,你好
// output: 浣犲ソ -> 使用 gbk 展示(浏览器默认编码 UTF-8,故乱码)
data:text/html;charset=gbk,你好
// output: 你好 -> UTF-8 编码,内容先使用 base64 解码,然后展示
data:text/html;charset=UTF-8;base64,5L2g5aW9
  • 第四部分是 base64 编码设定,可选
  • 最后一部分是这个 dataurl 承载的内容,可以是纯文本,也可以是经过 base64 编码的内容

平时我们经常见到的是这样的:

1
2
//内嵌base64图片
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");

Data URL 的优劣

优点

  • img 的 src 说明这个是外部资源,浏览器会向服务器拉取一次资源请求,占用网络资源
  • 大多数浏览器都有一个并发请求数不能超过 4 个的限制。如果一个网页里嵌入了太多的外部资源,请求会使整个页面加载延迟。
  • 图片体积太小的时候,不值得占用一个 http 会话

缺点

  • base64 编码的数据体积是原数据的体积 4/3,也就是说 data url 形式的图片会比二进制格式的图片体积大 1/3
  • data url 形式的图片不会被浏览器缓存,这意味着每次访问这样页面的时候都会被下载一次。如果这个图片被整个网站使用,就是一个使用效率问题(已被解决)

    如何将 data url 数据放入浏览器缓存,通过 css 样式文件。所有浏览器都会积极缓存 css 文件来提高页面加载效率

浅谈http

发表于 2018-04-23

http(hypertext transfer protocol)超文本传输协议

http 传输流

发送端在层与层间传输,每经过一层都会被加上首部信息,接受端每经过一层都会删除一条

  • 应用层:本来是 http 数据
  • 经过传输层,加上 tcp 首部
  • 经过网络层,加上 ip 首部
  • 经过链路层,加上以太网首部
  • 然后接收端依次经过链路层,网络层,传输层,应用层

状态码

1xx 传达信息

  • 100 continue
  • 101 switching protocols

2xx 成功

  • 200 OK,表示从客户端发来的请求在服务器端被正确处理
  • 204 No content,表示请求成功,但响应报文不含实体的主体部分
  • 206 Partial Content,进行范围请求

3xx 重定向

  • 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
  • 302 found,临时性重定向,表示资源临时被分配了新的 URL
  • 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源
  • 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
  • 307 temporary redirect,临时重定向,和 302 含义相同

4XX 客户端错误

  • 400 bad request,请求报文存在语法错误
  • 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
  • 403 forbidden,表示对请求资源的访问被服务器拒绝
  • 404 not found,表示在服务器上没有找到请求的资源
  • 405 method not allowed 一般是 get 和 post 弄错了

5xx 服务器错误

  • 500 internal sever error,表示服务器端在执行请求时发生了错误
  • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
  • 504 gateway timeout 网关超时

http 协议格式

http 的请求和响应的消息协议是一样的,分为三个部分,起始行,消息头和消息体。这三个部分以 CRLF 作为分隔符,最后一个消息头有两个分隔符,表示消息头部结束。

  • 起始行:如 GET /index.html HTTP/1.1
  • 消息头部(http header):多个键值对组成,如 Content-Encoding: gzip,多个键值对之间使用 CRLF 作为分隔符
  • 消息体(http body):实体部分,请求实体和响应实体

http 的无状态性

  • 所谓 HTTP 协议的无状态性是指服务器的协议层无需为不同的请求之间建立任何相关关系,它特指的是协议层的无状态性。但是这并不代表建立在 HTTP 协议之上的应用程序就无法维持状态。应用层可以通过会话 Session 来跟踪用户请求之间的相关性,服务器会为每个会话对象绑定一个唯一的会话 ID,浏览器可以将会话 ID 记录在本地缓存 LocalStorage 或者 Cookie,在后续的请求都带上这个会话 ID,服务器就可以为每个请求找到相应的会话状态。

输入 url 到页面加载发生了什么

  • 输入地址
  • 浏览器查找域名的 IP 地址
  • 这一步包括 DNS 具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存…
  • 浏览器向 web 服务器发送一个 HTTP 请求
  • 服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)
  • 浏览器跟踪重定向地址
  • 服务器处理请求
  • 服务器返回一个 HTTP 响应
  • 浏览器显示 HTML
  • 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS 等等)
  • 浏览器发送异步请求
1…456…9
Yoki

Yoki

云在青天水在瓶

82 日志
21 标签
© 2019 Yoki
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4