面试角度的前端学习路线

1.CSS两栏、三栏布局

我知道是columns属性,不过试验了一下没有生效。郁闷😒。为什么MDN的例子是正常的,我自己写一个p标签加上columns属性不生效呢?

两个元素对比
效果对比

很玄学吧,前端就是玄学,遇到这种问题真没啥好说的,只能慢慢排查,可能是浏览器,也可能是代码字符的问题。

但是最后的结果让我惊呆了。原来是我输入的字符串没有空格,导致排版时被当作一个单词处理,因而无法形成分栏。那么我是否可以结合word-break属性(break-all:单词断行和break-word:一行显示不下才断行,否则换行)呢?可以,并且在这里是一样的。

2.CSS水平垂直居中

这个方法比较多。

方法一(推荐):使用单行flex布局,设定justify-content(x轴)与align-items(y轴)等于center

方法二:使用百分比定位,calc(50% + 固定值)使元素居中。

方法三:margin:0 auto实现水平居中,再利用定位垂直居中

关于使用vertical-align,对行内元素(或设置line-height的元素)有效,表格这里不讨论。有一个面试官说只有一个inline元素时这个属性无效,但是我实验了下是有效的emm

行内元素
<div style="background:orange;font-size:16px">
<div style="display:inline-block;vertical-align:middle">HAHA</div>
<div style="display:inline-block;">A</div>
BBBBB
</div>

3.闭包在js的作用

首先,闭包是一种结构,利用外层函数存储数据,内层函数操作数据。使用时调用外层函数,返回内层函数。之后调用返回的内层函数即可,内层函数的返回值是外层函数的数据。每一次调用外层函数,都有一个独立的存储数据的环境。

比如下面的例子:

http://js.jsrun.pro/tWfKp/edit

闭包形成了一种类似于类的数据结构,可以间接访问、控制变量。

4. typeof 与 instanceof

typeof A的返回值是js中的类型,A instanceof B的返回值是布尔值。

http://js.jsrun.pro/yffKp/edit

typeof很直观,每一个变量都对应一种js中的基本数据类型,但instanceof不同,它的原理是constructorB.prototype是否存在于A的原型链上。在A和B的情况下A.___proto__和B.prototype是完全相同的(===)。

5.原型链,继承

js的原型链实现了类似于其他语言的继承功能。

当访问一个对象的属性时,js引擎不仅会寻找其属性,还会寻找其原型上的属性(优先级低于本身的属性)。对象的原型可以用__proto__访问,指向构造器的prototype(js中的构造器只是普通函数)。

http://js.jsrun.pro/2ffKp/edit

6.bind apply call

我们都知道,这三个函数用于绑定this。并且我还实现过。

js中一个对象的属性如果是包含this的函数,那么this默认指向这个对象。如果想变更this指向,就需要bind。

bind的参数是this,返回值是绑定this后的函数。

apply和call是直接执行绑定this后的函数,他们的第一个参数都是this。call接受参数列表,apply接受参数数组。

http://js.jsrun.pro/k2fKp/edit

7. var let 和const

let和const是es6新增的语法。最大的不同是支持块级作用域。并且不允许重复声明。let即变量,const即常量。

var只有函数级作用域。如果什么也不用直接声明变量,那么会被提到global对象。

8. new操作符原理

首先看new的作用。new通过接受一个函数,返回了一个对象。这个对象的来源有两种情况,一种是构造器返回了一个对象,另一种是构造器没有返回一个对象。

我们需要创建一个新的对象并将其原型链接到构造器的prototype,之后我们绑定this到新对象,将构造器执行一次。如果函数内部有this.xxx的赋值语句,那么新的对象的xxx属性就会被赋值。

当构造器返回了一个对象,我们将返回的对象作为最终返回值;否则,我们将新对象作为返回值。(也就是说如果构造器返回了对象,那么this.xxx这种语句会无效)

http://js.jsrun.pro/62fKp/edit

9. 箭头函数

ES6新语法:()=>{}

箭头函数的this等于当前上下文的this,意味着即使一个对象的箭头函数方法被执行,该对象也不会变化。

同时,不可以作为构造函数。毕竟它没有绑定this,也没有arguments对象,不过可以用rest语法接受参数。

也不可以使用yield。

10. promise 以及手写实现思路

