1.【nodejs】全网首发教程 从零写一个websocket服务器 无任何框架
2.webRTC服务器搭建(基于Janus)与Demo运行
3.开源篇-WebSocket搭建UE5通信桥梁
4.了不起的 Webpack HMR 学习指南(含源码分析)
5.vscode server源码解析(三) - code server
6.探究webpack代码热更新原理
【nodejs】全网首发教程 从零写一个websocket服务器 无任何框架
nodejs从零搭建websocket服务器教程
这篇文章提供了一个无框架的全网首发教程,作者自信地表示,其行的源码已经具备了基础websocket服务器功能,且在便利性、性能和自定义性上优于主流框架。通过npm直接安装`npm i iiws`,网站地图源码下载只需注意修改package.json的main字段。源码链接:/Bylx/iiws,注释为英文。 理解websocket与/meetecho/jan...获取Janus最新代码。1.2 安装依赖:依赖库通过命令行或源码安装。遇到问题时,如libcurl4未安装,需解决。
1.2.1 命令行安装:确保所有官方推荐的系统应用源码库都已成功安装。
1.2.2 源码安装:libnice和libwebsocket是关键,老版本libnice需手动编译。
1.2.3 libsrtp:非必须,但推荐升级到最新版以避免bug。
1.2.4 usrsctp:用于Data Channel,如需消息通道则需要安装。
1.3 编译Janus:安装文档生成工具后,营销模块源码执行configure,查看配置信息。
1.4 运行Janus:复制配置文件并进行SSL配置,启用HTTPS和WSS支持。
1.4.1 生成和配置SSL证书
1.4.2 检查运行端口和警告信息
2. Demo运行
网页端Demo在源码的html目录中,使用piler?=?webpack(config);?//?这里的server是全局变量?server?=?new?Server(compiler,?options,?log);?if?(options.socket)?{ ?server.listen(options.socket,?options.host,?(err)?=>?{ })?}?else?{ ?server.listen(options.port,?options.host,?(err)?=>?{ })?}?}深入核心,了解如何通过compiler初始化服务器server对象,幻云源码并且调用listen方法
//?webpack-dev-server/lib/Server.js?class?Server?{ ?constructor(compiler,?options?=?{ },?_log)?{ ?//?保存webpack实例?this.compiler?=?compiler;?//?保存用户的配置参数?this.options?=?options;?this.heartbeatInterval?=?;?//?socketServer参数?this.socketServerImplementation?=?getSocketServerImplementation(this.options);?this.sockets?=?[];?//?设置文件监听的目录范围?this.contentBaseWatchers?=?[];?//?开启代码热更新的必备参数?this.hot?=?this.options.hot?||?this.options.hotOnly;?//?文件监听配置?this.watchOptions?=?options.watchOptions?||?{ };?this.setupHooks();?this.setupApp();?this.setupDevMiddleware();?this.createServer();?}?//?使用文件编译结束的钩子?setupHooks()?{ ?const?addHooks?=?(compiler)?=>?{ ?done.tap('webpack-dev-server',?(stats)?=>?{ ?//?服务端编译结束通过websocket告知客户端,以及传递当前的hash值和ok?this._sendStats(this.sockets,?this.getStats(stats));?this._stats?=?stats;?})?}?if?(this.compiler.compilers)?{ ?this.compiler.compilers.forEach(addHooks);?}?else?{ ?addHooks(this.compiler);?}?}?_sendStats(sockets,?stats,?force)?{ ?this.sockWrite(sockets,?'hash',?stats.hash);?this.sockWrite(sockets,?'ok');?}?//?利用express初始化一个服务器,用于静态资源的路由?setupApp()?{ ?this.app?=?new?express();?}?//?配置express搭建后的服务器,确认使用的协议?createServer()?{ ?//?如果使用的协议是piler,?Object.assign({ },?this.options,?{ ?logLevel:?this.log.options.level?})?)?}//?创建websocket服务器,用于下发模块更新的通知到客户端?createSocketServer()?{ ?const?SocketServerImplementation?=?this.socketServerImplementation;?this.socketServer?=?new?SocketServerImplementation(this);?this.socketServer.onConnection((connection,?headers)?=>?{ })?}?//?监听对应的端口开启静态资源路由,同时部署另一个websocket服务器?listen(port,?hostname,?fn)?{ ?return?this.listeningApp.listen(port,?hostname,?(err)?=>?{ ?this.createSocketServer();?}?}?}?//?添加两个打包入口模块,利用webpack将相关代码注入到bundle.js中,源码安装seurat用于客户端开启websokct以及处理热模块替换?Server.addDevServerEntrypoints?=?require('./utils/addEntries');?module.exports?=?Server;到这里webpack的HMR在node层做的处理基本完成了,这部分同样是让服务端拥有静态资源路由以及主动下发代码更新通知到客户端的能力,下面看一下如何实现客户端接收websocket通知后主动拉取更新后的服务端代码,并且替换执行新的模块代码
webpack客户端的代码肯定不会让开发人员自己去实现,不然就会出现千奇百怪的问题。这部分代码处理被黑盒处理,隐藏在了Server.addDevServerEntrypoints方法内,悄悄得在webpack带包过程中添加entry注入这部分代码处理
巧妙地划分客户端能力到两个模块中
//?webpack-dev-server/utils/addEntries.js?function?addEntries(config,?options,?server)?{ ?const?domain?=?createDomain(options,?app);?const?sockHost?=?options.sockHost`&sockHost=${ options.sockHost}`?:?'';?const?sockPath?=?options.sockPath`&sockPath=${ options.sockPath}`?:?'';?const?sockPort?=?options.sockPort`&sockPort=${ options.sockPort}`?:?'';?//?引入搭建websocket客户端代码块module?const?clientEntry?=?`${ require.resolve(?'../../client/'?)}?${ domain}${ sockHost}${ sockPath}${ sockPort}`;?//?处理客户端从服务端获取新模块并且替换执行的代码块module?let?hotEntry;?if?(options.hotOnly)?{ ?hotEntry?=?require.resolve('webpack/hot/only-dev-server');?}?else?if?(options.hot)?{ ?hotEntry?=?require.resolve('webpack/hot/dev-server');?}?}?module.exports?=?addEntries;从这里可以看出来,客户端需要的两个能力被划分到了两个代码模块中,一个是搭建websocket客户端,一个是处理客户端的代码模块更新和替换
搭建websocket客户端
//?webpack-dev-server/client/index.js?var?socket?=?require('./socket');?var?sendMessage?=?require('./utils/sendMessage');?var?createSocketUrl?=?require('./utils/createSocketUrl');?var?reloadApp?=?require('./utils/reloadApp');?var?socketUrl?=?createSocketUrl(__resourceQuery);?var?onSocketMessage?=?{ ?//?接收websocket服务端返回的最新hash值?hash:?function?hash(_hash)?{ ?status.currentHash?=?_hash;?}?ok:?function?ok()?{ ?sendMessage('Ok');?reloadApp(options,?status);?}?}?socket(socketUrl,?onSocketMessage);当客户端收到服务端返回的ok消息推送时,会调用reloadApp,这里看一下具体是怎么处理的
//?webpack-dev-server/client/utils/reloadApp.js?function?reloadApp(_ref,?_ref2)?{ ?if?(hot)?{ ?log.info('[WDS]?App?hot?update...');?var?hotEmitter?=?require('webpack/hot/emitter');?hotEmitter.emit('webpackHotUpdate',?currentHash);?//?如果当前宿主是浏览器环境,则触发webpackHotUpdate消息推送?if?(typeof?self?!==?'undefined'?&&?self.window)?{ ?self.postMessage("webpackHotUpdate".concat(currentHash),?'*');?}?}?}?module.exports?=?reloadApp;所以调用this.postMessage("webpackHotUpdate".concat(currentHash),'*')
有发送就会有接收,找一下对应的回调处理,而处理这部分的代码被划分到了hot模块中,根据hash获取新的代码模块并进行替换执行
处理客户端的代码模块更新和替换
//?webpack/hot/dev-server.js?if?(module.hot)?{ ?var?lastHash;?var?check?=?function?check()?{ ?module.hot?.check(true)?.then(function(updatedModules)?{ ?//?容错,如果不存在待更新的模块,直接刷新页面?if?(!updatedModules)?{ ?log("warning",?"[HMR]?Cannot?find?update.?Need?to?do?a?full?reload!");?log(?"warning",?"[HMR]?(Probably?because?of?restarting?the?webpack-dev-server)"?);?window.location.reload();?return;?}?}?.catch(function(err)?{ ?window.location.reload();?}?}?hotEmitter.on("webpackHotUpdate",?function(currentHash)?{ ?lastHash?=?currentHash;?if?(!upToDate()?&&?module.hot.status()?===?"idle")?{ ?log("info",?"[HMR]?Checking?for?updates?on?the?server...");?//?调用check方法拉取更新后的模块代码并进行处理?check();?}?});?}这里的module.hot.check方法,其实是另一位隐藏的大佬进行的方法注入
HotModuleReplacementPlugin
由于涉及到另一个插件的解析,放到后面去扩展。感兴趣的读者可以去webpack/lib/hotModuleReplacement.js阅读源码。
这里重点介绍针对module.hot.check都注入了怎样的代码
//?webpack/lib/web/JsonpMainTemplate.runtime.jsfunction?hotCreateModule(moduleId)?{ ?var?hot?=?{ ?check:?hotCheck?}function?hotCheck(apply)?{ ?hotSetStatus("check");?return?hotDownloadManifest(hotRequestTimeout).then(function(update)?{ ?hotAvailableFilesMap?=?update.c;?hotUpdateNewHash?=?update.h;?hotSetStatus("prepare");})?}?function?hotDownloadManifest(requestTimeout)?{ ?requestTimeout?=?requestTimeout?||?;?return?new?Promise(function(resolve,?reject)?{ ?var?request?=?new?XMLHttpRequest();?var?requestPath?=?__webpack_require__.p?+?""?+?hotCurrentHash?+?".hot-update.json";?request.open("GET",?requestPath,?true);?request.timeout?=?requestTimeout;?request.send(null);?request.onreadystatechange?=?function()?{ ?var?update?=?JSON.parse(request.responseText);?resolve(update);?}?}?}这里之所以使用JSONP的方式获取新的模块代码,是因为JSONP获取的代码可以直接执行,而hash.hot-update.js代码里有个webpackHotUpdate函数调用,最后重点看一下这个函数是如何处理代码模块替换和执行的
//?webpack/lib/HotModuleReplacement.runtime.js?window["webpackHotUpdate"]?=?function?(chunkId,?moreModules)?{ ?hotAddUpdateChunk(chunkId,?moreModules);?};?function?hotAddUpdateChunk(chunkId,?moreModules)?{ ?//?更新的模块moreModules赋值给全局全量hotUpdate?for?(var?moduleId?in?moreModules)?{ ?if?(Object.prototype.hasOwnProperty.call(moreModules,?moduleId))?{ ?hotUpdate[moduleId]?=?moreModules[moduleId];?}?}?//?调