socket io 初步了解(node js)

背景

公司的同事自己用socket io实现websocket服务,我用node js websocket去连接发现连接不上,于是我就找我同事。我同事说:必须要用socket io client连接才可以。我觉得很奇怪,一个weocket框架怎么还不能满足普通的实现,我说你是不是代码写错了,后面发现是我理解错了,我同事也理解错了,我们当初定义协议走websocket 通信,协议就直接走json 或者其他自定义协议即可。

自己花了半天时间看帮助文档和代码开发

分析过程

  1. 直接分析源代码调试
  2. 看官方的帮助文档,全部看完

socket io 到底是什么?

socket io是一个网络通信框架,依赖websocekt,并没有实现websocket,对应业务层逻辑,跟我们自己依赖系统套接字(tcp协议)实现自定义协议是业务层一样。如果我们用websocket实现自定义协议,别人用websocket必须按照我们定自定义协议通信,所以socket io 服务,不能用原生的websocket连接,我自己用127.0.0.1:3000/socket.io/?transport=websocket&EIO=4 虽然可以建立的链接,但发送消息就立即断开,因为包的格式不对。
它定义协议包,我们只要emit 提交包就可以了,我们可以给基本类型,或者给对象或者Buffer(二进制数据),基本就不用管协议了。

socket io 几个我用到特性

emit 投递数据包

//客户端监听
socket.emit("hello", { a: "b", c: [] });

//服务监听hello 消息
server.io.on("hello", (a,b,c)=>{

})

这个因为自定义协议,所以包的定义就不要了,投递内容就可以了。
纯websocket 不能emit 投递数据,不是给网络使用的,所以这个特性用起来就特别舒服,减少一些工作量。
不然我们就要在服务器端 在onmessage 来自己解释数据包了,那么就要定义id字段,来判断是什么类型的。具体包的协议我没有研究,如果自己就用二进制来传递,解包就必须一个一个写。socket io 能判断传递类型,那么说明就是在网络协议里面传了类型,不然无法知道什么类型。

身份验证

客户端在连接时候可以给socket auth复制,比喻传递一个对象{token: "验证密钥"},也可以传递函数,用函数的返回值灵活获取。
如果你token有过期时间,那么建议传递函数,因为要更新token.
官方的例子

import { io } from "socket.io-client";

const socket = io("ws://example.com/my-namespace", {
  reconnectionDelayMax: 10000,
  auth: {
    token: "123"
  },
  query: {
    "my-key": "my-value"
  }
});

重连

如果服务器重启,或者客户端断网,那么socket io 自己会进行重连。如果服务器主动disconnet的话是不会进行重连的。
重连得到时候默认传递之前给的token,如果token有更新就校验失败(如果过期的话),所以还是建议传递函数。

request-respond的模式支持 类似http request- respond 一一对应

你可以提交一个包,最后一个参数是一个函数话,服务器会通过通过回调函数传递参数(肯定不真调用回调,看起来像而已,2个跨网络 怎么可能直接调用呢,这个就点像ipc)

socket.emit("task1",data,call_back);

原理

创建任务id跟你数据包一起传递给服务器,但服务器会传送数据也会带上这个任务ID,但检测有任务id进行比对,找到当初回调函数,调用即可。 一些好多网络库也是这么开发的 ,通过任务ID实现。
http 半双工,只能一问一答(协议决定),http 发送请求后就一直等待结果,结果无法访问时候,可能不能发送内容(http 1.1)http 2.0 我就不知道了,如果实现全双工的话,估计也是通过类似task id逻辑是实现

socket io 实现回调代码

    /**
     * Called upon a server acknowlegement.
     *
     * @param packet
     * @private
     */
    onack(packet) {
        const ack = this.acks[packet.id];
        if ("function" === typeof ack) {
            debug("calling ack %s with %j", packet.id, packet.data);
            ack.apply(this, packet.data);
            delete this.acks[packet.id];
        }
        else {
            debug("bad ack %s", packet.id);
        }
    }

            //emit(ev, ...args)
            //最后一个参数如果函数,创建packet,服务器响应时候,传送回来即可
            //这样子就可以变成Http
            if ("function" === typeof args[args.length - 1]) {
            const id = this.ids++;
            debug("emitting packet with ack id %d", id);
            const ack = args.pop();
            this._registerAckCallback(id, ack);
            packet.id = id;
        }