promise语法用于异步操作。通常js代码按顺序执行,上一条指令没有执行完就会阻塞,但有一些应用场景需要等待任务完成,又不想阻塞其他代码。比如网页加载/网络请求完毕后播放动画,就需要onready或onload这种回掉函数。Promise是对回调函数的一种包装。

Promise的用法:使用new Promise( function ),其中function可以接受两个参数resolve( function )与reject( function ),只要执行这两个函数之一就可以完成该Promise,并在链式语法下进行下一步操作。

then、catch的区别是,catch只在出错时被调用。

手写实现思路嘛,从来没想过,对我来说应该是比较难的。我记得promise有很多polyfill,那么肯定是可以写的。这个Promise对象啊,接受了一个函数作为参数,还向这个函数传递resolve和reject。可以理解成promise对象内执行了接受的函数,一旦resolve或reject被(异步操作如setTimeout)触发,转过来就执行then中的内容。那么可以认为resolve函数实际上执行了then中的内容。不过这么想的话,new Promise时不知道then中的内容,所以这样不行。那么能不能先执行,resolve或reject实际上在Promise对象内做了一个标记,因此调用then时是在检查标记是否为“已完成”。这里也有点问题,then难道要反复查询任务是否已完成吗?每隔一段时间就查询,理论上可以实现Promise的效果,但实际上不知道是不是这么做的。有时间看看Promise的实现呗。

11. promise.all应用场景

我有一个真实的适用场景,图片加载完之后再播放动画。。。字面意思,所有的promise都完成才执行这操作,那么有些操作可能需要调用多个API,可以使用Promise.all。

12. promise和async/await区别

promise是一种对异步的包装,实现了链式调用,async/await也是嵌套回调的一种替代,它用类似于同步代码的书写方式描述异步操作。

async/await是基于promise的实现,async function的类型会自动转换为promise,其返回值也是promise对象。在async函数中,await处会等待后面的promise执行完毕,再执行剩下的代码。如果await后不是promise对象,那么立即作为promise返回。

使用async/await时,通常和promise紧密结合,比如await后面可以跟promise.all以实现等待多个异步操作,比如可以用catch对async function捕捉错误。

代码演示

13. vue的生命周期 *

记一下,从new Vue()开始,初始化事件和生命周期后(事件是指DOM事件吗),允许第一个钩子beforeCreate添加用户代码。之后是注入和检验,这一块还不懂,注入什么,检验prop类型吗。然后是created钩子。之后才开始判断有没有el属性,编译template。beforeMounted。创建vm.$el替换el是什么意思呢?el本来是用选择器选择的一个元素,现在用$el替代对元素的操作。Mounted。之后就是beforeUpdate和Updated。销毁时有beforeDestory钩子和destroyed钩子。Destroy解除了绑定、销毁了子组件和事件监听器。

关于Vue的生命周期,我之后还会继续了解。

Vue 实例生命周期

14. diff算法 *

这个等等实现一下。我没有了解过diff算法,先放着。

参考https://www.zhihu.com/question/29504639

现在先理解一下,diff算法是为了解决什么问题,虚拟DOM为什么被提出来。

在经典的设计思路下,每增加一种操作,都不得不包含更新DOM的代码。当应用复杂起来,就有人提出MVP、MVC等设计模式,尽管这些设计模式让代码结构更清晰,但该写的代码还是得写。

有一种设计模式减少了视图逻辑,它就是MVVM,通过模板和引擎实现双向绑定。虚拟DOM是实现MVVM的一种方式。这种方式通过引擎(有一点更新就)更新所有DOM,但避免了浏览器对DOM重新渲染带来的巨大开销。

在默认的情况下,我们考虑更新所有DOM就是拿新的从模板处理得到的DOM树替换原有的DOM树,重新渲染一遍网页。diff算法使这个过程变为,只替换变化的部分。

怎么实现呢?可以在js中用对象拷贝dom节点属性,然后拿更新后的对象与更新前的对象比较,找出不同的地方以后,直接修改dom节点属性。

这里其实应该有一个疑问,虚拟dom和mvvm和模板引擎的关系。前面说了,更新所有dom就是用另一颗DOM树替换原来的DOM树,那为什么不直接更改变化部分,而一定要先生成,后对比,再改变呢?嗯,用模板引擎从状态生成dom树是比较容易和直观的。如果想在数据变化时直接应用到所有相关的地方,那就得定义很多很复杂的逻辑,比如给对象属性A设置setter,除了A变化直接引起视图变化,还引起与A关联的逻辑引起的视图变化,想要自动生成这段逻辑,就是mvvm的另一种实现方式了,想起来其实非常复杂。

