什么是报文?

1.报文(message)
我们将位于应用层的信息分组称为报文。报文是网络中交换与传输的数据单元,也是网络传输的单元。报文包含了将要发送的完整的数据信息,其长短不需一致。报文在传输过程中会不断地封装成分组、包、帧来传输,封装的方式就是添加一些控制信息组成的首部,那些就是报文头。

2.报文段(segment)
通常是指起始点和目的地都是传输层的信息单元。

3.分组/包(packet)
分组是在网络中传输的二进制格式的单元,为了提供通信性能和可靠性,每个用户发送的数据会被分成多个更小的部分。在每个部分的前面加上一些必要的控制信息组成的首部,有时也会加上尾部,就构成了一个分组。它的起始和目的地是网络层。

4.数据报(datagram)
面向无连接的数据传输,其工作过程类似于报文交换。采用数据报方式传输时,被传输的分组称为数据报。通常是指起始点和目的地都使用无连接网络服务的的网络层的信息单元。

5.帧(frame)
帧是数据链路层的传输单元。它将上层传入的数据添加一个头部和尾部,组成了帧。它的起始点和目的点都是数据链路层。

6.数据单元(data unit)
指许多信息单元。常用的数据单元有服务数据单元(SDU)、协议数据单元(PDU)。
SDU是在同一机器上的两层之间传送信息。PDU是发送机器上每层的信息发送到接收机器上的相应层(同等层间交流用的)。

下面是网络模型中每一层对应的信息情况

应用层——报文(message)
传输层——数据段/报文段(segment) (注:TCP叫TCP报文段,UDP叫UDP数据报,也有人叫UDP段)
网络层——分组、数据包(packet)
链路层——帧(frame)
物理层——P-PDU(bit)

TCP和UDP的区别

UDP
TCP
是否连接
无连接
面向连接
是否可靠
不可靠传输,不使用流量控制和拥塞控制
可靠传输,使用流量控制和拥塞控制
连接对象个数
支持一对一,一对多,多对一和多对多交互通信
只能是一对一通信
传输方式
面向报文
面向字节流
首部开销
首部开销小,仅8字节
首部最小20字节,最大60字节
适用场景
适用于实时应用(IP电话、视频会议、直播等)
适用于要求可靠传输的应用,例如文件传输

快速理解WebSocket 协议

在了解WebSocket之前需要先了解socket是什么。
首先要知道,现在任何的网络协议都是基于socket协议。
socket的中文翻译叫“套接字”,然而这个中文翻译非常晦涩难懂,很难解释清楚。
其实socket直接翻译过来的含义是插座的意思。
我的个人理解就是2个人互相发送文件的时候,如果发1个文件就在插座上插1条线,再发另外1个文件就再插1条线。不管是发文件还是收文件,都是通过插座上面的线来传输,这个线就叫“线程”。而插座就是为了“创建”和“关闭”这些“线程”而设计的。

先看一下socket的流程图

我们发现socket的流程非常简单,主要分三步
1.创建
2.收发
3.关闭
程序中创建socket协议通常有两个参数,第一个为ip协议规范(ipv4/ipv6),第二个为协议类型(TCP/UPD)
例如PHP的socket_create方法,socket_create ( int $domain , int $type , int $protocol ) : resource
具体参数对应的含义请查看文档

接下来正式说明什么是WebSocket
定义:WebSocket是一种在单个TCP连接上进行全双工通信(同时双向发送数据)的协议。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
http协议url地址是长这样http://example.com:/some/path
而websocket协议的地址是这样ws://example.com:80/some/path

在客户端使用WebSocket非常简单

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { //连接成功后的回调
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) { //接收数据
console.log( "Received Message: " + evt.data);
ws.close();
};