room的概念

我没有写,但以后用到,这样子方便对用户进行过滤,这个业务方便对用户进行快速操作而已,不用自己进行for循环遍历比较了

不支持websocket会用http 轮训模式

默认就是轮询,所以建议直接设置transport 为 ['websocket', 'polling'] 2个服务器。但我没有测试,如果开始用轮询是否会升级到websocket,没有验证这个需求。

socket io 简单总结

socket io 是一个网络框架,封装一些业务功能,所以你不能用纯websocket连接,具备验证,重连,房间,自动解包特性,加快业务开发能力。市面太多用来聊天软件,我觉得主要原因是浏览器兼容性,他可以一套代码兼容所有浏览器能力(可以在不支持websocket浏览器跑),因为聊天网页不知道用户在什么浏览器上,但我感觉这个特性在未来会越来越弱,因为浏览器版本升级,win7 等系统份额越来越少,那么以后系统浏览器标准化,感觉这个框架就比较弱了,因为稍微有实力公司自己在websocket 写一套业务很简单,不过在socket io 上二次开发也简单。


现在互联网是一个好的时代,什么业务都基本对应的开源代码,什么东西都参考,除了小部分高端或者底层东西没有。对于小公司来说太方便了,开发速度嘎嘎的。

感谢那些为开源做出贡献的那些人

node js 多线程

一、前提

我以前只认为node js 只能单线程,我自己用了一段时间electron 桌面开发,如果遇到CPU密集型业务怎么做呢?我不可能只靠一个CPU来搞业务啊,这个限制不是太大了?

二、解决方法

1:多进程

child_process

等模块创建多进程

这种打补丁方法不是很好,涉及进程通信,写代码效率不高,很容易出现bug。开发者很可能出现变量无法访问的问题,因为多个进程,变量没有共享。不过业务写很分开,也问题不大,多进程资源比较大。

2:多线程

worker_threads

真正用的是多线程,我自己写代码测试,不停创建worker_threads..

测试代码

const { Worker, isMainThread, parentPort } = require('worker_threads');
console.log("hello world:" + isMainThread)

/*if (isMainThread) {
  // This code is executed in the main thread and not in the worker.
  
  // Create the worker.
  const worker = new Worker(__filename);
  // Listen for messages from the worker and print them.
  worker.on('message', (msg) => { 
      console.log(msg); 
    });
} else {
  // This code is executed in the worker and not in the main thread.
  
  // Send a message to the main thread.
  parentPort.postMessage('Hello world!');
}*/

function test(){
    if(isMainThread){
      console.log("自动线程哦");
      const worker = new Worker("./work.js");
    }
   
}

setInterval(() => {
  test();
}, 1000);

通过定时器不停创建工程线程。

work.js

const { isMainThread, parentPort,Worker, threadId } = require('worker_threads');



const min = 2
const max = 1e7

function generatePrimes(start, range) {
    let primes = []
    let isPrime = true
    let end = start + range
    for (let i = start; i < end; i++) {
      for (let j = min; j < Math.sqrt(end); j++) {
        if (i !== j && i%j === 0) {
          isPrime = false
          break
        }
      }
      if (isPrime) {
        primes.push(i)
      }
      isPrime = true
    }
    return primes
 }


console.log("线程id:" + threadId)
generatePrimes(1, 10000000)
console.log("线程id:" + threadId + " 完成")

可以通过资源管理器查看线程【默认不显示,自己设置一下】,线程数不停增加

可以不停增加,因为我这个写法,会很快把cpu吃死。。。。如果像以前nodejs 普通写法,最多只能吃一个CPU..


注意在线程里面不要直接访问对象,毕竟node js 模式就是异步+主线程,引入多线程导致更多问题,多线程仅仅执行,然后丢回主线程即可。

electron 拦截 window.open

一、背景

我之前写一个工具用来客户端加载网页,同时可以注入js,增加功能,类似chrome 浏览器扩展逻辑,但由于网页总是新建一个窗口,我虽然用JS 注入,修改了所有 a 标签的 。但对于动态的无能为力,除非定时器不停遍历,但性能不好,于是就想从electron入手。

二、过程