以上论述就是关于为什么要使用虚拟DOM。

接下来具体讨论一下实现。

表示DOM树需要三个描述信息:标签名、属性以及子节点。然后可以利用render函数真的创建DOM节点。

diff算法。git管理代码时也有diff算法,它不会把所有行和所有行做比较。这里的diff算法也不会让所有节点相互比较,而是只考虑同一层级。实际操作中,将新旧两棵树深度优先遍历并标记节点便于比较,将有差异的结点(用数组将差异)记录下来。

结点替换、属性修改、文本节点内容修改都可以直接记录。值得一提的是调换、新增、删除结点,应该用列表对比算法优化,避免全部替换。(为了使用列表对比算法,结点应该有key,但key相同不代表内容相同)

应用差异(patch):表示dom树前面已经说了,但还需要应用差异。同样DFS遍历DOM树,对替换和修改直接应用,新增、插入、删除需要再写一个函数处理。

之后虚拟DOM就实现了,只需要反复比较差异,再应用差异。

代码我还没有写,考虑到具体实现比较花时间,先处理一下别的问题。

与虚拟DOM相关的还有一点,那就是框架如何实现watch。

我们知道,javascript可以直接设置属性的setter与getter,因此我们遍历data中的每一个属性并为其设置setter。需要注意的是,当一个key用作getter或setter,也就意味着无法存储值。所以getter和setter需要指向另一个属性。

基于这种特性,我们可以用订阅-发布模式设计watch。即一旦属性被修改,执行绑定的函数。我在28里面已经部分实现了订阅-发布模式,现在的情况就是,需要watch的属性,绑定处理函数到列表,并使其setter可以emit事件。当然也可以直接让setter执行处理函数。

15. 状态码304

Not Modified。协商缓存一致,客户端向服务器发起get请求后(服务器判断)内容与先前没有变化,于是返回304。

16. ES6新特性

不一一列举了,主要是箭头函数,const/let,模板字符串,解构赋值,rest运算符

17.防抖和节流

这两个概念针对持续触发的事件,比如onMouseMove等。

防抖debounce:一定时间内只执行一次,如果在这个时间内又触发,重新计时。实现的话,额外新建一个变量,或者在原本的回调函数上写一个闭包,用于保存时间变量。一旦函数被触发就会根据时间变量检查是否执行,正在计时就重新计时,否则就执行后重新计时。如果想在时限过后执行,执行那里改成setTimeout即可。“防抖”是一个很直观的描述词,就像称重,稳定下的结果

节流throttle:一定时间内只执行一次,但不会在期限内重新计时。逻辑和防抖类似,只是去掉正在计时的情况下重新计时的代码。

18.抓包

19. 跨域以及xss、csrf

20. http2.0

http2.0在应用层与传输层之间增加了二进制层,将数据分为更小的块。为一些功能提供了基础。

多路复用:http请求被拆分成二进制帧,一个tcp连接中可以同时请求和接受多个资源了,因此没必要打包资源。http1.1会对同一网站同时请求的资源做限制造成阻塞。

请求优先级:不同类型文件的返回顺序可以不同。客户端可以指定优先级,但最终执行还是服务器决定。

服务器推送:服务器可以对客户端发起多个响应。

首部压缩:维护header表,只传送更新的部分。

21.webpack *

webpack的基本概念

入口(entry):这很好理解,打包工具需要从一个文件开始引入其依赖,再引入依赖的依赖。默认./src/index.js。有时候我们需要分离第三方库与app,那么entry可以是一个对象。

输出(output):何处输出打包的js。如果入口有多个,可以用[name]替代文件名。

loader:帮助webpack理解js以外的文件。module.rules中配置,也可以引入资源时指定或用命令行指令指定(不推荐)。

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },//左边斜杠间是正则表达式
      { test: /\.ts$/, use: 'ts-loader' }
    ]
  }
};

插件(plugin):执行node允许的任何操作。暂时用不到,略过。

模式(mode):开发环境或生产环境。不同环境有不同的默认优化,全局变量process.env.NODE_ENV值不同。

其他

配置:就是一个导出对象的js文件。

模块:module、chunk理解为webpack 模块,通过loader支持css、ts甚至图片等文件。在webpack文档中chunk就表示一块的概念,不是术语。bundle就是打包后的一堆模块。

