关于socket.io:SocketIO-解决-WebSocket-通信

大家好呀,我是小菜~ 本文次要介绍 Socket.IO 微信公众号已开启,小菜良记,没关注的同学们记得关注哦! 在介绍 Socket.IO 之前, 咱们先思考一个问题, 如果这个时候有个需要, 相似实现人工客服的性能该如何实现? 在线客服,需要了解起来很简略,就相当于一个 web 的聊天页面,也就是客户端可能 即时拉取到服务端的响应当然, 作为接口工程师, 这并不是一个很难解决的问题, 咱们能够提供一个获取聊天记录的接口, 通过该接口咱们能够获取到 对方曾经发送到音讯. 那么问题又来了, 如何保障可能 即时 的获取到聊天记录呢? 想必这也不是问题, 前端能够通过定时器 的形式, 将间隔时间缩短到 100 毫秒, 这样子就曾经实现了近实时的获取音讯 setInterval(function () { // do something},100)当咱们写完以上代码上线后, 却通过监控能够发现, 上线后的服务器指标显著比之前有所晋升 服务器是非常宝贵的资源, 那么为什么会产生这种状况呢? 回过头一想, 会产生这种状况也无可非议, 每 100 毫秒就申请一 次后端, 如果有聊天记录产生, 那么这种申请就认为是有意义的, 但如果长时间未聊天, 每次申请返回都是空记录, 那么这种 频繁申请就是无意义的. 频繁申请会使服务器压力增大, 并且节约带宽流量. 那么有没有别的形式能够解决? 咱们兴许能够应用 SSE 形式, SSE 并不是一个什么比拟新鲜的概念, 它呈现的工夫也很早 SSE 全称 Server-Sent Events,指的是网页主动获取来自服务器的更新,也就是自动化获取服务端推送至网页的数据,这是一个 H5 的属性,除了 IE,其余规范浏览器根本都兼容这种形式不须要客户端定时去获取,而是服务端向客户端申明要发送流信息,而后连续不断地发送过去 只管这种形式不须要定时轮询, 然而它只能单工通信,建设连贯后,只能由服务端发往客户端,且须要占用一个连贯,如果须要客户端向服务端通信,那么须要额定再关上一个连贯! ...

March 13, 2022 · 3 min · jiezi

使用socketio实现多房间通信聊天室

websocket的实现有很多种,像ws和socket.io,这里使用的是socket.io来实现多房间的效果。 这里的使用没有使用socket.io官方提供的namespace和room,而是完全通过一个namespace实现的。数据传输使用JSON格式,封装了消息规范 消息体规范const actionType = { join:'JOIN',//加入 leave:'LEAVE',//离开 talk:'TALK',//消息 action:'ACTION',//用户操作 push:'PUSH'//系统推送}//消息体class MSG { constructor(type,body){ this.type = type; this.body= body; }}安装使用npm install socket.io-rooms --savedemo演示把项目从github上clone下来后,执行npm start,然后打开example/index.html即可品尝到演示效果 使用方式服务端Serverconst {User,Rooms} = require('socket.io-rooms')const server = require('http').createServer();const io = require('socket.io')(server);//大厅io.on('connection', client => { let user = new User(); client.emit('user',user); client.on('join', data => { /\* 加入某个房间 \*/ Rooms.join(data,user,io,client) }); client.on('message',msg=>{ if(user.roomId){ // io.to(user.roomId).emit('message',msg) if(msg.type == 'update'){ user.update(msg.body); } msg.user = user.uid; Rooms.send(user.roomId,msg) }else{ io.emit('message',msg) } console.log(msg) }) client.on('disconnect', () => { /\* … \*/ console.log("连接断开") Rooms.leave(user) });});server.listen(80);这里传输统一使用`JSON`格式,消息`title`也以`message`为主,这里端口写的80,你可以使用其他端口,如果你是Express,也可以共用80端口。 ...

October 15, 2019 · 1 min · jiezi

socketio

写了一个socket.io服务,实现了用户区分、公聊、私聊等。。。码云地址https://gitee.com/liuoomei/so... app.js //app.jsvar express = require('express')var app = express();var server = require('http').Server(app);// var io = require('socket.io')(server);var path = require('path');app.use(express.static(path.join(__dirname, 'public')))app.get('/', function(req, res){ res.sendFile(path.join(__dirname, 'index.html'));});module.exports = app;bin/www #!/usr/bin/env node/** * Module dependencies. */let app = require('../app');let debug = require('debug')('mysocket:server');let http = require('http');let _ = require('underscore');/** * Get port from environment and store in Express. */let userLs = []let port = normalizePort(process.env.PORT || '3000');app.set('port', port);/** * Create HTTP server. */let server = http.createServer(app);/** * Listen on provided port, on all network interfaces. */server.listen(port);let io = require('socket.io')(server);io.on('connection', function(socket){ socket.on('login', function(userid){ let obj = { userid, id: socket.id } userLs.push(obj) console.log(userid + '建立了链接'); socket.emit('userLs',userLs) socket.emit('login',socket.id) }); // 判断用户离线事件可以通过socket.io自带的disconnect事件完成,当一个用户断开连接,disconnect事件就会触发 socket.on('disconnect', function(){ let _user = _.where(userLs,{id:socket.id}) console.log('_user',_user) console.log(socket.id + '中断了链接'); userLs = userLs.filter(it =>{ return _user.every(item =>{ return it.id != item.id }) }) // do somethings console.log('del',userLs) }); socket.on('message', function (data) { //服务端像所以也没发送数据 let _user = _.where(userLs,{id:socket.id}) io.sockets.emit('message', {id:_user[0].id,userid:_user[0].userid,message:data.message}); //给所有人(包括自己)发送消息 // socket.broadcast.emit('message', data.message); //给所有人(不包括自己)发送消息 }); socket.on('sayTo', function (data) { let toMsg = data.message; let toId = data.id; // nodejs的underscore扩展中的findWhere方法,可以在对象集合中,通过对象的属性值找到该对象并返回。 let _user = _.where(userLs,{id:toId}) if(_user){ let toSocket = _.findWhere(io.sockets.sockets, {id: toId}); // 通过该连接对象(toSocket)与链接到这个对象的客户端进行单独通信 socket.emit('message', {id:socket.id,message:toMsg}) //向建立该连接的客户端广播 toSocket.emit('message', {id:socket.id,message:toMsg}); }else{ socket.emit('error','该用户不在线') } // socket.emit() :向建立该连接的客户端广播 // socket.broadcast.emit() :向除去建立该连接的客户端的所有客户端广播 // io.sockets.emit() : 向所有客户端广播,等同于上面两个的和 });});server.on('error', onError);server.on('listening', onListening);/** * Normalize a port into a number, string, or false. */function normalizePort(val) { let port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false;}/** * Event listener for HTTP server "error" event. */function onError(error) { if (error.syscall !== 'listen') { throw error; } let bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; }}/** * Event listener for HTTP server "listening" event. */function onListening() { let addr = server.address(); let bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); console.log(`服务已启动,端口:${addr.port}`)}public/index.html ...

June 17, 2019 · 3 min · jiezi

百度地图实时动态轨迹

百度地图实时动态轨迹后端直接使用node版的socket.io推送轨迹点const server = require('http').createServer()const socketIo = require('socket.io')(server)const CreatePoint = require('./CreatePoint.js')// 所有已连接的客户端socketIo.on('connection', (client) => { // console.log(client) // 断开时删除 client.on('disconnect', () => { // 。。。 }) // 当客户端触发setPoint事件之后,socket就向客户端推送新的坐标 client.on('setPoint', (point) => { console.log(point) // 1. 创建一个创造坐标的实例 let CreateCustomPoint = new CreatePoint(point) // 每隔3秒向客户端推送一次坐标 setInterval(() => { client.emit('newPoint', CreateCustomPoint.randomAction()) }, 3000) })})server.listen(3000, '192.168.1.202', () => { console.log('this server is listening on port 3000')})前端就一个发送事件和监听事件先需要连接到socket import VueSocketIo from 'vue-socket.io'Vue.use(new VueSocketIo({ debug: true, // 这里的地址就是后端地址 connection: '192.168.1.202:3000', vuex: { store, actionPrefix: 'SOCKET_', mutationPrefix: 'SOCKET_' } // options: { path: "/my-app/" }}))sockets: { // socket服务器连接时触发 connect: () => { console.log('已成功连接到socket服务器') }, // 接收socket服务器推送的newPoint事件 newPoint(point) { console.log(`接收到socket服务器的新坐标${point}`) // console.log(this) this.points.push(point) // 关闭原来的标志物 this.mkrTool.close() this.map.clearOverlays() // this.map.centerAndZoom(point, 15) // 在新坐标添加覆盖物 this.freshOverlay(point) } },效果 ...

June 12, 2019 · 1 min · jiezi

songEagle开发系列如何科学有效的让博客文章实时保存

一、写在前面如何实现文章的实时保存?一般写文章的写博客的网站都会有这个功能点,这样保证了用户在不小心退出的情况下数据的保存下来,这样的交互比较符合用户的使用心理学。对于用户来说这是一个非常实用的功能,作为一个博客来说,有这个还是不错的。哈哈^_^ 二、实现的思路一个功能的实现,你的编程思路很重要,决定着你在coding之前的设计,思路就是你的编程设计。 使用webSocket来进行浏览器与服务器的实时通信服务端使用redis来缓存实时的编辑的文章,因为编辑的时候文章会出现频繁的改动,频繁的读写数据库算不上一个科学合理的解决方案服务端使用定时任务,目前设置的是每天的凌晨三点,将redis缓存的数据存储到mysql数据库中浏览器初次进入到新增文章的页面的时候,使用websocket从服务端获取数据,首先从redis中查找有没有数据,没有数据再去mysql数据库中查找浏览器初次进入到编辑页面,不需要从服务端获取数据,这样避免请求接口时间上浪费使用vue的watch方式来监听写文章页面的变化,变化时使用websocket向服务端保存到redis中文章保存的时候,清空redis和mysql的实时保存的数据三、主要代码const SocketIO = require('socket.io');const config = require('../config/environment');const DraftRedis = require('./draft-redis');const redisMysql = require('./redis-mysql');const draftPostRedisKey = config.draftPostRedisKey;exports.initSocket = function (server) { console.log('init websocket'); //socket.io let socketHandle = SocketIO(server, { serveClient: true, path: config.socketioPath }); let draftRedis = new DraftRedis(config.db.redis); socketHandle.on('connection', function (socket) { console.log('socket connected'); // 离开编辑文章页面 socket.on('disconnect', function () { console.info('[%s] DISCONNECTED', socket.sid); }); // 进入新增文章页面,获取已保存的草稿(可以为空) socket.on('getDraftPost', async function () { let data = await draftRedis.get(draftPostRedisKey); if (!data) { data = await redisMysql.getDraftPostFromMysql(); socket.emit('getDraftPost', data); await draftRedis.set(draftPostRedisKey, data); } else { socket.emit('getDraftPost', data); } }); // 实时保存文章内容 socket.on('saveDraftPost', async function (data) { let res = await draftRedis.set(draftPostRedisKey, data); socket.emit('saveDraftPost', res); }); // 保存后清空已保存的文章草稿 socket.on('clearDraftPost', async function () { await draftRedis.destroy(draftPostRedisKey); await redisMysql.clearDraftPostOfMysql(); socket.emit('clearDraftPost', true); }); });};四、方法说明目前个人博客的后台使用的是,只有一个账户,没有添加多账户体系,所以redis的mysql中只存有一条记录。后续有空会慢慢加上多账户体系涉及到的模块及说明1、/server/util/draft-socketio.js服务端websocket服务,使用socket.io库2、/server/util/draf-redis.jsredis的set方法和get公共方法3、/server/util/redis-mysql.js ...

May 23, 2019 · 1 min · jiezi

Laravel整合PHPSocketIo实现web消息推送

PHPSocket.IO,PHP跨平台实时通讯框架PHPSocket.IO是PHP版本的Socket.IO服务端实现,基于workerman开发,用于替换node.js版本Socket.IO服务端。PHPSocket.IO底层采用websocket协议通讯,如果客户端不支持websocket协议, 则会自动采用http长轮询的方式通讯。环境Ubuntu 18Laravel 5.8PHPSocket.IO 1.1安装依赖composer require workerman/phpsocket.iocomposer require guzzlehttp/guzzle启动程序整合到artisan命令中创建文件命令php artisan make:command MsgPush app/Console/Commands/MsgPush.php <?phpnamespace App\Console\Commands;use Illuminate\Console\Command;use Workerman\Worker;use Workerman\Lib\Timer;use PHPSocketIO\SocketIO;class MsgPush extends Command{ protected $signature = 'msg-push {action=start : start | restart | reload(平滑重启) | stop | status | connetions} {--d : deamon or debug}'; protected $description = 'web消息推送服务'; // 全局数组保存uid在线数据 private static $uidConnectionCounter = []; // 广播的在线用户数,一个uid代表一个用户 private static $onlineCount = 0; // 广播的在线页面数,同一个uid可能开启多个页面 private static $onlinePageCount = 0; //PHPSocketIO服务 private static $senderIo = null; public function __construct() { parent::__construct(); } /** * 根据脚本参数开启PHPSocketIO服务 * PHPSocketIO服务的端口是`2120` * 传递数据的端口是`2121` */ public function handle() { global $argv; //启动php脚本所需的命令行参数 $argv[0] = 'MsgPush'; $argv[1] = $this->argument('action'); // start | restart | reload(平滑重启) | stop | status | connetions $argv[2] = $this->option('d') ? '-d' : ''; // 守护进程模式或调试模式启动 // PHPSocketIO服务 self::$senderIo = new SocketIO(2120); // 客户端发起连接事件时,设置连接socket的各种事件回调 self::$senderIo->on('connection', function ($socket) { // 当客户端发来登录事件时触发,$uid目前由页面传值决定,当然也可以根据业务需要由服务端来决定 $socket->on('login', function ($uid) use ($socket) { // 已经登录过了 if (isset($socket->uid)) return; // 更新对应uid的在线数据 $uid = (string)$uid; // 这个uid有self::$uidConnectionCounter[$uid]个socket连接 self::$uidConnectionCounter[$uid] = isset(self::$uidConnectionCounter[$uid]) ? self::$uidConnectionCounter[$uid] + 1 : 1; // 将这个连接加入到uid分组,方便针对uid推送数据 $socket->join($uid); $socket->uid = $uid; // 更新这个socket对应页面的在线数据 self::emitOnlineCount(); }); // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致) $socket->on('disconnect', function () use ($socket) { if (!isset($socket->uid)) { return; } // 将uid的在线socket数减一 if (--self::$uidConnectionCounter[$socket->uid] <= 0) { unset(self::$uidConnectionCounter[$socket->uid]); } }); }); // 当self::$senderIo启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据 self::$senderIo->on('workerStart', function () { // 监听一个http端口 $innerHttpWorker = new Worker('http://0.0.0.0:2121'); // 当http客户端发来数据时触发 $innerHttpWorker->onMessage = function ($httpConnection, $data) { $type = $_REQUEST['type'] ?? ''; $content = htmlspecialchars($_REQUEST['content'] ?? ''); $to = (string)($_REQUEST['to'] ?? ''); // 推送数据的url格式 type=publish&to=uid&content=xxxx switch ($type) { case 'publish': // 有指定uid则向uid所在socket组发送数据 if ($to) { self::$senderIo->to($to)->emit('new_msg', $content); } else { // 否则向所有uid推送数据 self::$senderIo->emit('new_msg', $content); } // http接口返回,如果用户离线socket返回fail if ($to && !isset(self::$uidConnectionCounter[$to])) { return $httpConnection->send('offline'); } else { return $httpConnection->send('ok'); } } return $httpConnection->send('fail'); }; // 执行监听 $innerHttpWorker->listen(); // 一个定时器,定时向所有uid推送当前uid在线数及在线页面数 Timer::add(1, [self::class, 'emitOnlineCount']); });// Worker::$daemonize = true; Worker::runAll(); } /** * 将在线数变化推送给所有登录端 * 须是public方法,可供其它类调用 */ public static function emitOnlineCount() { $newOnlineCount = count(self::$uidConnectionCounter); $newOnlinePageCount = array_sum(self::$uidConnectionCounter); // 只有在客户端在线数变化了才广播,减少不必要的客户端通讯 if ($newOnlineCount != self::$onlineCount || $newOnlinePageCount != self::$onlinePageCount) {// var_dump('emitOnlineCount: ', self::$uidConnectionCounter); //将在线数变化推送给所有登录端 self::$senderIo->emit( 'update_online_count', [ 'onlineCount' => $newOnlineCount, 'onlinePageCount' => $newOnlinePageCount ] ); self::$onlineCount = $newOnlineCount; self::$onlinePageCount = $newOnlinePageCount; } }}启动PHPSocket.Io服务#守护进程模式启动php artisan msg-push start -d#调式模式启动php artisan msg-push startweb页面resources/views/socketio.blade.php ...

May 1, 2019 · 3 min · jiezi

java实现socketio客户端功能

基于java做一个socket.io客户端前言最近公司这边让我去订阅一个第三方机构的websocket server,也是头疼,免不了和对方各种沟通,大家都很忙,收到回复很慢,开发方向也不知道。先是做了一个普通websocket的客户端,但后面了解到对方是基于socket.io做的一个server,又重新做了一个基于socket.io-client-java开源库的客户端。涉及到公司商业机密,所以做了一个demo,转自https://blog.csdn.net/q56231293811/article/details/84873776<!--more--> 1. Client(Socket.IO Client Library for Java)先上代码 package com.dasnnj.practice.share.socket;import io.socket.client.IO;import io.socket.client.Socket;import java.util.Arrays;/** * Description <P> TODO : socket.io client端 <P> * * @author DASNNJ <a href="mailto:dasnnj@outlook.com"> dasnnj@outlook.com </a> * @date 2019-04-27 18:32 */public class Client { public static void main(String[] args) { String url = "http://localhost:9999"; try { IO.Options options = new IO.Options(); options.transports = new String[]{"websocket"}; //失败重试次数 options.reconnectionAttempts = 10; //失败重连的时间间隔 options.reconnectionDelay = 1000; //连接超时时间(ms) options.timeout = 500; final Socket socket = IO.socket(url, options); //监听自定义msg事件 socket.on("msg", objects -> System.out.println("client: 收到msg->" + Arrays.toString(objects))); //监听自定义订阅事件 socket.on("sub", objects -> System.out.println("client: " + "订阅成功,收到反馈->" + Arrays.toString(objects))); socket.on(Socket.EVENT_CONNECT, objects -> { socket.emit("sub", "我是訂閲對象"); System.out.println("client: " + "连接成功"); }); socket.on(Socket.EVENT_CONNECTING, objects -> System.out.println("client: " + "连接中")); socket.on(Socket.EVENT_CONNECT_TIMEOUT, objects -> System.out.println("client: " + "连接超时")); socket.on(Socket.EVENT_CONNECT_ERROR, objects -> System.out.println("client: " + "连接失败")); socket.connect(); } catch (Exception ex) { ex.printStackTrace(); } }} 流程:启动client会创建scoket,并将uri,options等参数set进去监听一些事件(可自定义),也就是将event为key,回调为value,put 进callbacks(其为ConcurrentMap) ...

April 28, 2019 · 2 min · jiezi

使用koa和socket.io简单搭建多人聊天流程

koa与socket.io简单流程分析:1. 服务端触发初始化io.on(‘connection’, socket => {});2. 客户端发送say会话socket.emit(‘say’, ‘我是客户端’); 3. 服务端监听say会话socket.on(‘say’,data => {}); 4. 服务端发送message会话socket.emit(‘message’, {hello: ‘你是谁’});5. 客户端接收message消息socket.on(‘message’, (data) => {});服务端:const koa = require(‘koa’);const app = new koa();const server = require(‘http’).Server(app.callback())const io = require(‘socket.io’)(server);const port = 8081;server.listen(process.env.PORT || port, () => { console.log(app run at : http://127.0.0.1:${port});})io.on(‘connection’, socket => { console.log(‘socket初始化完成’); socket.on(‘say’, data => { console.log(data, ‘接收到的信息’) socket.emit(‘message’, {hello: ‘你是谁’}) })})客户端<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Document</title> <style> input { background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #bfcbd9; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 40px; line-height: 1; outline: 0; padding: 3px 10px; } .el-button–primary { color: #fff; background-color: #20a0ff; border-color: #20a0ff; } .el-button { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #00aac5; border: 1px solid #c4c4c4; color: #fff; margin: 0; padding: 10px 15px; border-radius: 4px; outline: 0; text-align: center; } </style></head><body> <div> <div id=“content”> </div> </div> <div> <input type=“text” id=“input”> <button class=“el-button el-button–primary el-button–large” type=“button” onclick=“say()"><span>发送消息</span></button> </div> <script src=”./socket.io.js"></script> <script> // 建立连接 var socket = io.connect(‘http://localhost:8081’); // 监听 message 会话 socket.on(‘message’, function (data) { let html = document.createElement(‘p’) html.innerHTML = 系统消息:&lt;span&gt;${data.hello}&lt;/span&gt; document.getElementById(‘content’).appendChild(html) console.log(data); }); // 按钮点击事件 function say() { let t = document.getElementById(‘input’).value if (!t) return let html = document.createElement(‘p’) html.innerHTML = 你细声说:&lt;span&gt;${t}&lt;/span&gt; document.getElementById(‘content’).appendChild(html) socket.emit(‘say’, { my: t}); } // 监听 news 会话 socket.on(’news’, function (data) { console.log(data); let html = document.createElement(‘p’) html.innerHTML = 小皮咖说:&lt;span&gt;我知道了,你说“${data.hello}”&lt;/span&gt; document.getElementById(‘content’).appendChild(html) }); // 监听吃饭会话 socket.on(’eating’, function (data) { console.log(data); let html = document.createElement(‘p’) html.innerHTML = 小皮咖说:${data.hello} document.getElementById(‘content’).appendChild(html) }); </script></body></html> ...

April 5, 2019 · 2 min · jiezi

WebRTC 入门教程(一)| 搭建WebRTC信令服务器

作者:李超,音视频技术专家。本入门教程将分为三篇内容,分别讲述信令服务器的搭建、媒体服务器的搭建、Android 端的 WebRTC 应用实现,全文采用开源框架来搭建,适用于大多数入门的开发者。转载请注明出处。如遇到 WebRTC 开发问题,可以点击这里,关注作者与他交流。前言我们在学习 WebRTC 时,首先要把实验环境搭建好,这样我们就可以在上面做各种实验了。对于 WebRTC 来说,它有一整套规范,如怎样使用它的接口、使用SDP进行媒体协商、通过ICE收集地址并进行连通性检测等等。除此之外,WebRTC还需要房间服务器将多端聚集到一起管理,以及信令服务器进行信令数据交换(如媒体描述信息SDP的交换,连接地址的交抽换等),但在WebRTC的规范中没有对这部分内容进行规定,所以需要由用户自己处理。你可以根据自己的喜好选择服务器(如 Apache,Nginx 或 Nodejs),我今天将介绍如何使用 Nodejs 来搭建信令服务器。为什么选择 NodejsApache、Nginx和Nodejs都是非常成熟的Web服务器,Nginx 可以说是的性能是最好的Web服务器了。但从未来的发展来说,Nodejs可能会更有优势。现在以Chrome为代表的浏览器的功能越来越强大,以前认为通过浏览器不可能完成的事儿,现在它都可以轻松实现。H5、 WebSocket的出现以及现在WebRTC的加入,让大家越来越觉得以后的浏览器可以说是“无所不能”。因此,推动 JavaScript 语言的发展越来越迅速。这可以从现在 JavaScript 技术的火爆,以及各种层叠不穷JS FrameWork的出现得以印证。而 Nodejs 的最大优点即是可以使用 JS 语言开发服务器程序。这样使得大量的前端同学可以无缝的转到服务器开发,甚至有可能前后端使用同一套代码实现。对于这一点我想无论是对个人还是对于企业都是具大的诱惑。一方面 JS 语言的简单性可以方便开发出各种各样功能的服务端程序。更可贵的是 Nodejs 的生态链非常的完整,有各种各样的功能库。你可以根据自己的需要通过安装工具 NPM 快速的安装,这也使它也得到了广大开发者的喜欢。Nodejs 现在是非常流行的 Web 服务器,它在服务器端使用 V8(JavaScript)引擎,通过它解析 JS 脚本来控制服务器的行为。这对于广大的 JS 同学来说真是太幸福了,在10年前还很难想像可以通过 JS 脚本语言来写服务器程序。当然,如果你想对Nodejs作能力拓展的话,还是要写C/C++库,然后加载到 Nodejs 中去。Nodejs的基本原理Nodejs的工作原理如上图所示, 其核心是 V8 引擎。通过该引擎,可以让 js 调用 C/C++方法 或 对象。相反,通过它也可能让 C/C++ 访问 javascript 方法和变量。Nodejs 首先将 JavaScript 写好的应用程序交给 V8 引擎进行解析,V8理解应用程序的语义后,再调用 Nodejs 底层的 C/C++ API将服务启动起来。 所以 Nodejs 的强大就在于 js 可以直接调用 C/C++ 的方法,使其能力可以无限扩展。以开发一个 HTTP 服务为例,Nodejs 打开侦听的服务端口后,底层会调用 libuv 处理该端口的所有 http 请求。其网络事件处理如下图所示: 当有网络请求过来时,首先会被插入到一个事件处理队列中。libuv会监控该事件队列,当发现有事件时,先对请求做判断,如果是简单的请求,就直接返回响应了;如果是复杂请求,则从线程池中取一个线程进行异步处理;线程处理完后,有两种可能:一种是已经处理完成,则向用户发送响应;另一种情况是还需要进一步处理,则再生成一个事件插入到事件队列中等待处理;事件处理就这样循环往复下去,永不停歇。两个 V8 引擎如上图所示,在我们使用 Nodejs之后实际存在了两个 V8 引擎。一个V8用于解析服务端的 JS 应用程序,它将服务启动起来。另一个 V8 是浏览器中的 V8 引擎,用于控制浏览器的行为。对于使用 Nodejs 的新手来说,很容易出现思维混乱,因为在服务端至少要放两个 JS 脚本。其中一个是服务端程序,控制 Nodejs 的行为,它由 Nodejs 的V8引擎解析处理;另一个是客户端程序,它是要由浏览器请求后,下发到浏览器,由浏览器中的 V8 引擎进行解析处理。如果分不清这个,那麻烦就大了。安装 Nodejs下面我们就来看看具体如何安装 Nodejs。安装 Nodejs 非常的简单: 在Ubuntu系统下执行:apt install nodejs或在Mac 系统下执行:brew install nodejs通过上面的步骤我们就将 Nodejs 安装好了。我这里安装的 Nodejs版本为:v8.10.0。安装NPM除了安装 Nodejs 之外,我们还要安装NPM(Node Package Manager),也就是 Nodejs 的包管理器。它就像Ubuntu下的 apt 或Mac 系统下的brew 命令类似,是专门用来管理各种依赖库的。在它们没有出现之前,我们要安装个包特别麻烦。以Linux为例,假设要安装一个工具,其基本步骤是:先将这个工具的源码下载下来。执行./configure 生成Makefile 文件。执行 make 命令对其进行编译。最后,执行 make install 将其安装到指定目录下。如果编译过程中发现有依赖的库,则要对依赖库执行前面的4步,也就是先将依赖库安装好,然后再来安装该工具。大家可以看到,以前在Linux下安装个程序或工具是多么的麻烦。Linux 有了apt 之后,一切都变得简单了。我们只要执行 apt install xxx 一条命令就好了,它会帮你完成上面的一堆操作。对于 Nodejs的安装包也是如此,NPM 就是相当于 Linux 下的 apt,它的出现大大提高了人们的工作效率。NPM 的安装像安装 Nodejs 一样简单:在Ubuntu下执行:apt install npm或在Mac下执行:brew install npmsocket.io此次,我们使用 Nodejs 下的 socket.io 库来实现 WebRTC 信令服务器。socket.io特别适合用来开发WebRTC的信令服务器,通过它来构建信令服务器特别的简单,这主要是因为它内置了房间 的概念。上图是 socket.io 与 Nodejs配合使用的逻辑关系图, 其逻辑非常简单。socket.io 分为服务端和客户端两部分。服务端由 Nodejs加载后侦听某个服务端口,客户端要想与服务端相连,首先要加载 socket.io 的客户端库,然后调用 io.connect();就与服务端连上了。需要特别强调的是 socket.io 消息的发送与接收。socket.io 有很多种发送消息的方式,其中最常见的有下面几种,是我们必须要撑握的:给本次连接发消息socket.emit()给某个房间内所有人发消息io.in(room).emit()除本连接外,给某个房间内所有人发消息socket.to(room).emit()除本连接外,给所以人发消息socket.broadcast.emit()消息又该如何接收呢?发送 command 命令S: socket.emit(‘cmd’);C: socket.on(‘cmd’,function(){…});送了一个 command 命令,带 data 数据S: socket.emit(‘action’, data);C: socket.on(‘action’,function(data){…});发送了command命令,还有两个数据S: socket.emit(action,arg1,arg2);C: socket.on(‘action’,function(arg1,arg2){…});有了以上这些知识,我们就可以实现信令数据通讯了。搭建信令服务器接下来我们来看一下,如何通过 Nodejs下的 socket.io 来构建的一个服务器:这是客户端代码,也就是在浏览器里执行的代码。index.html:<!DOCTYPE html><html> <head> <title>WebRTC client</title> </head> <body> <script src=’/socket.io/socket.io.js’></script> <script src=‘js/client.js’></script> </body></html>该代码十分简单,就是在body里引入了两段 JS 代码。其中,socket.io.js 是用来与服务端建立 socket 连接的。client.js 的作用是做一些业务逻辑,并最终通过 socket 与服务端通讯。首先,在server.js目录下创建 js 子目录,然后在 js目录下生成 client.js。下面是client.js的代码:var isInitiator;room = prompt(‘Enter room name:’); //弹出一个输入窗口const socket = io.connect(); //与服务端建立socket连接if (room !== ‘’) { //如果房间不空,则发送 “create or join” 消息 console.log(‘Joining room ’ + room); socket.emit(‘create or join’, room);}socket.on(‘full’, (room) => { //如果从服务端收到 “full” 消息 console.log(‘Room ’ + room + ’ is full’);});socket.on(’empty’, (room) => { //如果从服务端收到 “empty” 消息 isInitiator = true; console.log(‘Room ’ + room + ’ is empty’);});socket.on(‘join’, (room) => { //如果从服务端收到 “join" 消息 console.log(‘Making request to join room ’ + room); console.log(‘You are the initiator!’);});socket.on(’log’, (array) => { console.log.apply(console, array);});在该代码中:首先弹出一个输入框,要求用户写入要加入的房间。然后,通过 io.connect() 建立与服务端的连接,根据socket返回的消息做不同的处理:当收到房间满"full"时的情况;当收到房间空“empty"时的情况;当收到加入“join"时的情况;以上是客户端(也就是在浏览器)中执行的代码。下面我们来看一下服务端的处理逻辑:服务器端代码,server.js:const static = require(’node-static’);const http = require(‘http’);const file = new(static.Server)();const app = http.createServer(function (req, res) { file.serve(req, res);}).listen(2013);const io = require(‘socket.io’).listen(app); //侦听 2013io.sockets.on(‘connection’, (socket) => { // convenience function to log server messages to the client function log(){ const array = [’>>> Message from server: ‘]; for (var i = 0; i < arguments.length; i++) { array.push(arguments[i]); } socket.emit(’log’, array); } socket.on(‘message’, (message) => { //收到message时,进行广播 log(‘Got message:’, message); // for a real app, would be room only (not broadcast) socket.broadcast.emit(‘message’, message); //在真实的应用中,应该只在房间内广播 }); socket.on(‘create or join’, (room) => { //收到 “create or join” 消息 var clientsInRoom = io.sockets.adapter.rooms[room]; var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; //房间里的人数 log(‘Room ’ + room + ’ has ’ + numClients + ’ client(s)’); log(‘Request to create or join room ’ + room); if (numClients === 0){ //如果房间里没人 socket.join(room); socket.emit(‘created’, room); //发送 “created” 消息 } else if (numClients === 1) { //如果房间里有一个人 io.sockets.in(room).emit(‘join’, room); socket.join(room); socket.emit(‘joined’, room); //发送 “joined”消息 } else { // max two clients socket.emit(‘full’, room); //发送 “full” 消息 } socket.emit(’emit(): client ’ + socket.id + ’ joined room ’ + room); socket.broadcast.emit(‘broadcast(): client ’ + socket.id + ’ joined room ’ + room); });});在服务端引入了 node-static 库,使服务器具有发布静态文件的功能。服务器具有此功能后,当客户端(浏览器)向服务端发起请求时,服务器通过该模块获得客户端(浏览器)运行的代码,也就是上我面我们讲到的 index.html 和 client.js 并下发给客户端(浏览器)。服务端侦听 2013 这个端口,对不同的消息做相应的处理:服务器收到 message 消息时,它会直接进行广播,所有连接到该服务器的客户端都会收收广播的消息。服务端收到 “create or join”消息时,它会对房间里有人数进行统计,如果房间里没有人,则发送"created" 消息;如果房间里有一个人,发送"join"消息和“joined"消息;如果超过两个人,发送"full"消息。要运行该程序,需要使用 NPM 安装 socket.io 和 node-static,安装方法如下:进入到 server.js 所在的目录,然后执行下面的命令。npm install socket.ionpm install node-static启动服务器并测试通过上面的步骤我们就使用 socket.io 构建好一个服务器,现在可以通过下面的命令将服务启动起来了:node server.js如果你是在本机上搭建的服务,则可以在浏览器中输入 localhost:2013 ,然后新建一个tab 在里边再次输入localhost:2013 。此时,打开控制台看看发生了什么? 在Chrome下你可以使用快捷键 Command-Option-J或Ctrl-Shift-J的DevTools访问控制台。小结以上我向大家介绍了 Nodejs 的工作原理、Nodejs的安装与布署,以及如何使用 要sokcet.io 构建 WebRTC 信令消息服务器。socket.io 由于有房间的概念所以与WebRTC非常匹配,用它开发WebRTC信令服务器非常方便。另外,在本文中的例子只是一个简单例子并没有太多的实际价值。在后面的文章中我会以这个例子为基础,在其上面不断增加一些功能,最终你会看到一个完整的Demo程序。 ...

March 19, 2019 · 3 min · jiezi

TCP socket和web socket的区别

小编先习惯性的看了下某中文百科网站对Web Socket的介绍,觉得很囧。如果大家按照这个答案去参加BAT等互联网公司的前端开发面试,估计会被鄙视。还是让我们阅读一些英文材料吧。让我们直接看stackoverflow上的原文,然后翻译:原文地址:https://stackoverflow.com/que…这个讨论有超过8万的阅读量。首先我们来阅读这段有166个赞的回答:When you send bytes from a buffer with a normal TCP socket, the send function returns the number of bytes of the buffer that were sent. 当我们向一个通常的TCP套接字发送一段来自内存buffer中的字节数据时,send系统调用返回的是实际发送的字节数。If it is a non-blocking socket or a non-blocking send then the number of bytes sent may be less than the size of the buffer. 如果发送数据的目的方套接字是一个非阻塞套接字或者是对写操作非阻塞的套接字,那么send返回的已发送字节数可能小于buffer中待发送字节数。If it is a blocking socket or blocking send, then the number returned will match the size of the buffer but the call may block. 如果是阻塞套接字,两者会相等,因为顾名思义,如果send系统调用没有把所有待发送数据全部发送,则API调用不会返回。With WebSockets, the data that is passed to the send method is always either sent as a whole “message” or not at all. Also, browser WebSocket implementations do not block on the send call.而Web socket和TCP socket的区别,从发送的数据来看,不再是一系列字节,而是按照一个完整的"消息体"发送出去的,这个"消息体"无法进一步再分割,要么全部发送成功,要么压根就不发送,不存在像TCP套接字非阻塞操作那样出现部分发送的情况。换言之,Web Socket里对套接字的操作是非阻塞操作。这个区别在维基百科上也有清晰阐述:Websocket differs from TCP in that it enables a stream of messages instead of a stream of bytes再来看接收方的区别。原文:But there are more important differences on the receiving side of things. When the receiver does a recv (or read) on a TCP socket, there is no guarantee that the number of bytes returned correspond to a single send (or write) on the sender side. It might be the same, it may be less (or zero) and it might even be more (in which case bytes from multiple send/writes are received). With WebSockets, the receipt of a message is event driven (you generally register a message handler routine), and the data in the event is always the entire message that the other side sent.同理,在TCP套接字的场景下,接收方从TCP套接字读取的字节数,并不一定等于发送方调用send所发送的字节数。而WebSocket呢?WebSocket的接收方从套接字读取数据,根本不是像TCP 套接字那样直接用recv/read来读取, 而是采取事件驱动机制。即应用程序注册一个事件处理函数,当web socket的发送方发送的数据在接收方应用从内核缓冲区拷贝到应用程序层已经处于可用状态时 ,应用程序注册的事件处理函数以回调(callback)的方式被调用。看个例子:我通过WebSocket发送一个消息“汪子熙”:在调试器里看到的这个字符串作为回调函数的输入参数注入到函数体内:Chrome开发者工具里观察到的WebSocket消息体:下次面试被面试官问到TCP和WebSocket套接字的区别,相信大家应该能够知道如何回答了。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

February 24, 2019 · 2 min · jiezi

记一次Socket.IO长链服务的性能压测

网易云信IM系统中的Web版使用了Socket.IO实现浏览器环境下的长链服务;区别于常规的长链服务,为该服务的压测提出了一些新的挑战,本文总结了测试过程中的一些收获供参考。Part1测试工具选项一、工具选型GatlingNode.jsJMeterJava WebSocket这些工具都是可以支持WebSocket协议的工具,主要从以下几个方面进行对比:上手难易程度测试资源开销和性能测试平台结合的难易程度二、对比过程对比场景(1)5000个用户,每秒钟发送100个登录请求,并保持连接(2)5000个用户,每隔5s发送一条点对点消息,即发送消息频率1000工具使用过程Gatling工具简介:Gatling是一款基于Scala 开发的高性能服务器性能测试工具,它主要用于对服务器进行负载测试,并分析和测量服务器的各种性能指标。工具语言:Scala语言工具官网:http://gatling.io/docs/2.0.0-…相关代码:遇到的问题:(1)scale语言比较陌生,脚本编写时困难较多,目前暂未实现隔一定时间,即发心跳又发消息的场景;(2)无法和性能测试平台结合,系统整合成本比较高;Node.js + Socket.IO工具简介:Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效;而Socket.IO是一个基于Node.js架构体系的且原生支持WebSocket协议,可用作实时通信的软件包。Socket.IO为跨浏览器构建实时应用提供了完整的封装,且完全由JavaScript实现,语言更容易理解。工具语言:JavaScript工具官网:• http://socket.io/docs/• http://nodejs.cn/相关代码:遇到问题:(1)多节点并发分布式控制(2) 整合到现有的性能测试平台JMeter工具简介:JMeter是比较常见的性能测试开源工具,支持多种协议并且可以自定义java请求,更加灵活。工具语言:(1)Java(2) JMeter xml脚本文件工具官网:http://jmeter.apache.org/user…https://blog.flood.io/socket-…相关代码:遇到问题JMeter的工具本上是同步的,如果建立1万个连接的话,需要启动1万个线程处理,因此不适合测试高并发长连接的场景。Jetty WebSocket Client工具简介Jetty提供了功能更强的WebSocket API,使用一个公共的核心API供WebSocket的服务端和客户端使用。工具语言: Java工具官网http://www.eclipse.org/jetty/相关代码遇到问题需要自己写并发控制三、对比结果总结因为用来做性能压测,所以考虑到压测客户端需要实现高并发请求,选择Gatling和Socket.IO做了简单的对比测试。Part2 长链服务端性能问题的定位如第一部分所述,我们最终选择用Socket.IO实现了并发压测的长链客户端,下面简单说明下长链服务器端测试的一些收获;首先,简单描述下被测试服务器的功能和前端Socket.IO客户端保持长连接;解析前端JSON结构的数据包,并作二次封装后抓发给后端APP服务,并且将后端APP服务转发过来的响应包转换成为socketio可以识别的json数据包,并发送给客户端为此,我们确定该服务主要的测试点为测试服务器可以支撑的最大链接数:该测试点涉及到了TCP链接,以及我们需要注意哪些参数,才可以保证用户建立的连接数目不会因为限制而达不到目标。每秒钟可以解析的包的数量:该测试点涉及到了网络流量,如果已经达到了网络流量的峰值时,会出现什么样的问题。以下是这次测试过程中遇到的一系列问题:问题1:最大连接数只能达到65K+65535,对于程序员来说,这是一个很敏感的数字,因为一台服务器,限制的最大端口号即为65535,所以出现这个问题后:第一反应:端口号不够用了。 分析认为Link服务作为服务端,对外提供的服务端口仅有一个,所以不存在服务端端口号不够用的情况;第二反应:句柄数不够用了。文件句柄数相当于文件的标识符,建立一条socket连接,同样会使用一个文件句柄,而系统默认的单个进程使用的文件句柄为1024;对于这种需要保持大量连接的服务来说,一般情况下都是需要修改文件句柄数的,文件句柄数修改的方法如下:A)查看单个进程使用的最大文件句柄数的方法:B)查看当前进程打开了多少个文件句柄呢:C)修改Linux的最大文件句柄数限制的方法:1)ulimit -n 65535在当前session有效,用户退出或者系统重新后恢复默认值2)修改用户下的.profile文件:在.profile文件中添加:ulimit -n 65535只对当前用户有效3)修改文件/etc/security/limits.conf,在文件中添加:(立即生效-当前session中运行ulimit -a命令无法显示)4)修改文件/etc/sysctl.conf添加:运行命令:/sbin/sysctl -p 使配置生效但是修改文件句柄的限制后,该问题依然没有得到解决。这个时候想到可以使用dmesg查看系统日志,dmesg命令可以显示Linux内核的环形缓冲区信息,可以从中获得诸如系统架构、CPU和挂载的硬件,RAM等运行级别的大量系统信息,因此dmesg命令在设备故障的诊断方面是非常重要的。通过dmesg,查看到了如下问题:从上面的信息可见conntrack表满了,那么conntrack表是做什么的?nf_conntrack/ip_conntrack用来跟踪连接条目,会使用一个哈希表来记录 established 的记录nf_conntrack 在 2.6.15 被引入,而 ip_conntrack 在 2.6.22 被移除,如果该哈希表满了dmesg命令就会出现:nf_conntrack: table full, dropping packet如何修改conntrack表的大小到此为止,这个问题我们算是解决了,总结下,遇到的网络相关的知识点包括 文件句柄 和conntrack表。问题2:在某些用例场景下,稳定连接只能建立3800+这个问题的前提是某些时候,也就是说偶现的,这种情况基本上可以排除是应用程序内部的问题。那对于这个问题我们使用了哪些定位手段呢?watch和netstat 两个命令watch -d 定期查看一些信息,默认是2s;netstat -st | grep ignored 查看TCP连接的一些统计信息;可见有SYNs to LISTEN sockets ignored在不停增加,这表明收到连接建立过程中的三次握手的ACK包,但是因各种原因(包括accept队列满) 创建socket失败;ss -ln : 查看进程对应的backlog的使用情况backlog:简单理解来说,连接已经在TCP层建立成功(完成了三次握手的过程)但是还没有被应用程序所接受,这种情况下,该链接会存放到backlog这样一个缓冲队列内通过上面这个命令可以看到应用程序的backlog队列已经满了,但是即便把这个值调整为1024,该队列还是会满的。到这儿,我们的定位过程陷入了僵局,下一步该怎么定位呢?这期间我们查看了内存信息,CPU使用情况等,均没有找对地方;但是在定位过程中,我们发现,有时候JStack、JProfiler等工具都无法连上该服务了。突然想到文件句柄是不是满了?又执行了以下几条命令:/proc/{pid}这个目录下了对应了所有在运行的进程的相关信息,通过查看/proc/{pid}/fd目录下的个数我们可以看到当前进程使用的文件句柄数有多少。通过查看limits文件里面的值,可以看到系统对该进程的一些限制,不幸的是,出现这个问题的时候,max open files这个值被限制为了4096。调整该值之后最终解决了这个问题问题3:大量的连接建立不成功当我们解决了一个又一个的问题后,突然发现高压力下存在大量的连接建立不成功的问题,主要表现在:原来:每秒钟500个连接建立的请求时,5w个用户都可以建立成功;现在:每秒钟500个连接建立的请求时,5w个用户只有4w左右的连接可以建立成功;这个问题也花费了不短 的时间,我们使用了各种命令查看各种网络指标通过netstat查看到send-q中有大量的消息堆积;通过sar查看到有大量的重传;总结起来还是TcpDump抓包比较效果更好,从测试的开始,我们就只抓这一条链路上的包,且发送方和接收方,双方都抓包:然后通过Wireshark分析,可以看到发送方/客户端:存在一定时间段发送出大量的重传包接收方/服务端:客户端发送大量重传包的这个过程中,什么都没有收到这基本说明网络有问题然后我们通过netperf测试了下网络带宽的情况,发现这两台机器之间的网络上限只有21Mb,而且测试过程中的网络请求量是超过这个值的。由于是在云主机环境上,通过咨询云网络相关的同事发现,问题原因是因为云主机之间的网络QoS开启限制导致的,限制了每台云主机之间的网络带宽最高位21Mb,超过这个值则会被丢包。TCP的链接状态图最后总结下在定位类似的网络问题中一些常规的命令、工具和关注的方向可以使用以下命令推荐使用以下工具重点关注以下指标想要获取更多产品干货、技术干货,欢迎关注网易云信博客。

January 21, 2019 · 1 min · jiezi

Hola~ 一款基于Electron的聊天软件

Hola前言本项目旨在从零到壹,制作一款界面精美的聊天软件。Github 地址因为已工作,所以可能没有多少时间来继续跟进这个项目了,项目可优化的点已在下文列出,欢迎大家 Fork 或 Star。ps: 征 logo 一枚。因为本人是开发,设计功底欠缺,所以软件 logo 设计的有点丑,如果有大神有更好的 logo,欢迎 email。技术栈开发环境操作系统:macOS High Sierra v10.13.1编辑器:Visual Studio Code v1.19.1npm:v5.3.0Node:v8.4.0客户端UI设计:Sketch软件框架:Electron界面实现:Vue.js + Vuex + Vue-Router + Webpack通信模块:socket.io-client视频聊天:原生 WebRTC服务端服务器:Node.js后端框架:Koa2通信模块:socket.io数据库:Redis 和 MongoDB软件效果图实现功能[x] 登录注册模块(<手机号+验证码>形式的登录注册)[x] 聊天区模块[x] 最近联系人列表[x] 历史消息(暂未做上拉加载)[x] 私聊[x] 文本消息[x] 图片消息[x] 视频聊天[x] 群聊[x] 文本消息[x] 图片消息[x] 联系人模块[x] 联系人列表[x] 好友资料展示[x] 群组资料展示[x] 删好友,退出或解散群组[x] 功能区模块[x] 添加好友/群组[x] 创建群组[x] 设置区模块[x] 个人资料设置[x] 软件设置[x] 国际化[x] 中文[x] 英文项目目录.├── LICENSE ├── README.md├── client # 客户端代码├── docs # 各种文档(需求文档、UI文档、流程图、数据库设计等)├── preview.png # 软件预览图└── server # 服务端代码反思 & 展望该项目为我大学毕业设计的项目,因时间紧迫,只实现了基本的聊天、加删好友等功能,很多功能还未实现,所以软件还是有很多的瑕疵。为此,我特意思考了很长时间,将待改进的细节或新的功能总结如下:[ ] 历史消息做成上拉瀑布流加载的效果[ ] 为消息注明消息时间、发送状态、已读未读等状态[ ] 为最近联系人列表添加最后一条消息的展示[ ] 为最近联系人添加未读消息个数的统计[ ] 添加好友或加入群组时要进行确认[ ] 为软件的新消息使用系统原生通知窗口通知[ ] 为软件增加原生菜单[ ] 升级输入框,从而可以向输入框直接插入剪切板中的图片[ ] 自己搭建文件服务器,图片服务器(或者使用第三方比如七牛云、阿里云的相关服务)[ ] 为 WebRTC 实现后备方案,搭建 Relay Server,以增强视频聊天的稳定性[ ] 增加网络断开处理的相关逻辑[ ] 了解数据加密相关知识,为消息作加密处理[ ] 为软件做跨平台处理,兼容性方面有待加强[ ] 实现软件自动更新[ ] 接入智能机器人聊天[ ] 实现本地存储历史消息(nedb)[ ] 为软件加入聊天情况分析(比如每天发了多少条消息,与谁聊天最频繁等)扩展阅读初探 Electron - 理论篇初探 Electron - 升华篇XCel 项目总结 - Electron 与 Vue 的性能优化【译】Electron 自动更新的完整教程(Windows 和 OSX)Getting Started with WebRTC通俗易懂:一篇掌握即时通讯的消息传输安全原理即时通讯安全篇(三):常用加解密算法与通讯安全讲解socket.io断线后重连和消息离线存储如何实现Socket.IO stream运用google-protobuf的IM消息应用开发(前端篇)Can one hack “paste image” support into a textarea in Firefox?在线和离线事件 ...

November 5, 2018 · 1 min · jiezi