1.Prometheus-Remote Read Meets Streamingãç¿»è¯ã
2.面试官:说一下大文件分片下载
3.websocket+netty实时视频弹幕交互功能(Java版)
4.transfer-encodingåcontent-lengthçä¸åå®ç°
Prometheus-Remote Read Meets Streamingãç¿»è¯ã
Prometheusæ°çæ¬ 2..0 çåå¸ï¼ä»å å«äºè®¸å¤bug fixåæ¹è¿ãä½ å¯ä»¥å¨è¿å¿ æ¥çåæ´ ãè¿éæä¸ä¸ªè®¸å¤ç¨æ·æ项ç®ååæå¾ çåè½ï¼ chunked, streamed version of remote read API .å¨æ¬æä¸ï¼æå°æ·±å ¥ä»ç»æ们å¨è¿ç¨åè®®ä¸æ´æ¹äºåªäºå 容ï¼æ´æ¹çåå 以åå¦ä½ææå°ä½¿ç¨å®ã
èªä»1.xçæ¬ï¼Prometheusæäºä¸è¿ç¨åå¨éè¿ remote API 交äºçè½åã
è¿ä¸ªAPIå 许第ä¸æ¹ç³»ç»éè¿ä¸¤ç§æ¹æ³ä¸metrics æ°æ®äº¤äº:
è¿æ¯å°Prometheusæ°æ®æ¾äºç¬¬ä¸æ¹ç³»ç»åå¨çæ常è§åæ³ãå¨è¿ç§æ¨¡å¼ä¸ï¼Prometheuså¨ææ§å°åç»å®ç«¯ç¹åéä¸æ¹æ ·æ¬æ°æ®ã
å¨3æ份åºäº WAL çRemote writeå¨å¯é æ§åèµæºå ç¨çä¸é½å¾å°äºå·¨å¤§çæåãå¼å¾ä¸æçæ¯ è¿é ææç第ä¸æ¹åå¨é½æ¯æ该ç§æ¹å¼ã
readæ¹æ³ä¸å¤ªå¸¸è§ãå®æ¯å¨ March
æ·»å ç(æå¡ç«¯å é¨å å«)ï¼èªé£ä»¥å没æçå°æ¾èçåå±ã
æ®ç½ç±³ä¿®æ¯2..0çæ¬ä¿®å¤äºRead APIä¸å·²ç¥çèµæºç¶é¢ãæ¬æå°éç¹è®¨è®ºè¿äºæ¹è¿ã
remote readçå ³é®ç¹æ¯ä¸ç»è¿PromQL计ç®çåæä¸ç´æ¥æ¥è¯¢Prometheus storage ( TSDB )ã ä»å Querier æ¥å£ç±»ä¼¼ï¼ç¨äºä»åå¨å±è·åæ°æ®ã
è¿ä¸ªç¹æ§å 许对Prometheusééå°çæ¶åºæ°æ®è¿è¡è®¿é®ï¼ remote read主è¦ä½¿ç¨åºæ¯å¦ä¸ï¼
è¿ç¨è¯»APIæ´é²äºä¸ä¸ªç®åçHTTP endpoint ï¼å®æææ°æ®æ ¼å¼å¦ä¸:
éè¿è¿ä¸ªåè®®ï¼å®¢æ·ç«¯å¯ä»¥éè¿ matchers å start 以å end çæ¶é´åºé´æ¥è·åå°ç¹å®çæ¶åºæ°æ®
è¿åæ°æ®æ ¼å¼å¦ä¸ï¼
Remote readè¿åå¹é çæ¶åºæ°æ®ï¼å å«rawæ°æ®ï¼å å«vauleåæ¶é´æ³ï¼
对äºremote readæå¦ä¸2ä¸ªå ³é®é®é¢ãå®è½ç¶å¾å®¹æ使ç¨åç解ï¼ä½æ¯æ们å®ä¹çprotobufæ ¼å¼çå个HTTP请æ±ä¸æ²¡æstreamingåè½ãå ¶æ¬¡ï¼ååºçæ¯rawæ ¼å¼æ°æ®(floatå¼åintæ¶é´æ³)ï¼èä¸æ¯TSDBåå¨çåºäºâChunkâçç»è¿ç¼ç åå缩åçæ°æ®ã
没æstreamingçè¿ç¨è¯»åçserver端é»è¾æ¯:
ä¸é¢æ¯PrometheusåThanos Sidecar(remote read 客æ·ç«¯)å¨remote read请æ±æé´å çå å使ç¨æ åµ:
å¼å¾æ³¨æçæ¯ï¼å³ä¾¿å¯¹äºPrometheusåçHttpæ¥å£ç query_range æ¹æ³èè¨ï¼ æ¥è¯¢,个series ä¹å¹¶ä¸æ¯ä¸ä¸ªå¥½ä¸»æï¼å ä¸ºä½ çæµè§å¨ä¹ä¸å¸æè·åï¼åå¨ç¶å渲ææ°ç¾å åèçæ°æ®ãæ¤å¤ï¼å¯¹äºä»ªè¡¨æ¿ååç°ç®çæ¥è¯´ï¼æ¥æè¿ä¹å¤æ°æ®ä¹æ¯ä¸ç°å®çï¼å 为人ä¸å¯è½è¯»åè¿ä¹å¤§éçæ°æ®ãè¿å°±æ¯ä¸ºä»ä¹æ们é常æ们ä¸ä¼å起大äºä¸ªæ¶åºçæ¥è¯¢è¯·æ±ã
è½ç¶è¿æ ·åå¾å¥½ï¼ä½æ¯å¦å¤ä¸ç§å¸¸è§ææ¯åæ³æ¯éè¿èåæ¥è¯¢å»è·å¾èå好ç个æ¶é´åºåï¼ä½åºå±æ¥è¯¢å¼æå¿ é¡»éè¿ä¸å个åºåå»è®¡ç®ååºï¼ä¾å¦ä½¿ç¨ aggregators ãè¿å°±æ¯ä¸ºä»ä¹åThanosè¿æ ·çç³»ç»ï¼å¨å ¶ä»æ°æ®ä¸ï¼ä½¿ç¨TSDBæ°æ®ä»è¿ç¨è¯»åï¼é常æ åµä¸ï¼è¯·æ±å¾éã
ç解Prometheuså¨æ¥è¯¢æ¶æ¯å¦ä½è¿ä»£æ°æ®ç对ç解è¿ä¸ªé®é¢å¾æ帮å©ãæ ¸å¿æ¦å¿µå¯ä»¥ä» Querier ç Select æ¹æ³è¿åçä¸ä¸ªå« SeriesSet çç±»åä¸çåºæ¥ãæ¥å£å¦ä¸ï¼
ä¸é¢è¿ä¸ç³»åæ¥å£ä¸ºè¿ç¨æä¾äºä¸ç§åºäºâæµâçè½åãæ们ä¸åéè¦é¢å 计ç®å å«æ ·æ¬çåºåå表ã使ç¨è¿ä¸ªæ¥å£ï¼æ¯ä¸ª SeriesSet.Next() å®ç°é½å¯ä»¥æ ¹æ®éè¦è·ååºåãæ们è¿å¯ä»¥éè¿ SeriesIterator.Next å¨æå°åå«è·åæ¯ä¸ªæ ·æ¬ã
éè¿è¿ä¸ªåè®®ï¼Prometheuså¯ä»¥å°½å¯è½å°çåé å åï¼å 为PromQLå¼æå¯ä»¥æ´ä¼çå¯¹æ ·æ¬è¿è¡è¿ä»£ï¼ä»¥è®¡ç®åºæ¥è¯¢ç»æãTSDB以åæ ·çæ¹å¼å®ç°SeriesSetï¼ä»¥ä¸ç§æä½³çæ¹å¼ä»åå¨å¨æ件系ç»ä¸çåä¸ä¸ä¸ªæ¥ä¸ä¸ªå°è·ååºåï¼ä»èæå°ååé ã
è¿å¯¹ remote read APIæ¥è¯´ååéè¦ï¼å 为æ们å¯ä»¥ä»¥ç¸åçè°ç¨å½¢å¼æ¥è¿ä»£å¼çå客æ·ç«¯åéå个æ¶åºä¸çä¸é¨åchunkå½¢å¼çæ°æ®ãå 为protobufåç没æåå²æ¶æ¯æ°æ®çæºå¶ï¼æ以æ们 æ©å±äº protoå®ä¹æ¥å 许åéä¸ç»å°çprotocol bufferæ¶æ¯ï¼èä¸æ¯å个巨大çæ¶æ¯ä½ãæ们æè¿ç§remote read模å¼ç§°ä½ STREAMED_XOR_CHUNKS èèç模å¼å« SAMPLES ãæ©å±äºprotocolæå³çPrometheusåä¹ä¸éè¦ç¼å²æ´ä¸ªååºäºï¼ä»å¯ä»¥å¨è°ç¨ SeriesSet.Next æè SeriesIterator.Next è¿ä»£æ¶åéä¸ä¸ªæåºçç¬ç«å¸§ï¼ä»¥å°½å¯è½çä¸ä¸ä¸ä¸ªæ¶åºæ°æ®å¤ç¨åä¸ä¸ªå å页ã
ç°å¨ï¼ STREAMED_XOR_CHUNKS 模å¼çååºæ¯ä»¥ä¸ä¸ç»Protobufæ¶æ¯ï¼å¸§ï¼
ä½ å¯ä»¥åç°æ¶æ¯å¸§ä¸å å«rawæ ¼å¼æ°æ®äºãè¿æ¯æ们åç第äºç¹æåï¼æ们以chunkçå½¢å¼åéæ¶æ¯æ ·æ¬ï¼è§ç è¿ä¸ªè§é¢ æ¥äºè§£æ´å¤å ³äºchunkçç¥è¯ï¼å®ä¸æ们åå¨å¨TSDBä¸çchunkå®å ¨ç¸åã
æ们æç»ä½¿ç¨äºä»¥ä¸æå¡ç«¯é»è¾:
æ们ææç设计é½å¨ è¿é
ä¸æ§ç解å³æ¹æ¡ç¸æ¯ï¼è¿ç§æ°æ¹æ³çæ§è½å¦ä½?
æ们æ¥å°Prometheus 2..0 å 2..0 remote readç¹åè¿è¡å¯¹æ¯ãå°±å¦æ¬æå¼å¤´ç»åºçåæ¥ç»æï¼æç¨Prometheusåæå¡ç«¯ï¼ç¨Thanosçsidecaråè¿ç¨è¯»åç客æ·ç«¯ãæä½¿ç¨ grpcurl 对Thanos sidecaræ§è¡gRPCè°ç¨æ¥æµè¯remote readãæ´ä¸ªæµè¯å¨æçç¬è®°æ¬ï¼(Lenovo X1 GB, i7 8thï¼ä¸dockerä¸çk8sç¯å¢éè¿è¡ãï¼ä½¿ç¨ kind )
æ°æ®æ¯äººå·¥çæçï¼è¡¨ç¤ºé«åº¦å¨æç,个åºå(æåçæ åµ)ã
æµè¯ç详ç»ç»æè¯·è§ thanosbench repo
åå°å åæ¯æ们解å³æ¹æ¡ç主è¦ç®æ ãå¨æ´ä¸ªè¯·æ±è¿ç¨ä¸ï¼Prometheusçç¼å²åºå¤§çº¦ä¸ºMBï¼èThanosåªæå°éçå å使ç¨ãå¤äºäºæµå¼Thanos gRPC StoreAPI, sidecarç°å¨æ¯ä¸ä¸ªé常ç®åç代çã
æ¤å¤ï¼æå°è¯äºä¸åçæ¶é´èå´ååºåæ°éï¼ä½æ£å¦é¢æçé£æ ·ï¼æä¸ç´çå°Prometheusçåé æå¤ä¸ºMBï¼èThanosçåé å没æãè¿è¯ææ è®ºä½ å个请æ±å¤å°ä¸ªæ ·æ¬ï¼æ们çremote readå§ç»ä½¿ç¨æå®çå å大å°ãåé å åçå¤å°å请æ±æ°æ®æ°éçå½±å大大åå°ï¼æ è®ºä½ è¯·æ±å¤å°æ°æ®ï¼ä»é½å§ç»åé ç¸åçå åã
å¨å¹¶åæ§éå¶ç帮å©ä¸ï¼è¿ä½¿å¾é对ç¨æ·æµéè¿è¡å®¹éè§ååå¾æ´å 容æã
å¨æçæµè¯æé´ï¼CPU使ç¨çä¹æææé«ï¼ä½¿ç¨çCPUæ¶é´åå°äºä¸¤åã
éè¿streamingåæ´å°çç¼ç 次æ°ï¼æ们åæ¶è¿å®ç°äºåå°remote read请æ±çååºå»¶è¿ã
Remote read 8å°æ¶æ¶é´è·¨åº¦å å«ä¸ªåºåï¼
2hæ¶é´è·¨åº¦ï¼
å¨PrometheusåThanos侧å¤çååºååçæ¶é´ä¸ï¼é¤äºä½2.5åçååºå»¶è¿å¤ï¼ streamçæ¬çååºæ¯åæ¶çï¼èé streamçæ¬å®¢æ·ç«¯å»¶è¿sï¼ real minus user timeï¼ã
Remote read以åååååå ¼å®¹çæ¹å¼è¿è¡äºæ©å±ãè¿è¦å½åäºprotobufå accepted_response_types 被æ§çæ¬æ忽ç¥çå段ï¼åæ¶å¦ææ§ç客æ·ç«¯ï¼å设éç¨ SAMPLES 模å¼çremote readï¼ä¸æ¯æ accepted_response_types ä»ä¹è½æ£å¸¸å·¥ä½ã
remote readåè®®ååçæ¬å ¼å®¹æ¹å¼ï¼
为äºä½¿ç¨æ°çåºäºstreamed remote readçPrometheus v2..0ï¼ç¬¬ä¸æ¹ç³»ç»å¿ é¡»å° accepted_response_types = [STREAMED_XOR_CHUNKS] æ·»å å°è¯·æ±ä¸ã
Prometheuså°±ä¼ç¨ ChunkedReadResponse æ¥æ¿ä»£èçæ¬æ¶æ¯ä½ãæ¯ä¸ª ChunkedReadResponse æ¶æ¯é½ç¬¦åvarint大å°ååºå®å¤§å°bigendian uintç¨äºCRC Castagnoliæ ¡éªåã
对äºgoè¯é³æ¥è¯´æ们æ¨èä½¿ç¨ ChunkedReader æ¥ç´æ¥è¯»åæµ
注æï¼ storage.remote.read-sample-limit 设置å°å¨ STREAMED_XOR_CHUNKS. storage.remote.read-concurrent-limit æ¡ä»¶ä¸ä¸åèµ·ä½ç¨ã
ä¹æä¾äºä¸ä¸ªæ°çé 置项 storage.remote.read-max-bytes-in-frame æ¥æ§å¶æ¯ä¸ªæ¶æ¯ä½çæ大大å°ã建议é»è®¤ä¸º1MBï¼å 为谷æ建议protobufæ¶æ¯ ä¸å¤§äº1MB .ã
æ£å¦åé¢æå°çï¼ Thanos å 为è¿ä¸ªç¹æ§æ¶çé¢ä¸°ãStreamed remote read å¨ v0.7.0 被å¢å ï¼å æ¤ï¼é åPrometheus 2..0 ææ´é«çæ¬ï¼é½å°èªå¨éç¨ streamed remote read çå½¢å¼ã
Release 2..0å¼å ¥äºæ©å±ç remote readåPrometheusæå¡å¨ç«¯å®ç°ï¼ç¶èï¼ä¸ºäºå åå©ç¨æ©å±çè¿ç¨è¯»åè®®çä¼å¿ï¼ç®åè¿éè¦åä¸äºäºæ :
综ä¸æè¿°ï¼ååãæµè¿ç¨è¯»åç主è¦å¥½å¤æ¯:
å¦ææ¨æä»»ä½é®é¢æåé¦ï¼ä¸å¦æ¢å¾ï¼è¯·éæ¶å¨GitHubä¸æ交é®é¢æå¨é®ä»¶å表ä¸æé®ã
面试官:说一下大文件分片下载
文件上传、下载是常见需求,大文件上传通过分片上传优化,如阿里云 OSS 的大文件分片上传。那么,大文件下载如何优化呢?答案同样是modbus源码分片下载,或称为流式传输。
我们通过 Nest 项目探索实现。首先,创建 Nest 项目并在 AppController 中添加下载路由。运行服务,浏览器访问下载路由,触发下载,Devtools 显示正确设置的 header。header 也可通过 @Header 装饰器添加,效果相同。
然而,数字市场源码直接返回文件全部内容会占用大量内存,对于大文件下载,需要实现流式下载,即读取一部分返回一部分。HTTP 协议提供此功能,通过设置 transfer-encoding:chunked。这种方式下,文件以块形式分段传输,直至传输结束。
流式传输原理基于 transfer-encoding:chunked,它让服务器不断返回内容直至返回一个空块表示结束。例如,文件内容可以分块为“Hello”、“,”、“World”、“!”,官在民间源码每块长度分别为 5、1、5、1,最后以长度为 0 的空块代表传输结束。
在代码中实现流式传输,使用 Node.js 的 stream API 读取内容,配合 Nest 封装的 StreamableFile 类实现流式返回。不再返回 Content-Length,而是返回 Transfer-Encoding:chunked,实现流式传输。
Nest 项目中不要直接使用 Node.js 的 stream API,因为其事件处理较为复杂。StreamableFile 类简化了操作,自动处理流式传输的流程。使用此类时,官网搭建源码Content-Type 默认为 application/octet-stream,可修改为其他格式。
浏览器和 HTTP 协议内置了流式下载支持,只需设置对应 header 即可,无需额外实现。下载响应体结构通过 wireshark 抓包验证,每个分块包含 chunk size 和 chunk data。Nest 小册仓库上传了相关案例代码。
总结,大文件下载优化通过分片下载实现,HTTP 协议支持流式传输,只需设置 transfer-encoding:chunked。Nest 项目中使用 StreamableFile 类简化了流式传输操作。通过 wireshark 抓包可验证响应体结构。更多内容可参考 Nest 通关秘籍小册。
websocket+netty实时视频弹幕交互功能(Java版)
年了,二开视频源码还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,ppt都上弹幕玩法了,不整个弹幕都说不过去了,今天笔者就抽空做了一个实时视频弹幕交互功能的实现,不得不说这样的形式为看视频看直播,讲义PPT,抽奖等形式增加了许多乐趣。一.技术选型1.1netty官方对于netty的描述:netty.io/
主要关键词描述:netty是异步事件驱动网络框架,可做各种协议服务端,并且支持了FTP,SMTP,HTTP等很多协议,并且性能,稳定性,灵活性都很棒。
可以看到netty整体架构上分了三个部分:
以零拷贝,一致性接口,扩展事件模型的底层核心。
Socket,Datagram,Pipe,HttpTunnel作为传输媒介。
传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,GoogleProtobuf等各种各种的传输形式。
1.2WebSocketWebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于年被IETF定为标准RFC,并由RFC补充规范。WebSocketAPI也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocketAPI中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
1.3为什么做这样的技术选型。由上述可知,实时直播交互作为互动式是一个双向数据传输过程。所以使用webSocket。netty本身支持了webSocket协议的实现,让实现更加简单方便。
二.实现思路2.1服务架构整体架构是所有客户端都和我的服务端开启一个双向通道的架构。
如下图:
2.2传输流程如下图:
三.实现效果先看看效果吧,是不是perfect,接下来就来看具体代码是怎么实现的吧。
视频直播弹幕示例四.代码实现4.1项目结构一个maven项目,将代码放一个包下就行。
4.2Java服务端Java服务端代码,总共三个类,Server,Initailizer和Handler。
4.2.1先做一个nettynio的服务端:一个nio的服务,开启一个tcp端口。
importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.EventLoopGroup;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.nio.NioServerSocketChannel;/***Copyright(c)lbhbinhao@.com*@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicenumBulletChatServer{ /***Serverinstance*/SERVER;privateBulletChatServer(){ EventLoopGroupmainGroup=newNioEventLoopGroup();EventLoopGroupsubGroup=newNioEventLoopGroup();ServerBootstrapserver=newServerBootstrap();server.group(mainGroup,subGroup).channel(NioServerSocketChannel.class).childHandler(newBulletChatInitializer());ChannelFuturefuture=server.bind();}publicstaticvoidmain(String[]args){ }}4.2.2服务端的具体处理逻辑
importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelPipeline;importio.netty.channel.socket.SocketChannel;importio.netty.handler.codec.**@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatInitializerextendsChannelInitializer<SocketChannel>{ @OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ ChannelPipelinepipeline=ch.pipeline();pipeline.addLast(newHttpServerCodec());pipeline.addLast(newChunkedWriteHandler());pipeline.addLast(newHttpObjectAggregator(*));pipeline.addLast(newIdleStateHandler(8,,));pipeline.addLast(newWebSocketServerProtocolHandler("/lbh"));pipeline.addLast(newBulletChatHandler());}}后台处理逻辑,接受到消息,写出到所有的客户端:
importio.netty.channel.Channel;importio.netty.channel.ChannelHandler;importio.netty.channel.ChannelHandlerContext;importio.netty.channel.SimpleChannelInboundHandler;importio.netty.channel.group.ChannelGroup;importio.netty.channel.group.DefaultChannelGroup;importio.netty.handler.codec.**@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatHandlerextendsSimpleChannelInboundHandler<TextWebSocketFrame>{ //用于记录和管理所有客户端的channelpublicstaticChannelGroupchannels=newDefaultChannelGroup(GlobalEventExecutor.INSTANCE);@OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,TextWebSocketFramemsg)throwsException{ //获取客户端传输过来的消息Stringcontent=msg.text();System.err.println("收到消息:"+content);channels.writeAndFlush(newTextWebSocketFrame(content));System.err.println("写出消息完成:"+content);}@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx)throwsException{ channels.add(ctx.channel());}@OverridepublicvoidhandlerRemoved(ChannelHandlerContextctx)throwsException{ StringchannelId=ctx.channel().id().asShortText();System.out.println("客户端被移除,channelId为:"+channelId);channels.remove(ctx.channel());}@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause)throwsException{ cause.printStackTrace();//发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除ctx.channel().close();channels.remove(ctx.channel());}}4.3网页客户端实现
<!DOCTYPEhtml><html><head><metacharset="utf-8"><meta/libs/jquery/2.1.4/jquery.min.js"type="text/javascript"></script><script>//1.获取元素varoBox=document.querySelector('.box');//获取.box元素varcW=oBox.offsetWidth;//获取box的宽度varcH=oBox.offsetHeight;//获取box的高度functioncreateEle(txt){ //动态生成span标签varoMessage=document.createElement('span');//创建标签oMessage.innerHTML=txt;//接收参数txt并且生成替换内容oMessage.style.left=cW+'px';//初始化生成位置xoBox.appendChild(oMessage);//把标签塞到oBox里面roll.call(oMessage,{ //call改变函数内部this的指向timing:['linear','ease-out'][~~(Math.random()*2)],color:'#'+(~~(Math.random()*(1<<))).toString(),top:random(0,cH),fontSize:random(,)});}functionroll(opt){ //弹幕滚动//如果对象中不存在timing初始化opt.timing=opt.timing||'linear';opt.color=opt.color||'#fff';opt.top=opt.top||0;opt.fontSize=opt.fontSize||;this._left=parseInt(this.offsetLeft);//获取当前left的值this.style.color=opt.color;//初始化颜色this.style.top=opt.top+'px';this.style.fontSize=opt.fontSize+'px';this.timer=setInterval(function(){ if(this._left<=){ clearInterval(this.timer);//终止定时器this.parentNode.removeChild(this);return;//终止函数}switch(opt.timing){ case'linear'://如果匀速this._left+=-2;break;case'ease-out'://this._left+=(0-this._left)*.;break;}this.style.left=this._left+'px';}.bind(this),/);}functionrandom(start,end){ //随机数封装returnstart+~~(Math.random()*(end-start));}varaLi=document.querySelectorAll('li');//functionforEach(ele,cb){ for(vari=0,len=aLi.length;i<len;i++){ cb&&cb(ele[i],i);}}forEach(aLi,function(ele,i){ ele.style.left=i*+'px';});//产生闭包varobj={ num:1,add:function(){ this.num++;//obj.num=2;(function(){ console.log(this.num);})}};obj.add();//window</script></body></html>这样一个实时的视频弹幕功能就完成啦,是不是很简单,各位小伙伴快来试试吧。
五.小结上班撸代码,下班继续撸代码写博客,这个还是很简单,笔者写这个的时候一会儿就写完了,不过这也得益于笔者很久以前就写过netty的服务,对于Http,Tcp之类协议也比较熟悉,只有前端会有些难度,问下度娘,也很快能做完,在此分享出来与诸君分享。
来源:binhao.blog.csdn.net/article/details/
transfer-encodingåcontent-lengthçä¸åå®ç°
å段æ¶é´å¨é¡¹ç®ä¸çå°å¦ä¸ç代ç ï¼
1
2
3
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader("Transfer-Encoding", "utf8");
filterChain.doFilter(servletRequest, servletResponse);
åææ¯æ³å¯¹è¾åºçå 容è¿è¡ç¼ç ï¼å´ç¨éäºååºå¤´ï¼ç»æè¿ä¸ªé误çååºå¤´å¯¹åé¢ç客æ·ç«¯ç¨åºå¸¦æ¥äºè®¸å¤éº»ç¦ãè¿éæå¿ è¦å¯¹è¿ä¸ªè¿åçå 容è¿è¡è¯¦ç»å°äºè§£ã
ä¼ è¾æ°æ®ç¼ç ï¼Transfer-Encoding
æ°æ®ç¼ç ï¼å³è¡¨ç¤ºæ°æ®å¨ç½ç»ä¼ è¾å½ä¸ï¼ä½¿ç¨æä¹æ ·çä¿è¯æ¹å¼æ¥ä¿è¯æ°æ®æ¯å®å ¨æåå°ä¼ è¾å¤çãå¯ä»¥æ¯åæ®µä¼ è¾ï¼ä¹å¯ä»¥æ¯ä¸å段ï¼ç´æ¥ä½¿ç¨åæ°æ®è¿è¡ä¼ è¾ã
ææçå¼ä¸ºï¼TrunkedåIdentity.
ä¼ è¾å 容ç¼ç ï¼Content-Encoding
å 容ç¼ç ï¼å³æ´ä¸ªæ°æ®ä¿¡æ¯æ¯å¨æ°æ®å¨ç«¯ç»è¿ææ ·çç¼ç å¤çï¼ç¶å客æ·ç«¯ä¼ä»¥æä¹çç¼ç æ¥ååå¤çï¼ä»¥å¾å°åå§çå 容ãè¿éçå 容ç¼ç 主è¦æ¯æå缩ç¼ç ï¼å³æå¡å¨ç«¯å缩ï¼å®¢æ·ç«¯è§£å缩ã
å¯ä»¥åèçå¼ä¸ºï¼gzip,compress,deflateåidentityã
ä¼ è¾å å®¹æ ¼å¼ï¼Content-Type
å å®¹æ ¼å¼ï¼å³æ¥æ¶çæ°æ®æç»æ¯ä»¥ä½ç§çå½¢å¼æ¾ç¤ºå¨æµè§å¨ä¸ãå¯ä»¥æ¯ä¸ä¸ªå¾çï¼è¿æ¯ä¸æ®µææ¬ï¼æè æ¯ä¸æ®µhtmlãå å®¹æ ¼å¼é¢å¤æ¯æå¯éåæ°,charsetï¼å³å®é å 容çå符éãéè¿å符éï¼å®¢æ·ç«¯å¯ä»¥å¯¹æ°æ®è¿è¡è§£ç¼ç ï¼ä»¥æç»æ¾ç¤ºå¯ä»¥çå¾æçæåï¼èä¸æ¯ä¸æ®µbyte[]æè æ¯ä¹±ç )ã
3ç§æè¿°ä¿¡æ¯ï¼å¯ä»¥ç±ä¸å¾æ¥è¡¨ç¤º(æ¥æºäºãHttpæå¨æåã)ï¼
ä»ä¸æä¸ï¼å¯ä»¥çåºï¼å®é ä¸åfilterä¸çå 容å¯è½æ¯æ³è¡¨è¾¾ä»¥ä¸çææ:
1
2
3
response.setContentType("text/html;charset=UTF8");
//æè
response.setContentType("application/json;charset=UTF8");
å 容é¿åº¦ï¼Content-Length
å 容é¿åº¦ï¼å³è¡¨ç¤ºæ´ä¸ªä¼ è¾å 容çææé¿åº¦ä¿¡æ¯ã客æ·ç«¯å¯ä»¥éè¿æ¤å¤´ä¿¡æ¯æ¥å¤ææ¥æ¶çæ°æ®æ¯å¦å·²ç»å®å ¨æ¥æ¶ãæ¤ç¼ç åtransfer-encodingç¸å²çªï¼å 为transfer-encodingä¼éè¿é¢å¤çå¤çæ¹å¼æ¥æ¹åæ°æ®çç»ç»æ¹å¼ï¼å°±ä¼æ¹åå®é çæ°æ®é¿åº¦ï¼å¦æ客æ·ç«¯ä»æç §åcontent-lengthæ¥å¤ççè¯ï¼åä¸ä¼æ¥æ¶å°å®æ´çæ°æ®ã
ç±äºtransfer-encodingåcontent-lengthä¹é´åå¨å²çªé®é¢ï¼å æ¤å¨æå¡ç«¯å客æ·ç«¯å°±ä¼æç¸åºçå®ç°æ¥æ¯æç¸åºçæ°æ®å¤çãæ´ä¸ªå¤çè¿ç¨æç §RFC æ¥å¤çã
å¤çè§åï¼(ion:closeæ¶ï¼å³è¡¨ç¤ºå®¢æ·ç«¯åªä¼è¯·æ±ä¸æ¬¡ï¼ä¸ä¼ä½¿ç¨Keep-Aliveï¼è¿æ ·çè¯ï¼ä¸éè¦ä½¿ç¨trunkedä¼ è¾ï¼
//å 为客æ·ç«¯ç¥éä½æ¶æ°æ®å·²ç»ä¼ è¾å®ï¼ä½¿ç¨read() == -1å³å¯å¤æ
if (entityBody && http && !connectionClosePresent) {
//使ç¨ChunkedOutputFilteræ¥å¯¹ä¼ è¾çæ°æ®äºæ¬¡å¤çï¼å³åæ®µä¼ è¾
getOutputBuffer().addActiveFilter
(outputFilters[Constants.CHUNKED_FILTER]);
contentDelimitation = true;
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
} else {
//æå使ç¨åå§æ°æ®ä¼ è¾æ¹å¼
getOutputBuffer().addActiveFilter
(outputFilters[Constants.IDENTITY_FILTER]);
}
}
æ¤æ®µä»£ç å°å¨åºç¨å¤çå®é»è¾æè è°ç¨response.outputStream.writeæ¶ä¼è°ç¨ã详ç»çå¤çé»è¾ï¼å¯åèä¸æä¸ç注éã
éè¦æ³¨æçæ¯ï¼ç±äºHttpå·¥ä½å¨TCP/IPä¹ä¸ï¼å æ¤æ°æ®çå®æ´æ§ä¿è¯å·²ç»ä¸éè¦ç±Httpæ¥å¤çäºãæ以ä¾é trunkedæ¥ä¿è¯æ°æ®å®æ´æ§å·²ç»æ²¡æ太大æä¹ãç°å¨trunkedçæä¹å¨äºé对keep aliveä¼ è¾æ¶ï¼trunkedå¯ä»¥éè¿ç¹æ®çå¤çæ¥åè¯å®¢æ·ç«¯(éè¿åé头é¿åº¦0æ¥æ è¯)ï¼è¯¥æ¬¡çæ°æ®å·²ç»ååºå®æ¯ã客æ·ç«¯å¯ä»¥å¤ç并å次使ç¨è¯¥è¿æ¥è¿è¡ä¸ä¸æ¬¡å¤çäºãæ以å¨ä¸é¢çtrunkedå¤çä¸ï¼tomcatå¦æ认为没æ使ç¨trunkedçå¿ è¦æ¶ï¼å°±ä¸ä¼å¼ºå¶ä½¿ç¨trunkedäº(å¦connection:closeè¿ç§ä¸æ¬¡æ§è¯·æ±æ¨¡å)
客æ·ç«¯å®ç°HttpClient
httpclient(çæ¬4.3.3ï¼ç主è¦å®ç°å¨ç±»EntityDeserializerä¸ï¼å³å¦ä½å»è·åæ°æ®å¹¶åå解ç ãå®ç°æ¹æ³ä¸ºdoDeserializeï¼ä¸»è¦çå®ç°å¦ä¸æ示ï¼
final long len = this.lenStrategy.determineLength(message);
if (len == ContentLengthStrategy.CHUNKED) {
entity.setChunked(true);
entity.setContentLength(-1);
entity.setContent(new ChunkedInputStream(inbuffer));
} else if (len == ContentLengthStrategy.IDENTITY) {
entity.setChunked(false);
entity.setContentLength(-1);
entity.setContent(new IdentityInputStream(inbuffer));
} else {
entity.setChunked(false);
entity.setContentLength(len);
entity.setContent(new ContentLengthInputStream(inbuffer, len));
}
å³éè¿å¤ælenghå¼æ¥ç¡®å®æ¯ä½¿ç¨ä¸åçæ°æ®è§£æã解æåºæ¥çæµå¤çå ±æ3ç§ä¸åçå¤çæ¹å¼ï¼å³transfer-encodingä¸æå®çchunkedåidentityï¼ä»¥åç±content-lengthæå®çå¤çæ¹å¼ã
对lengthçå¤æé»è¾å¦ä¸æ示:
final Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING);
// We use Transfer-Encoding if present and ignore Content-Length.
// RFC, 4.4 item number 3
//é¦å æ ¹æ®RFC 4.4ä¸ä»ç»çï¼é¦å å¤çtransfer-encoding
if (transferEncodingHeader != null) {
final HeaderElement[] encodings;
encodings = transferEncodingHeader.getElements();
// The chunked encoding must be the last one applied RFC, .
final int len = encodings.length;
//åªå¤ææ¯å¦åtrunkedåidentityç¸çï¼å¨é½ä¸ç¸ççæ åµä¸é»è®¤ä½¿ç¨identityï¼ä»¥é¿å 解æåºéçæ åµ
if (HTTP.IDENTITY_CODING.equalsIgnoreCase(transferEncodingHeader.getValue())) {
return IDENTITY;
} else if ((len > 0) && (HTTP.CHUNK_CODING.equalsIgnoreCase(
encodings[len - 1].getName()))) {
return CHUNKED;
} else {
return IDENTITY;
}
}
//ç¶åå使ç¨content-lengthï¼è¿éåæ ·å¤æï¼åªæå¨ç¡®å®å¥½conten-lengthçæ åµä¸æ使ç¨ï¼å¦æç¡®å®ä¸å¥½ï¼ä»ç¶ä½¿ç¨identity
final Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN);
if (contentLengthHeader != null) {
long contentlen = -1;
final Header[] headers = message.getHeaders(HTTP.CONTENT_LEN);
contentlen = Long.parseLong(header.getValue());
}
if (contentlen >= 0) {
return contentlen;
} else {
return IDENTITY;
}
}
å¨ä»¥ä¸çå¤çå½ä¸ï¼æ们çå°identityå¤çæ¹å¼æå¤çãå¯ä»¥ç解为ï¼åªè¦æ¯ä¸è½è§£æçé½ä½¿ç¨identityï¼å³åå§å¤çæ¹å¼ã
éè¿ä»¥ä¸çå®ç°ï¼å¯ä»¥äºè§£å®¢æ·ç«¯å¨æ¥æ¶å®æ°æ®ä¹åçä¸åååºæ¹å¼åå¤çé»è¾ï¼è¿ä¹è½è§£éå¨é¡¹ç®ä¸çå¥æªæ åµäºã
é®é¢åºç°å解å³
å¨æ们ç项ç®ä¸ï¼ç±äºä¸é¢çé误çfilterçé®é¢ãæ们å¨ä¹å使ç¨httpclientå¨æ¥æ¶æ°æ®æ¶(使ç¨httpPost)ï¼æ¬æ¥æ³æ¥æ¶ä¸ä¸ªjsonæ°æ®ä¸²ï¼å³æ»æ¯è¿å类似以ä¸çæ°æ®:
1
2
3
{ abxxx.......}
0
è¿ç§æ åµåªå¨å¤çæ们请æ±çæå¡æä¼åºç°ï¼è¯·æ±å ¶å®ä¹åç项ç®æå¡ä¸ä¼åºç°ãå¤æ¬¡åç°è¿ä¸ªç¹æ®çå¼ï¼å¥½å表示æ°æ®é¿åº¦ãå¨ä¸ç¥éåå çæ åµä¸ï¼æ们éè¿å¨æå¡ä¸å å ¥ä»¥ä¸ä»£ç ä¹åï¼é®é¢å¥½å就解å³äºï¼
1
2
3
byte[] bytes = generatedBytes();//çæjson bytesæ°ç»ä¿¡æ¯
response.setContentLength(bytes.length);//强å¶æ§è®¾ç½®contentLengthå¼
response.getoutputStream.write(bytes);
ä½åæä¸ä¸ªé®é¢åçäºï¼åç°ååºå¾æ ¢ãæ¯æ¬¡è¯·æ±é½è¦è±è´¹æ¥è¿sçæ¶é´ï¼ä½çæ§æå¡ä»£ç ï¼ååºå¾å¿«çãèä¸å¨æå¡è¿åä¹åï¼å®¢æ·ç«¯éè¦ç»§ç»çå¾ ä¸æ®µæ¶é´æè¿åæ°æ®ãç»debug代ç ï¼æç»åç°httpclientå¨ä½¿ç¨EntityUtils.toStringä¸æ¯è¿æ ·åç:
1
2
3
4
5
6
7
8
final Reader reader = new InputStreamReader(instream, charset);
final CharArrayBuffer buffer = new CharArrayBuffer(i);
final char[] tmp = new char[];
int l;
while((l = reader.read(tmp)) != -1) {
buffer.append(tmp, 0, l);
}
return buffer.toString();
è¿éçwhile循ç¯å¨è¿æ¥æªå ³éçæ åµä¸ä¼ä¸ç´çå¾ ãç»åå°keepAliveå±æ§ï¼è¿éè¯å®é»è®¤ä¼ä½¿ç¨keepAliveè¿è¡è¯·æ±ï¼èåæå¡å¨ä¹è¯å®æ²¡æå ³éè¿æ¥ãå æ¤ï¼æ们åå¨ä½¿ç¨httpClientæ¶ï¼å¼ºå¶å å ¥ä»¥ä¸å¤´ï¼
1
httpPost.addHeader("Connection","Close");
è¿æ ·å£°æ客æ·ç«¯åªä¼è¯·æ±ä¸æ¬¡ï¼å°±æå¼è¿æ¥ãå å ¥ä¹åï¼å¥½åé®é¢åä¸æ¬¡è§£å³ã
ä½è¿ç§è§£å³æ¹å¼æä¹æè§ä¹ä¸æ¯æ£è§ç解å³æ¹æ³ï¼å 为åªæ¯ç¢°å·§å¤çäºè¿ä¸ªé®é¢ãæç»ç解å³æè·¯ï¼è¿æ¯å¨è®¤çäºè§£äºHttpåè®®ä¹åï¼å¹¶æ¥çç¸åºçæå¡ä»£ç ä¹åï¼å é¤é误çè¾åºå¤´ï¼ç´æ¥å°±è§£å³äºæ¤é®é¢ãè³äºå¢å content-length头ï¼è¿ä¸ªå¨å é¤headerä¹åï¼å¯ä»¥ç´æ¥å é¤äº(å 为å¢å äºé¢å¤ç代ç )ã