webpack之前:iife,以前看过忘了,就是避免污染全局作用域,于是可以用来组合不同模块还不冲突。后来有了各种标准,比如commonjs、amd,还有很新的es模块,但是就种种原因webpack弥补了他们的缺点。

(function () {
    statements
})();

runtime与manifest:这一部分代码用于管理模块间的依赖,和应用程序代码、第三方库代码共同组成最后的应用。runtime是代码,而manifest是数据,即runtime通过manifest解析和加载模块。

target:编译的目标环境,node或web等。

模块热替换:发出http请求对比chunk list,热更新。

22.babel *

23. 原生DOM拖拽

我没有看过这方面文章,感觉应该是onclick持续一段时间后触发拖动,onmousemove根据e移动被拖动元素(也可以是与被拖动元素大小相同的另一个元素),在松手的事件中取消拖动状态。但我的这种设想只适合绝对定位的情况。

来写一个html5下的dom拖拽:

<div id="xx" draggable="true" ondragstart="drag(event)" style="background:red;width:40px;height:40px"></div>
<div ondrop="drop(event)" ondragover="allowdrop(event)" style="border:1px solid black;width:100px;height:100px;"></div>
function drag(e){
  console.log(e)
}

function drop(e){
  console.log('drop',e)
  e.target.appendChild(document.getElementById('xx'))
}

function allowdrop(e){
  e.preventDefault();
}

24. JS中的原型继承

前面已经说到过一点了。这里再展开一下。我们需要让被继承函数的方法都定义在原型上,数据在this里定义。

继承它的函数可以运行被继承函数的构造函数,并将this指向自己,这样做继承了构造方法。

其他方法通过原型继承。我们应该让child的prototype内容与parent的prototype相同,再更改prototype.constructor。

虽然是原型继承,但是大体上可以把构造器看作类,用call使用parent的构造器继承数据,并用prototype继承方法。

25. 关于缓存机制

http头中有几个字段可以控制缓存。

Expire/Cache-Control会指定缓存的时间。Expire限于http1.0,第二次请求在一定时间内就直接请求缓存。现在浏览器默认http1.1,所以用Cache-Control。

Cache-Control可以添加多个字段,取值包括private/public(允许客户端缓存)、no-store(强制缓存、对比缓存都不触发)、no-cahce(对比缓存)、max-age(缓存内容会在一定时间后失效)等。

强制缓存在本地,对比缓存则由服务器判断,并返回304状态码。

还有几个属性解释一下。Last-Modified即资源最后修改时间,If-Modified-Since作为请求头将上次的Last-Modified发送给服务器,于是服务器可以判断资源是否需要更新。ETag是资源唯一标识符,If-None-Match也是将上一次的标识发送给服务器,服务器根据标识符是否变化决定返回304还是其他状态码。

26. 手写一下ajax与fetch

我太习惯用库了,一次都没有用过xmlhttprequest,fetch倒还用过,现在手写一下。

我们先来看看new出来的xhr对象长什么样子:

几个钩子,还有readyState、response、responseURL、status、statusText、timeout、upload以及withCredentials。

先解释一下readyState,0表示请求未初始化,1表示服务器连接已建立,2表示请求已接收,3表示请求处理中,4表示请求完成响应就绪。onprogress对应3,onload对应4。每一次readyState变化,都会调用onreadystatechange。

status是状态码,timeout是可以设置的超时毫秒数,withCredentials=true的话跨站点访问就会带上cookies、认证头或者tls客户端证书,这个细节不清楚,一般是cookies。upload上传的时候用,这里不讨论。

<script>xhr= new XMLHttpRequest();
xhr.open('GET','https://www.w3school.com.cn/',true);//第三个参数指定异步/同步
xhr.send();
xhr.onreadystatechange=function(){
  console.log(xhr.readyState,xhr.status)
//  console.log(xhr.responseText,xhr.responseXML,xhr)
console.log('header',xhr.getAllResponseHeaders(),xhr)
}
xhr.onload=function(){
  console.log('onload',xhr.readyState)
}

xhr.onloadend=()=>console.log('loadend',xhr.readyState)

xhr.onprogress=()=>console.log('progress',xhr.readyState)

xhr.onloadstart=()=>console.log('start',xhr.readyState)

</script>

fetch是xhr的先进替代,接受url,返回promise对象。但是response的body是Readablestream类型,其text()方法返回的仍是promise对象。同时fetch默认不带cookie,并且需要手动指定‘content-type’等http头。还有一点,fetch只要有状态码返回就不会被捕捉到error,就是说只有网络错误,promise对象才会被reject,不过response.ok可以判断。