自己网络查找一些有关的信息,找到一个new-window 事件,这个事件可以捕获新建窗口逻辑。

one_plugin_ui.webContents.on('new-window',(event, url, frameName, disposition, options, additionalFeatures)=&gt;{
      log.debug("new")
      one_plugin_ui.loadURL(url) //在当前窗口打开,如果不想打开,完全想拦截,可以通过url 判断,然后不跳转URL
      event.preventDefault()      
    })

node request 设置代理

一、背景

自己用node request 请求 http 发现请求无法正确应答,我用fidder抓包没有抓对应数据包,因为默认node 不会走 windows 默认代理。

二、解决过程

在request 构建增加参数 proxy: 'http://127.0.0.1:8888'

这个设置代理到fidder里面去,如果非HTTPS 可以直接用wireshark来抓包。

三、补充

如果是第三方程序,如果不会默认走代理,可以用proxifier, 这个软件出来很多年,自己可以百度下载,官方可以试用30天。

proxifier过滤指定进程名字,然后转发443 80 端口数据包到fiddler上面,这样子就可以抓任何程序的包,无论他支不支持代理。

java 压缩的二进制数据传递给node js处理问题

一, 背景

自己压缩android 截图数据压缩然后网络传递给我node程序,然后发现node 无法解压。

二,排错过程

打印java 压缩的二进制数据数组,打印node收到 buffer,发送java数组函数负数,而node 得到没有负数

三,思考

看到这个2边数据不一样,我就想起来是什么导致。java byte 是 -128~127,node byte是 0~255. 这样子导致数据对不上,我要说一点二进制数据是一样的。这个只是node 读同样的二进制,解析数据0~255范围[byte],java虚拟机解析~128-127。这个就跟我们传送数据,会出现大端和小端问题一样,必须保证2边一致。。。。

四,解决

我直接在NODE 处理每个字节 var c = buffer[i] & 0xff 即可

node js 后台解析HTML(获取微博热门搜搜)

一,背景

最近自己准备写一个热门排行榜的功能,自己百度一下Node js 能够解析html的库,自己找到cheerio,这个库发现非常适合自己,因为他的用法跟jquery 类似。于是我拿微博热门搜索来练手,熟悉cheerio库,顺便用一个node js 网络库got

二,逻辑

通过网页源代码,可以分析每个tr下面td class=td-02 下面a标签就是我们要找的元素,那么通过jquery 语法写法 $("tbody>tr>td.td-02>a"),逻辑就这么简单。

三,代码

const html_parse = require("cheerio")
const got = require("got")

async function getWeibo(){
    var all_data = []
    var respond = await got("https://s.weibo.com/top/summary?cate=realtimehot", {headers:{
        "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.78"
    }})

    if(respond.body){
        let $ = html_parse.load(respond.body)
        let items = $("tbody>tr>td.td-02>a")

        for(let i = 1; i < 10 && i < items.length; i++){
            let item = "https://s.weibo.com";
            let href = $(items[i]).attr("href")
            let text = $(items[i]).text()
            all_data.push({
                href: item + href,
                text: text
            })
        }
    }
    return all_data
}


async function test(){
    let r = await getWeibo()
    console.log(r)
}

test()

代码是不是非常简单,await 只是异步语法糖,自从我用c# 就喜欢用这个语法,避免死亡回调,网络库用got ,我请求替换user-agent,貌似微博也没有做限制,你不设置照样也能获取数据,但我还是加了,以免带来不必要的麻烦,这里面let i = 1 从开始循环,因为它有一个置顶元素,设置1直接过滤掉,这里10 只是我自己主要10条,你可以通过参数传递获取条数。

四:效果图

五,补充

我以前解析html,我都是当成字符串切割,然后再自己解析,虽然这种比较快,但写代码每次都要计算切割,不够通用,如果直接用Html 解析库,就很容易获取了,特别这种类似jquery的查找方法的。


自己最近写前端比较多,经常看到说jquery 落寞之类,我自己反复思考这个问题,我觉得这个无稽之谈。jquery 只是封装操作dom的类而已,vue 或者 react 他们另外数据库绑定而已,他们只是隐藏了dom操作而已,但他们底层也必须实现dom操作元素。对于新手来说用vue 或者react 等等非常舒服,但毕竟不是根本。html + javascript + css 这个永远不会变,那么dom操作永远都需要。这个就好像windows开发程序员,你用了mfc封装 window 操作类,你就觉得windows api 是垃圾可笑。