ws.send('your message’); // 发送数据

ws.onclose = function(evt) { // 连接关闭后的回调
console.log("Connection closed.");
};

服务端使用WebSocket如果是NodeJS常用的就是
Socket.IO
如果用java就是Tomcat7.0以上
PHP用swoole

WebSocket协议的优点是什么?
最大的优点就是在实现即时通讯的方法上面更节省资源。
先说下面两种及时通讯方式的缺点
HTTP轮询:因为是单向通信,1次请求对应一次响应,然后关闭连接。如果服务端没有最新信息,那么就有大量的空响应回来,也就是无意义的请求,浪费资源。
Socket长连接:一旦建立连接就不会关闭,如果服务端没有最新数据,那么线程被占用,却没有收发数据,比轮询的方式还浪费资源。
WS协议:先用HTTP的方式握手建立连接,然后用socket的长连接方式保持通话,再用HTTP的方式挥手关闭连接。结合了上面两种方式的优点,是目前的最优手段。

快速理解TCP三次握手和四次挥手

三次握手:
例如我们打电话的时候先要确认双方身份。
假设客户端是儿子,服务端是妈妈
儿子:喂,妈妈在吗?
妈妈:我在,是儿子给我打电话吗?
儿子:对,是我啊,可以聊了。
如果双方确认不了身份就挂机不聊了。

TCP协议包每种标识对应的含义
* SYN(synchronous),建立联机。
* ACK(acknowledgement),确认。
* PSH(push),传输。
* FIN(finish),结束。
* RST(reset),重置。
* URG(urgent),紧急。

3次握手的过程:
1 client 发送 SYN seq=x 请求与服务器建立连接
2 server 发送 ACK=x+1,SYN seq=y 确认请求,请求与客户端建立连接
3 client 发送 ACK=y+1 确认请求

确认请求的方式就是把syn里面的x的值加1。

注意!!TCP协议每次传输数据包前面都会先再带一个ACK确认包,告诉对方我已经收到上次的数据包了;这也是TCP和UDP最大的区别。
TCP如果没有收到确认包,就会隔一段时间再发送一次数据,来保证每次传输的数据都是完整的。而UDP中没有,所以TCP稳定。
为什么要进行3次握手,因为客户端在没有收到响应的情况下会因为连接超时从而断开连接,服务端为了不浪费资源,发现客户端超时之后就会关闭连接了。

四次挥手:
三次握手是为了建立连接,而四次挥手是为了关闭连接,如果理解三次握手,那么四次挥手就非常好理解了,原理差不多。
先看两张图

1 客户端先发送关闭请求包FIN,进入等待状态1
2 服务端收到关闭且请求后,先发送确认收到的包ACK,进入关闭等待状态。这时候客户端先收到了确认包,进入等待状态2.
3 服务端也发送一个请求关闭的包FIN,进入最后确认状态
4 客户端收到服务端的关闭请求后发送最终确认包ACK给服务端,然后进入等待计时器,在计时器结束后,进入关闭状态,服务端收到最终确认包之后立即进入关闭状态。

为何要四次分手呢?

1 当客户端发出FIN报文段时,只是表示已经没有数据要发送了。但是,这个时候主客户端还是可以接受来自服务器的数据;
2 当服务器返回ACK报文段时,表示它已经知道客户端没有数据发送了,但是还是可以往客户端推送数据包;
3 当服务器也发送了FIN报文段时,就会告诉客户端,我也没有数据要发送了;
4 再双方都确认之后彼此就会愉快的中断这次TCP连接。
如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)

FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)

CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)

CLOSED: 表示连接中断。

快速理解HTTPS协议

先知道HTTP协议是明文发送,比如抓包工具里面可以看到账号密码;
HTTPS多了一层SSL协议,用来加密信息,原理如下:

1. 服务器先发送一个安全证书,告诉客户端与这个服务器通信是安全的
2. 请求信息前,服务器会生成2把钥匙和一个空箱子。
3. 然后把空箱子和其中1把钥匙发给客户端
4. 客户端把信息放入箱子,锁上再发给服务器,这样2边都有钥匙,就都可以访问箱子里面的内容了。

再传输的过程中,就算箱子被截取了,因为没有钥匙,就很难获取箱子里面的数据。以现在的黑客技术,破解箱子也是非常有难度的

原生JS实现AJAX

快速理解AJAX

1. 创建一个XMLHttpRequest实例
2. 通过open方法,填入请求类型,地址,同步还是异步
3. 设置请求头信息,比如内容格式为json
4. 通过send方法发送请求数据
5. 通过onreadstatechange方法获取响应数据,包括状态码和data

var xhr = new XMLHttpRequest(); //ajax实例化
xhr.open(ajaxData.type, ajaxData.url, ajaxData.async); // post,xxx.com/api,sync
xhr.setRequestHeader("Content-Type", ajaxData.contentType); // json
xhr.send(convertData(ajaxData.data));
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            ajaxData.success(xhr.response)
        } else {
            ajaxData.error()
        }
    }
}

快速理解移动端REM适配方案

我写这篇文章的时间是2020年,为什么要写上时间,因为我们知道前端技术更新的很快,可能过几年这篇文章里面介绍的技术就淘汰了。
目前主流的移动端适配方案有2种:媒体查询和rem。
不过媒体查询的方式更适合做响应式布局,所以从2017至今,移动端的设配方案就是使用rem。通常VUE项目中会安装flexible和postcss-px2rem两个插件来自动计算。
我相信有的人只是会用这2个插件,但是还没有认真了解过其原理。下面我就用最简短的说明来介绍一下其中的原理,帮助您快速理解。