<script>
fetch('https://www.w3school.com.cn/').then(r=>console.log(r,r.text().then(r=>console.log(r))))
</script>

27. JSONP

屡次面试都问到了jsonp如何实现,我不确定回答的怎么样。我对它的理解是,动态加载script标签,其内容是调用一个函数B,参数是数据(script标签的地址可以包含客户端传给服务器的数据)。而这个回调函数B,写在主程序中,它负责将数据接收并处理。

实际用的时候,某一处需要请求跨域资源,于是调用函数A动态加载一个标签,这个标签加载完后调用了回调函数B,B完成了对获取到的数据的处理过程。

这里有一个问题,如果想做一个jsonp库怎么办?也就是说我要发送请求并得到返回的结果。现在可以设计一个函数C,参数是url与回调函数的名字callback。返回的jsonp会自动调用回调函数。我们可以把处理数据的过程封装起来,也就是说callback不让用户定义了,callback是我们生成的函数,然后接受一个用户的处理数据函数。具体怎么做呢?我们在全局对象上定义callback函数,然后这个函数的用途是调用接受的用户处理数据的函数。

28. 发布-订阅模式

去年做过字节8道题的笔试,其中就有一道与设计模式有关,完全不会。那题是观察者模式/发布-订阅模式。

说实话我虽然不会,但on和emit确实是非常常见。查阅了一些资料后,大概明白,这种模式是对象间的一种一对多依赖关系。订阅者订阅事件,发布者在事件来临时通知订阅者(执行订阅者注册的代码)。

在写之前梳理一下思路。对象A拥有on方法和emit方法,on方法用于其他对象监听事件,emit方法用于触发事件。

let P={
    on:function(obj){
        this.subscriber.push(obj);
    },
    subscriber:[],
    emit:function(){
        this.subscriber.forEach(item=>item.content='ASD')
    }
}

let S1={content:'XAB'}
P.on(S1)
console.log(P,S1)

当然我写的这个是很简单的实现,我就是表述一下文字。实际上至少还要事件名。并且on注册的一般是回调函数,前面搞成对象不是很合适。

let P={
    on:function(event,fn){
        this.subscriber[event]=fn;
    },
    subscriber:{},
    emit:function(event){
        this.subscriber[event]();

    }
}

let S1=function(){
    console.log('S1')
}
P.on('tt',S1)
console.log(P,S1)
P.emit('tt')

再进化一下,支持emit参数。

let P={
    on:function(event,fn){
        this.subscriber[event]=fn;
    },
    subscriber:{},
    emit:function(event,message){
        this.subscriber[event](message);

    }
}

let F1=function(message){
    console.log('S1',message)
}
P.on('tt',F1)
P.emit('tt','hi')

再进化一下,支持注册多个函数。

let P={
    on:function(event,fn){
        (this.subscriber[event]||(this.subscriber[event]=[])).push(fn);
    },
    subscriber:{},
    emit:function(event,message){
        this.subscriber[event].forEach(item => {
            item(message)
        });;

    }
}

let F1=function(message){
    console.log('S1',message)
}
let F2=function(message){
    console.log('S2',message)
}
P.on('tt',F1)
P.on('tt',F2)
P.emit('tt','hi')

当然还可以有once,off,还有对特殊情况的处理,但是这里不写了。这里只是介绍概念。

29. 元信息

写这些完全是为了准备面试,很多时候被问到一个知识点,我其实是理解背后的原理,但我不知道实际应用中某个场合它就是这么做的。比如我被问到watch如何实现,我当时知道setter的用法以及发布订阅模式的一些信息,但是没有看过解析的文章,很难保证自己说的正确,于是就略过去了。这是非常遗憾的事情,所以需要特地为面试准备。

30. 同时有多个ajax请求

我们知道ajax通常都是异步方式请求,那么如何保证多个异步请求之间的拓扑顺序呢?

最直接的方式一定是将异步代码改为同步代码。我觉得也可以用promise实现管理,它提供了all方法、race方法、then方法完全可以组织多个异步请求。用回调计数相当于all方法。

改为同步的做法牺牲了时间,我没有必要等第一个请求完再触发第二个请求。我们可以让所有请求都发出,但是按顺序执行。

31. 循环引用

http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

听面试官说,require和import处理循环引用的方式是不一样的。