我觉得开发不能完全只管业务,不去了解任何有关底层知识,这种就是舍本逐源的做法。

electron 拦截下载,启动浏览器下载

一,说明

electron 不像浏览器会带下载管理,electron 页面调用下载的话,无法感知下载进度,我这里偷懒,直接丢给默认浏览器下载。

二,代码

    one_plugin_ui.webContents.session.on("will-download", (event, item, webContents)=>{
      let url = item.getURL()
      item.cancel()
      shell.openExternal(url);
      webContents.loadFile("download.html")
    })
  • one_plugin:electron的 windows
  • win-download:触发下载事件
  • item.cancel():取消electron默认下载逻辑
  • shell.openExternal:调用默认程序(浏览器)打开url
  • webContents.loadFile:加载自己定义下载界面【因为点击文件下载,会弹出新的窗口,默认是空白的,为了体验好一点,我增加一个提示下载的页面】
  • item: 对应的electron的对象 downloaditem

三,文档

downloaditem的介绍

nodejs console 日志统一增加时间戳

一,背景

node js 一般打印日志使用console.log ,如果现有项目希望增加打印日志,那么我们可以重载打印日志函数,我们直接用现有的功能模块

log-timestamp

二,使用

require('log-timestamp');

导入即可,默认时间戳是用的国际时间,你可以传入你要写的时间戳。

const log_time = require('log-timestamp');   //增加日志时间戳
log_time(function(){
    return "[" + new Date().toLocaleString() + "]"
})

三,原理

重载函数

类似console.log = function(...){ console.old_log(...)}

fishtools 插件工具1.0.0.2

背景

这个工具我很早之前就想开发了,陆陆续续开发一段事件时间,基本功能开发完全了

目的

软件快速通过关键词或者关键词的缩写打开对应的网址,因为自己常常会开一些web,进行访问。 这个软件有点借鉴utools,这个工具可以对接任何web网页,加入js注入。
本软件只是练手electron 技术而已,后续慢慢补充各种插件和web,实现自己软件自给自足。提高自己快速编码能力和产品的驾驭能力。

用途

  • 快速访问网址,不用自己记录各种网址
  • 可以常驻后台,避免浏览器关闭,导致网页关闭
  • 自带js注入框架,方便自己扩展各种网页功能,满足更多功能【具有无限可能】
  • 后续可能的功能是窗口大小记忆

使用教程

添加自己的网址,这里主要设置关键词,这里设置js,那么当自己ctrl + alt + k 调出输入框 ,然后输入js,就会列出访问的网址。

下载云端js 插件,你也可以右键自己添加js插件,这样子就可以控制任何网页界面代码。

这里我设置正则,匹配语雀文档时候,弹出密码验证框,这样子相当于给自己的网页语雀增加了密码验证。这里密码我写死为helloworld123

下载

链接:https://pan.baidu.com/s/172DBUEKF_Y911hNik7Gtsw
提取码:1234
复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V5的分享

代码

因为electron练手项目,所以暂时不开源,但electron asar只要解压就可以看到代码,所以你也可以获取到代码

TODO

  • 增加窗口大小记录
  • 增加导航栏,方便使用

electron 加载第三方页面,无法jquery问题

如题,这个官方问题里面已经写了解决方案,我只是记录一下方便整理

我在 Electron 中无法使用 jQuery、RequireJS、Meteor、AngularJS。

因为 Electron 在运行环境中引入了 Node.js,所以在 DOM 中有一些额外的变量,比如 moduleexports 和 require。 这导致 了许多库不能正常运行,因为它们也需要将同名的变量加入运行环境中。

我们可以通过禁用 Node.js 来解决这个问题,在Electron里用如下的方式:

// 在主进程中.
const { BrowserWindow } = require('electron')
const win = new BrowserWindow(format@@
  webPreferences: {
    nodeIntegration: false
  }
})
win.show()

假如你依然需要使用 Node.js 和 Electron 提供的 API,你需要在引入那些库之前将这些变量重命名,比如:

<head>
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
<script type="text/javascript" src="jquery.js"></script>
</head>