前端通信进阶

先看一个client端, 一个比较简单的demo

var source = new EventSource(‘/dates’); //指定路由发送 source.onmessage = function(e) { //监听信息的传输 var data = JSON.parse(e.data), origin = e.origin; }; source.onerror = function(e) { //当连接发生error时触发 console.log(e); }; source.onopen = function(e) { //当连接正式建立时触发 console.log(e); };

SSE主要就是创建一个EventSource对象. 里面的参数就是发送的路由, 不过目前还不支持CORS,所以也被限制在同源策略下.

在返回的source里面包含了,需要处理的一切信息.SSE也是通过事件驱动的,如上面demo所述. 这里,SSE通常有一下几类重要的事件.

| eventName | effect |

| :-- | :-- |

| open | 当连接打开时触发 |

| message | 当有数据发送时触发, 在event对象内包含了相关数据 |

| error | 当发生错误时触发 |

上面几个方法比较重要的还是message方法. message主要用来进行信息的接受, 回调中的event 包含了返回的相关数据.

event包含的内容

| property | effect |

| :-- | :-- |

| data | 服务器端传回的数据 |

| origin | 服务器端URL的域名部分,有protocol,hostname,port |

| lastEventId | 用来指定当前数据的序号.主要用来断线重连时数据的有效性 |

服务器返回数据格式

上文说过,SSE 是以event-stream格式进行传输的. 但具体内容是怎样的呢?

data: hi

data: second event

id: 100

event: myevent

data: third event

id: 101

: this is a comment

data: fourth event

data: fourth event continue

上面就是一个简单的demo. 每一段数据我们称之为事件, 每一个事件经过空行分隔. :前面是数据类型,后面是数据. 通常的类型有:

  • 空类型: 表示注释,在处理是会默认被删除.比如: this is a comment.

  • event: 声明该事件类型,比如message.

  • data: 最重要的一个类型, 表示传输的数据。可以为string格式或者JSON格式. 比如: data: {"username": "bobby"}

  • id: 其实就是lastEventId. 用来表明该次事件在整个流中的序号

  • retry: 用来表明浏览器断开再次连接之前等待的事件(不常用)

其实上面最重要的两个字段就是data,id. 所以,我们一般获取的话就可以使用 event.dataevent.lastEventId.

上文说道, 每一段内容是通过换行实现的, 那服务器端应该怎么实现, 写入的操作呢?

同样, 这里以nodeJS 为例:

res.write("id: " + i + “\n”);

res.write("data: " + i + “\n\n”);

通过使用’\n\n’进行两次换行操作–即,产生空行即可.

使用自定义事件

服务器端不仅可以返回指定数据,还可以返回指定事件.不过默认情况下都是message事件, 但我们也可以指定事件. 比如

event: myevent

data: third event

id: 101

这里出发的就是 myevent事件。 即, 这就是触发自定义事件的方式.

在front-end 我们可以使用addEventListener 来进行监听.