顾名思义,循环引用即A引用B,B也引用A的情况。这种情况很容易避免,但如果是a-b-c-a或者更复杂的情况,就棘手了。

commonJS的处理

首先require的过程会把引入的文件执行完,再执行后面的代码。

一旦发现某个模块被循环加载,即a require b,b require a时,require a只会得到a require b之前的执行结果,之后b继续执行,a继续执行。

ES6的处理

es6的import只是生成引用,不存在加载的问题(不会在引入时执行模块,只在需要时取值),自然没有循环加载的问题。带来的问题是,

也就是说,commonJS和ES6模块的最大区别在于,是否在引入时执行模块。但是,,,一般es6代码都会用babel转译,import还是会被转译成require。

32. less/sass

我会近期做一个小APP用上。我以前用stylus。我不觉得他们有多不可替代,但是大家都在用,面试也要问,那就只好用用了。

C++读取文件内容

很多题都会要求读取txt作为输入。

头文件

fstream

打开文件

ifstream inputData("/cpp/input.txt");
    if (!inputData.is_open())
    {
        cout << "open failed" << endl;
    }

...
inputData.close();

读取一行的内容

string temp;
getline(inputData, temp);

分隔一行的内容(split)

char a[65];
strcpy(a, temp.c_str());//temp是string类型,需要转换成const char*
char *w = strtok(a, " ");//第一个参数char*,第二个参数是分隔符
char *h = strtok(NULL, " ");//第一次以后,第一个参数传NULL即可获取下一段字符串

//如果内容是数字,atoi(w)与atoi(h)可将char*转换成int

分布式计算(1)

网格计算,云计算与分布式计算的区别

网格计算强调资源共享,使用者同时也是资源共享者,用于计算集中性服务(不便扩展 )。云计算的服务提供者少数而集中,资源专有,便于自动化扩展(其中对等计算更便于扩展,即每个节点拥有对等的服务,可以互相使用数据),使用者无需贡献资源。

分布式计算指将大型任务划分成部分,分配给其他计算机,并将计算结果组合的解决方案,包括云计算与网格计算。而并行计算虽然类似,但并行的单位是处理器,执行并行计算的单位是单机。

CAP理论

Consistency(一致性):不同节点上的同一份数据在同一时间是否一致。

Availability(可用性):集群中一部分节点故障后(包括分区同步信息时),集群整体是否还能处理客户端的更新请求。

Partition tolerance(分区容忍性):是否允许数据分区,即不同区域间在一定时间内无法通信。

CAP理论认为三点至多只能满足两点,比如满足CA的关系型数据库无法做到数据分区并且及时响应( 如果分区,那么在分区同步信息时,系统无可用性,如果仍然提供服务,不同区域的服务器数据将不同),而满足CP的数据库在分区同步信息时无法提供服务,如果cp数据库没有多份数据,那么节点故障后即无法提供服务。对于AP数据库,由于数据同步时提供服务,必然会造成数据不一致。

IPC范型

ipc即interprocess communication,从上层到下层分别是RPC,socket API与data transmission。Remote Procedure Call或Remote method call是最高层次抽象,数据作为参数和返回值。下面的socket api允许应用程序使用名为socket的逻辑结构交换数据,每一方建立一个socket,待发送数据被写入socket,在另一端的应用从socket中提取数据。

最底层的IPC抽象层利用并行或串行机制传输二进制信息,对应OSI传输层及以下(socket本身处于传输层与应用层间)。

分布式计算范型

消息传递范型

A发送请求消息,B接收并应答,并可能继续触发A应答。

客户-服务器范型

服务器被动响应客户端请求。

peer to peer范型

每个节点责任相同,即充当服务器又作为客户端。

消息系统

消息系统充当独立进程的中介,进程间不耦合,而是通过消息系统异步交换信息。消息系统分为两种:point to point与发布-订阅模型。point to point即一个节点向另一个节点发送消息;发布订阅模式中每条消息都与事件有关,感兴趣的应用程序可以订阅事件,事件被触发时将可以收到消息,因此很适合用于广播和组播。

远程过程调用

A向B发送请求并携带参数,接收B的返回值。

分布式对象

和rpc类似,但存在远程对象,对象除了可以使用方法,还拥有自己的数据。

网络服务

服务请求者发送请求到目录服务器,目录服务器返回方法的引用,因此可以使用更多的远程方法。

移动代理