首先我们知道rem是相对于html根元素的单位,默认情况html的font-size是16px,所以 1rem = 16px;

那么得出来,pc端设计稿的话如果30px换成rem的公式就是 30/16=1.8rem(目标单位/根字体单位);

为了适配不同的屏幕大小,在不同大小的屏幕上面,字体也要随着改变,所以我们不能只用16px,而需要动态的设定根字体的单位;

例如现在移动端的设计稿普遍使用ihpone6作为标准,也就是宽度为375,为了计算方便,首先用js写一个函数把html的字体单位设为375/10,也就是37.5,像这样:

let htmlDom = document.getElementsByTagName('html')[0]
let setFontSize = function () {
let htmlWidth = document.documentElement.clientWidth //(viewport宽度)
htmlDom.style.fontSize = htmlWidth / 10 + 'px' //iphone6=37.5
}
setFontSize()

具备了动态改变根字体的能力,只要给文字或者容器宽高的值设定rem就可以实现适配了;

那么根据设计稿的要求我们具体应该设置多少rem呢?这里有个公式,也非常简单,就是设计稿标注的像素值除以根字体的值就得出了rem的值;

基于iPhone6的设计稿某个文字标注30px。那么它的rem值 就是30/37.5 = 0.8rem。 所以在css中的字体大小就要写.8rem。

看到这里,大家可以尝试一下在chrome的调试看看,不过要注意一点就是,iphone6的屏幕是2x屏,所以在算rem的时候需要把30px看做30/2也就是15px,然后再用15/37.5。现在业界已经默认都使用Iphone6最为移动端UI的标准了。

但是每次写rem之前都得自己用计算器除一次也太麻烦了,所以我们可以写一个scss的函数来帮我们这样就方便很多,例如像下面这样使用。

@function px2rem($px) {
$rem: 37.5px; //iphone 6
@return ($px/$rem) + rem;
}
.box {
font-size: px2rem(15px);
}

为了方便编译scss文件,可以使用webpack来打包编译或则其他方式。

最后我写了一个小极简的demo,大家可以下载到本地运行一下加深理解。

https://github.com/yjxf8285/rem-demo

解决npm 全局安装某些包时报权限相关的错误

今天在Mac系统下全局安装node-sass总是报权限方面的错误。

错误信息:gyp ERR! stack Error: EACCES: permission denied, mkdir ‘/usr/local/lib/node_modules/node-sass/.node-gyp’

字面上大概意思就是没有操作这个目录的权限,然后我尝试手动把/usr/local/lib/node_modules的目录权限改成777也不行。

最后经过网上各种搜索解决办法,找到了根本原因是:

npm 出于安全考虑不支持以 root 用户运行,即使你用 root 用户身份运行了,npm 会自动转成一个叫 nobody 的用户来运行,而这个用户几乎没有任何权限。这样的话如果你脚本里有一些需要权限的操作,比如写文件(尤其是写 /root/.node-gyp),就会崩掉了。
为了避免这种情况,要么按照 npm 的规矩来,专门建一个用于运行 npm 的高权限用户;要么加 –unsafe-perm 参数,这样就不会切换到 nobody 上,运行时是哪个用户就是哪个用户,即使是 root。

知道了问题的原因就可以正确的解决了,所以把安装命令后面加上这个–unsafe-perm 参数即可。
完整的全局安装node-sass命令为:
sudo npm i -g node-sass --unsafe-perm

js 快速排序

问题:一个值未知的数组,如何快速根据值的大小进行排序。
思路:
先找到数组的中位数pivot作为参照物
遍历数组中的每一项与其比较,然后小的放到一个Left的空数组里面,比它大的放点right的空数组里面
再递归左边的数组和右边的组数,最后把2个数组和中位数连接到一个数组里面去

let arr = [11, 82, 4, 44, 20]
var quickSort = function (arr) {
    if (arr.length <= 1) { return arr }
    var pivotIndex = Math.floor(arr.length / 2)
    var pivot = arr.splice(pivotIndex, 1)[0]
    var left = []
    var right = []
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i])
        } else {
            right.push(arr[i])
        }
    }
    return quickSort(left).concat([pivot], quickSort(right))
}
console.info(quickSort(arr))

JS事件循环机制

eventloop重点:
同步任务先执行,然后是异步任务
异步任务又分宏和微
宏任务 setTimeout 最低
微任务 promise 优先级高

console.log(1); // 同步任务

(function(){ // 同步任务
console.log(2)
})();

setTimeout(function () { // 宏任务
console.log(3)
});

new Promise(function (a, b) { // 微任务
console.log(4);
a()
}).then(function () {
console.log(6)
});

console.log(7) // 同步任务

//执行顺序为 1 2 4 7 6 3