var source = new EventSource(‘/someEvents’); source.addEventListener(‘myevent’, function(event){ //doSth }, false);

服务端使用SSE

由于使用的是HTTP协议,所以对于服务端基本上没什么太大的改变. 唯一注意的就是, 发送数据使用res.write()即可,断开的时候使用res.end();

res.writeHead(200, { “Content-Type”: “text/event-stream”, “Cache-Control”: “no-cache”, “Access-Control-Allow-Origin”: “*” //允许跨域 }); var num =0; var f = function(){ if(num===10){ res.end(); }else{ res.write("id: " + num + “\n”); res.write("data: " + num + “\n\n”); num++; } setTimeout(f,1000); } f();

Ok~ 这里有一个demo, 大家可以打开控制台看一下. 会发现,有一个连接一直处于Content-Download状态. 该连接就是一个SSE。

兼容性

目前SSE,在市面上大受欢迎, 不过总有一个SB, 离经叛道… 居然连edge都不支持. 偶尔去翻了一下,还在underConsideration. 结果底下的评论基本都是xxxx. 有空可以去看看, 逼逼MS程序员.

websocket


websocket 不同于其他的HTTP协议,他是独立于HTTP存在的另外一种通信协议。比如,像这样的一个路径ws://websocket.example.com/,就是一个websocket 通信. 通常的实时通信并不会传输大量的内容, 所以,对于HTTP协议那种,进行连接时需要传递,cookie和request Headers来说, 这种方式的通信协议,会造成一定的时延(latency). websocket通信协议就是在这样的背景下诞生了, 他与SSE,ajax polling不同的是–双向通信.

talk is cheap, show the code

我们来看一个简单的websocket demo

var socket = new WebSocket('ws://localhost:8080/'); socket.onopen = function () { console.log('Connected!'); }; socket.onmessage = function (event) { console.log('Received data: ' + event.data); socket.close(); }; socket.onclose = function () { console.log('Lost connection!'); }; socket.onerror = function () { console.log('Error!'); }; socket.send('hello, world!');

可以说上面就是一个健全的websocket 通信了. 和SSE一样,我们需要创建一个WebSocket对象, 里面的参数指定连接的路由. 而且,他也是事件驱动的.

常见的事件监听有.

| event | effect |

| :-- | :-- |

| open | 当ws连接建立时触发 |

| message | 当有信息到来时触发 |

| error | 当连接发生错误时触发 |

| close | 当连接断开时触发 |

websocket 发送数据

另外,websocket 最大的特点就是可以双向通信。这里可以使用.

ws.send()方法发送数据, 不过只能发送String和二进制. 这里,我们通常call 数据叫做 Frames. 他是数据发送的最小单元.包含数据的长度和数据内容.

下面就是几种常用的发送方式

socket.send(“Hello server!”);

socket.send(JSON.stringify({‘msg’: ‘payload’}));

var buffer = new ArrayBuffer(128);

socket.send(buffer);

var intview = new Uint32Array(buffer);

socket.send(intview);

var blob = new Blob([buffer]);

socket.send(blob);

另外还可以使用binaryType指定传输的数据格式,不过一般都用不上,就不说了.

不过需要提醒的是, send方法,一般在open和message的回调函数中调用.

websocket 接受数据

同理,和SSE差不多, 通过监听message事件,来接受server发送回来的数据. 接受其实就是通过event.data来获取. 不过, 需要和server端商量好data的类型.

ws.onmessage = function(msg) { if(msg.data instanceof Blob) { processBlob(msg.data); } else { processText(JSON.parse(msg.data)); //接受JSON数据 } }

那server端应该怎样处理websocket通信呢?

websocket虽然是另外一种协议,不过底层还是封装了TCP通信, 所以使用nodeJS的net模块,基本就可以满足,不过里面需要设置很多的头. 这里推荐使用ws模块.

NodeJS 发送websocket数据

简单的websocket demo

var WebSocketServer = require(‘ws’).Server , wss = new WebSocketServer({ port: 8080 }); //通过ws+ssl的方式通信. 和HTTPS类似 wss.on(‘connection’, function connection(ws) { ws.on(‘message’, function incoming(message) { console.log(‘received: %s’, message); }); ws.send(‘something’); });

可以参考treeHouse 编写的WSdemo 点击预览

为什么websocket会有子协议

由于websocket 本身的协议对于数据格式来说,不是特别的清晰明了,ws可以传输text,blob,binary等等其他格式. 这样对于安全性和开发性能来说,友好度很低。所以,为了解决这个问题, subprotocols 出现了. 在使用时,client和server都需要配置一样的subprotocols. 例如:

var ws = new WebSocket(‘wss://example.com/socket’,

[‘appProtocol’, ‘appProtocol-v2’]);

服务端需要将subprotocols发送过去, 在handshakes的过程中,server 会识别subprotocols. 如果,server端也有相同的子协议存在, 那么连接成功. 如果不存在则会触发error, 连接就被断开了.

websocket 协议内容

websocket 是有HyBi Working Group 提议并创建的。 主要的内容就是 一张表.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相比TCP来说, 真的是简单~

其实一句话就可以说完.

Figure 17-1. WebSocket frame: 2–14 bytes + payload

具体内容是:

  • 第一个比特(FIN) 表明, 该frame 是否信息的最后一个. 因为信息可以分多个frame包传送. 但最终客户端接收的是整个数据

  • opcode(4bit)–操作码, 表示传送frame的类型 比如text(1)|| binary(2)

  • Mask 比特位表示该数据是否是从 client => server.

  • Extended length 用来表示payload 的长度

  • Masking key 用来加密有效值

  • Payload 就是传输的数据

websocket 能否跨域?

首先,答案是。 但,网上有两部分内容:

WebSocket is subject to the same-origin policy

WebSocket is not subject to the same-origin policy

看到这里我也是醉了. 事实上websocket 是可以跨域的。 但是为了安全起见, 我们通常利用CORS 进行 域名保护.

即,设置如下的相应头:

Access-Control-Allow-Origin: http://example.com

这时, 只有http://example.com 能够进行跨域请求. 其他的都会deny.

那什么是CORS呢?

how does CORS work


CORS 是Cross-Origin Resource Sharing–跨域资源分享. CORS 是W3C 规范中 一项很重要的spec. 一开始,ajax 收到 the same origin policy 的限制 奈何不得。 结果出来了JSONP 等 阿猫阿狗. 这让ajax很不安呀~ 但是,W3C 大手一挥, 亲, 我给你开个buff. 结果CORS 就出来了。

CORS 就是用来帮助AJAX 进行跨域的。 而且支持性也超级好. IE8+啊,亲~ 但是IE 是使用XDomainRequest 发送的.(真丑的一逼)

所以,这里安利一下Nicholas Zakas大神写的一个函数.(我把英文改为中文了)

function createCORSRequest(method, url) { var xhr = new XMLHttpRequest(); if (“withCredentials” in xhr) { // 检查xhr是否含有withCredentials属性 //withCredentials 只存在于XHR2对象中. xhr.open(method, url, true); } else if (typeof XDomainRequest != “undefined”) { // 检查是否是IE,并且使用IE的XDomainRequest xhr = new XDomainRequest(); xhr.open(method, url); } else { // 否则…基本上就不能跨域了 xhr = null; } return xhr; }

然后, 就可以直接,xhr.send(body). 那CORS其实就完成了.

但,withCredentials是什么意思呢?

CORS中的withCredentials

该属性就是用来表明,你的request的时候,是否带上你的cookie. 默认情况下是不带的. 如果你要发送cookie给server的话, 就需要将withCredentials设置为true了.

xhr.withCredentials = true;

但是,server并不是随便就能接受并返回新的cookie给你的。 在server端,还需要设置.

Access-Control-Allow-Credentials: true

这样server才能返回新的cookie给你. 不过,这还有一个问题,就是cookie还是遵循same-origin policy的。 所以, 你无法使用document.cookie去访问他. 他的CRUD(增删查改)只能由 server控制.

CORS 的preflight 验证

CORS的preflight request, 应该算是CORS中里面 巨坑的一个。 因为在使用CORS 的时候, 有时候我命名只发送一次请求,但是,结果出来了两个。 有时候又只有一个, 这时候, 我就想问,还有谁能不懵逼.

这里,我们就需要区分一下. preflight request的作用到底是什么。

preflight request 是为了, 更好节省宽带而设计的. 因为CORS 要求的网络质量更高, 而且 花费的时间也更多. 万一, 你发送一个PUT 请求(这个不常见吧). 但是服务端又不支持, 那么你这次的 请求是失败了, 浪费资源还不说,关键用户不能忍呀~

所以, 这里我们就需要区分,什么是简单请求, 什么是比较复杂的请求

简单请求

简单请求的内容其实就两块, 一块是method 一块是Header

  • Method

  • GET

  • POST

  • Header

  • Accept

  • Accept-Language

  • Content-Language

  • Last-Event-ID //这是SSE的请求头

  • Content-Type ,但只有一下头才能算简单

  • application/x-www-form-urlencoded

  • multipart/form-data

  • text/plain

比如, 我使用上面定义好的函数createCORSRequest. 来发送一个简单请求

var url = ‘http://example.com/cors’;

var xhr = createCORSRequest(‘GET’, url);

xhr.send();

我们来看一下,只发送一次简单请求时,请求头和相应头各是什么.(剔除无关的Headers)

//Request Headers

POST HTTP/1.1

Origin: http://example.com

Host: api.bob.com

//Response Headers

Access-Control-Allow-Origin: http://example.com

Access-Control-Allow-Credentials: true

Access-Control-Expose-Headers: Vary

Content-Type: text/html; charset=utf-8

上面就是一个简单的CORS 头的交互。 另外,说明一个Access-Control-Allow-Origin,该头是必不可少的.

本来在XHR中, 一般可以通过xhr.getResponseHeader()来获取相关的相应头。 但是 在CORS中一般可以获得如下几个简单的Header:

  • Cache-Control

  • Content-Language

  • Content-Type

  • Expires

  • ETag

  • Last-Modified

  • Pragma

如果你想暴露更多的头给用户的话就可以使用,Access-Control-Expose-Headers 来进行设置. 多个值用’,'分隔.

那发送两次请求是什么情况呢?

我们如果请求的数据是application/json的话,就会发送两次请求.

var url = ‘http://example.com/cors’;

var xhr = createCORSRequest(‘POST’, url);

xhr.setRequestHeader(‘Content-Type’,‘application/json’);

xhr.send();

第一次,我们通常叫做preflight req. 他其实并没有发送任何 data过去. 只是将本次需要发送的请求头发送过去, 用来验证该次CORS请求是否有效.

上面的请求头就有:

OPTIONS HTTP/1.1

Origin: http://example.com
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

前端面试题汇总


前端面试题是我面试过程中遇到的面试题,每一次面试后我都会复盘总结。我做了一个整理,并且在技术博客找到了专业的解答,大家可以参考下:

由于篇幅有限,只能分享部分面试题,完整版面试题及答案可以【点击我】阅读下载哦~

感悟

!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

前端面试题汇总


前端面试题是我面试过程中遇到的面试题,每一次面试后我都会复盘总结。我做了一个整理,并且在技术博客找到了专业的解答,大家可以参考下:

由于篇幅有限,只能分享部分面试题,完整版面试题及答案可以【点击我】阅读下载哦~

感悟

春招面试的后期,运气和实力都很重要,自己也是运气比较好,为了回馈粉丝朋友们(毕竟自己也玩了这么久哈哈哈),整理个人感悟和总结以上。最后祝愿大家能够收获理想offer!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值