发送请求的主机发送一个代理,代理沿着路径传播,在路径上使用各种服务并最终返回到请求主机。

云服务模型

Iaas,Paas,Saas。

Infrastructure as a service,即提供虚拟化的基础硬件,如云服务器。

Platform as a service,即提供开发、计算平台的服务,如App Engine。

Software as a service,即提供软件作为服务,一般就是B/S模式。

计算机安全(1)

少量概念

CIA

即Confidentiality(保密性)、Integrity(完整性)、Availability(可用性)

TCSEC、ITSEC、CC

计算机安全的标准,按时间排序,TCSEC最先提出了TCB(可信计算基)与访问控制机制,ITSEC提出了CIA,CC即现行的信息技术安全评估通用准则。

符号

P:Plain Text,即明文。

C:Cipher Text,即密文。

E:Encrypt,即加密。C=E(P)

D:Decrypt,即解密。P=D(C)

一个密码系统必须满足P=D(E(P))

分类

经典密码

即对称加密,加密与解密的密钥相同或能相互推导。包括DES、AES等。

公开密钥算法

非对称加密,加密使用公开的Pulic Key,解密使用私有的Private Key。包括RSA等,支持实现数字签名。

密码分析

包括穷举、统计分析、数学分析。

安全性

仅一次一密能达到无条件安全,但只要解密成本大于信息价值,或解密时间超过信息有效期即达到安全目的。

经典密码

替换技术

将明文替换成密文,可以用单表或多表,也可以替换单个字符或连续字符。

多字母替代:playfair

双字母作为一个单元,遇到连续的字母需要添加分隔符X,如果字符串长度是奇数补一个Q,然后对照5x5的密码表(英文有26个字母,所以将i、j视为同一字母),同行的字母右移,同列的字母下移,其他字母取形成的矩形的另外两角。

如密钥为gojam(密钥不能有重复字母),可以制成如下密码表:

g o j a m

b c d e f

h k l n p

q r s t u

v w x y z

现在加密明文“HeEatApple”,首先添加分隔符X,变成“HeXEatApXple”,刚好可以分为六组HE XE AT AP XP LE。

HE对应BN。(我默认了左下角对应左上角,右上角对应右下角)

XE对应DY。

AT对应EY。

AP对应NM。

XP对应LZ。

LE对应DN。

因此密文为BNDYEYNMLZ。

解密过程与加密过程相反,聪明的你一定可以解出来。

多字母替代:Hill Cipher 希尔密码

基于矩阵的线性变换,将m个连续字符转为m个密文。密钥K是m*m的矩阵,在模26运算中可逆,即K*K^-1=I(mod 26)。

加密时m个连续明文作为行向量与密钥K相乘并mod26,解密时m个连续密文与K^-1相乘并mod26。

矩阵乘法

a行b列的a*b矩阵与b行c列的b*c矩阵相乘,最终得到的矩阵a行c列。(即第一个矩阵的宽应该与第二个矩阵的高相同,否则可能无法相乘)

得到的a*c矩阵中,用C[i,j]表示第i行第j列元素,用A[i,j]与B[i,j]表示前两个矩阵的第i行第j列元素,有以下关系:

C[i, j]=A[i, 0]*B[0, j]+A[i,1]*B[1,j]+A[i,2]*B[2,j]+...+A[i, b]*B[b, j]

例如C[0,0]=A[0,0]*B[0,0]+A[0,1]*B[1,0] +A[0,2]*B[2,0]+...+A[0,b]*B[b,0]

也就是第一个矩阵的第一行乘以第二个矩阵的第一列,第一个矩阵的第一行乘以第二个矩阵的第二列,依次这样。

代数余子式

对矩阵A中的元素A(i,j),将第i行与第j列从矩阵A中移去,余下的部分作为行列式的值再乘以(-1)^(i+j)得到的数叫A[i,j]的代数余子式。

以A=[[3,2],[1,1]]作为例子,A(0,0)的代数余子式为1*(-1)^(0+0)即1。同理A(0,1)=-1,A(1,0)=-2,A(1,1)=3。

伴随矩阵

像上面由各个位置的代数余子式构成的矩阵被称为伴随矩阵,A的伴随矩阵A*=[[1,-1],[-2,3]]。

求逆矩阵

逆矩阵A^-1=A*/|A|,其中|A|代表A的行列式,等于3*1-2*1=1。

A*是转置后的伴随矩阵,因此A*=[[1,-2],[-1,3]]。

因此逆矩阵A^-1=[[1,-2],[-1,3]]。验证A乘以A^-1=[[1,0],[0,1]],即单位矩阵(对角线全是1,其余部分全是0)。

加密/解密

以上2*2矩阵作为例子,如明文为XD,即[23,3](26字母从0开始,25结束),乘以A等于[23*3+3*1,23*2+3*1]=[72,49],然后mod26=[20,23],即UX。

解密时[20,23]乘以A^-1,等于[20*1-23,20*(-2)+23*3]=[-3,29],mod26后等于[23,3]。

这种加密方式能够防止只有密文的统计学攻击,因为密钥矩阵本身包含了信息,不同频率的字符被分散到了不同密文。但如果有很多明文-密码对,或者攻击者可以获取各种明文的密码,那么密钥K很容易被计算出来。

置换技术

传统密码的两个要点,即替换与置换,交换位置能更好地保密信息。

QQ互联的注意事项

业务逻辑

最近试了一下用QQ登陆联系原有的账户体系,由于用了LeanCloud提供的后台服务,我只用关心QQ互联的部分。

首先,它的业务逻辑是:你先设置一个按钮,要跳转到QQ登陆页面,登陆完成后跳转到回调页面。回调页面即可以是用户主页也可以是一个中间页面用于跳转。

在PC上,曾经常见的模式是弹出QQ登陆小窗,登陆后原页面收到登陆成功的回调更新登陆状态。

但以上的方式如果在移动端使用,由于没有回调地址,唤起QQ登陆后无法跳转到用户界面,并且原页面也不会收到回调。

所以现在有一个模式,就是点击QQ登陆后原页面直接跳转到QQ登陆页面,登陆成功后回调地址收到accessToken与openId,之后再处理业务逻辑。

OAuth 2.0

A->B->C->D->E
先获取Code,然后再获得Token,重定向URI必须保持一致

现在你可以用他们的API来实现以上功能,比如QQ互联提供了QQ登陆地址的格式,通过Authorization获取AccessToken,通过AccessToken获取openId的接口,以及getUserInfo等api的接口。但我尝试后发现这些api没有设置跨域头,如果直接在前端获取会被浏览器拦截,因此我选择用他们的PHP SDK。

PHP SDK

SDK使用虽然很方便,但还是没有设置跨域头,你需要在页面输出前设置Access-Allow-Control-Origin。还没完,PHP SDK使用了session,不能像官方的获取用户信息的api一样直接请求,你还需要设置Access-Alllow-Control-Credentials,在前端请求时如果用AXIOS,也要加上withCredentials的配置。

由于我习惯把不涉及安全业务逻辑放到前端,所以我在callback.php里将AccessToken与OpenId传给前端的Login组件,由前端请求后判断是否是新用户等等。(也由于使用了LeanCloud,php并不能直接查数据库,所以干脆放在前端)

JS SDK

QQ互联我花了不少时间,主要是理解它的业务逻辑,还有跨域的问题。我开始用的是JS SDK,但似乎它已年久失修(12年更新过),而且它要求用script标签引入,我在前端还要检查SDK对象是否已加载完成,让我的代码很混乱,加上还是要后端协助解决跨域,所以还是不用了。

PHP&CURL 小插曲

其实还有一个小插曲,PHP SDK需要curl,但我安装的php 7.3 似乎并编译不上,而且php7.2-curl都是有的,但php7.3-curl还没有。。。只好换成7.2。

计算机网络笔记(6)

基本数据链路层协议

协议1:一个乌托邦式的单工协议

单工协议即数据只能单向传输。这个协议假设信道永远不会丢失或损坏帧,接收方的处理能力足够快,缓冲区足够大。

发送程序无限循环,接受程序响应事件,协议1中不包含流量控制和纠错功能。

协议2:无错信道上的单工停-等式协议

发送方的速度如果过快,接收方会被淹没,除了增强接收方的处理能力,可以让接收方给发送方发送反馈,发送方收到后才可以发送下一帧。

发送程序无限循环并等待接收方确认,接受程序响应事件后发送确认帧。

协议3:有错信道上的单工停-等式协议

有错信道上传输数据需要增加校验,接收方仅在数据正确时发送确认帧。但在确认帧丢失的情况下(超时),发送方将重发。接收方难以判断帧是重发还是新发,因此帧前加上序号以区分。

重复的帧也会收到确认帧,以便发送方决策。