#773 CMPP 短信网关协议

2022-03-29

术语

  • ISMG: Internet Short Message Gateway
  • SMPP: Short Message Peer to Peer
    一个国际上比较通用的短信网关协议
    CMPP 可能兼容 SMPP,因为 Wireshark 会将 CMPP 会话识别成 SMPP
  • CMPP: China Mobile Peer to Peer
    中国移动开发的的短信网关协议
  • SMC: Short Message Center
  • GNS: Gateway Name Server
    相当于 CMPP 网络的路由器
  • SP: Service Provider
  • SMC: Short Message Control
  • ISMG_Id: 网关代码
  • SP_Id: SP 企业代码
  • SP_Code: SP 服务代码
  • Service_Id: SP 业务类型
  • MO: 手机发送 Originate
  • MT: 手机接收 Terminated

网络连接

CMPP 基于 TCP 协议。可以长连接,也可以短连接。
长连接支持一次连接发送多次 CMPP 消息,没有消息的时候需要维持心跳。
短连接则是一次 CMPP 通信之后就断开连接。

关于心跳:
建议每 3 分钟一次心跳,如果 60 秒内没有心跳响应,应该再次心跳,总共 3 次没有响应就断开连接。
关于重试(网关与 SP 之间,网关之间):
60 秒之后没有响应,立即重试,总共 3 次没有响应就停发。

消息采用并发方式发送,加以滑动窗口流量控制,窗口大小参数 W 可配置,现阶段建议为 16,即接收方在应答前一次收到的消息最多不超过 16 条。

关于短连接:
60 秒超时,重试 2 次。

端口

  • 7890 长连接(SP 与网关之间)
  • 7900 短连接(SP 与网关之间,网关之间)
  • 7930 长连接(网关之间)
  • 9168 短连接(网关与 GNS 之间)

数据类型

var Total_Length uint32 // 消息总长度
var Command_Id uint32   // 命令或响应类型
var Sequence_Id uint32  // 消息流水号, 递增, 步长为 1, 循环使用
命令 Command_Id
CMPP_CONNECT 0x00000001
CMPP_TERMINATE 0x00000002
CMPP_SUBMIT 0x00000004
CMPP_DELIVER 0x00000005
CMPP_QUERY 0x00000006
CMPP_CANCEL 0x00000007
CMPP_ACTIVE_TEST 0x00000008
CMPP_FWD 0x00000009
CMPP_MT_ROUTE 0x00000010
CMPP_MO_ROUTE 0x00000011
CMPP_GET_ROUTE 0x00000012
CMPP_MT_ROUTE_UPDATE 0x00000013
CMPP_MO_ROUTE_UPDATE 0x00000014
CMPP_PUSH_MT_ROUTE_UPDATE 0x00000015
CMPP_PUSH_MO_ROUTE_UPDATE 0x00000016

RESP Command_Id 则是对应的 Command_Id 最高 4 位为 8,即 0x8X

SP 与 ISMG 之间的通信

对于一般开发来说就这 7 个接口。

  1. 建立连接 CMPP_CONNECT
  2. 断开连接 CMPP_TERMINATE
  3. 提交信息 CMPP_SUBMIT
  4. 获取状态 CMPP_DELIVER
  5. 撤回信息 CMPP_CANCEL
  6. 查询信息 CMPP_QUERY
    只是一些统计信息,我想不到这个接口的应用场景
  7. 链路检测 CMPP_ACTIVE_TEST

连接 CMPP_CONNECT

var Source_Addr [6]byte // SP_Id
var AuthenticatorSource [16]byte // SP_Code
// md5(Source_Addr + 9 字节 0 + shared secret + timestamp)
// timestamp: MMDDHHMMSS, 月日时分秒, 补 0
var Version uint8 // 版本号, 高 4 位表示主版本号, 低 4 位表示次版本号, 最大 15.15
var Timestamp uint32 // 时间戳, 默认为 0

CMPP_CONNECT_RESP

var Status uint8 // 状态, 0 正确, 1 结构错误, 2 非法源地址, 3 认证错误, 4 版本错误, 5 其他错误
var AuthenticatorISMG [16]byte // ISMG 认证码
// md5(Status + AuthenticatorSource + shared secret)
// 如果认证错误, 此项为空
var Version uint8 // 服务器支持的最大版本号

断开连接 CMPP_TERMINATE

无请求消息体,无响应消息体

提交信息 CMPP_SUBMIT

var Msg_Id uint64       // 消息标识, 网关负责自主生成,SP 留空
var Pk_total uint8      // 消息总条数, 从 1 开始
var Pk_number uint8     // 消息序号, 从 1 开始
var Registered_Delivery uint8 // 是否要求返回状态确认报告, 0 不需要, 1 需要, 2 产生 SMC 话单 (该类型短信仅供网关计费使用,不发送给目的终端)
var Msg_level uint8     // 信息级别, 0 低, 1 中, 2 高
var Service_Id [10]byte // 业务类型
var Fee_UserType uint8  // 计费用户类型, 0 普通用户, 1 行业用户, 2 用户组
var Fee_terminal_Id [32]byte // 被计费用户的号码
var TP_pid uint8        // GSM协议类型, 0 GSM 03.40, 1 CDMA, 2 WCDMA, 3 CDMA2000, 4 联通移动TD专用协议
var TP_udhi uint8       // GSM协议类型
var Msg_Fmt uint8       // 信息格式, 0 ASCII 串, 3 短信写卡操作, 4 二进制, 8 UCS2 编码, 15 含 GB 汉字
var Msg_src [6]byte     // 消息发送者, SP_Id
var FeeType [2]byte     // 资费类别
var FeeCode [6]byte     // 资费代码, 以分为单位
var ValId_Time [17]byte // 存活有效期,格式遵循 SMPP3.3 协议
var At_Time [17]byte    // 定时发送时间, YYMMDDhhmmsstnnp, t 是 1/10 秒,nn 是与 UTS 时间的差值 00-48,p `+/-`
var Src_Id [21]byte     // 源号码
// SP 的服务代码或前缀为服务代码的长号码,
// 网关将该号码完整的填到 SMPP 协议 Submit_SM 消息相应的 source_addr 字段
// 该号码最终在用户手机上显示为短消息的主叫号码
var DestUsr_tl uint8    // 接收信息的用户数量, 小于 100
var Dest_terminal_Id [][21]byte // 接收短信的用户号码 (MSISDN), 21 * DestUsr_tl
var Msg_Length uint8    // 短消息长度
var Msg_Content []byte  // 短消息内容
var Reserve [8]byte     // 保留

注意:关于短信群发的问题,若 SP 对于群发消息不要求状态报告的回送时,才可以考虑群发,否则必须逐条发送。

关于 TP_udhi 的解释:

如果 = 1,则需要在 Msg_Content 中加入一个 udhi 头,定义如下:

  1. 六字节
    05 00 03 开头,分别表示剩余 5 字节,xxx,剩余标识长度为 3
    第四字节,唯一标识
    第五字节,短信条数
    第六字节,序号
  2. 七字节
    06 08 04 开头,分别表示剩余 6 字节,xxx,剩余标识长度为 4
    第四五字节,唯一标识
    第六字节,短信条数
    第七字节,序号

CMPP_SUBMIT_RESP

var Msg_Id uint64       // 消息表示, SP 自主生成
var Result uint8        // 结果, 0 正确, 1 消息结构错, 2 命令字错, 3 消息序号重复, 4 消息长度错,
                        //       5 资费代码错, 6 超过最大信息长, 7 业务代码错, 8 流量控制错, 9~ 其他错误
  • MMDDHHMMSS,26 位
    月份 4 位, 1-12
    日期 5 位, 1-31
    小时 5 位, 0-23
    分钟 6 位, 0-59
    秒钟 6 位, 0-59
  • 网关代码,22 位
  • 序列号,16 位,递增, 步长为 1, 循环使用

查询 CMPP_QUERY

var Time [8]byte // YYYYMMDD
var Query_Type uint8 // 查询类型, 0 总数查询, 1 按业务类型查询
var Query_Code [10]byte // 查询码, 查询类型为 0 时无效, 查询类型为 1 时此处为业务类型
var Reserve [8]byte // 保留

CMPP_QUERY_RESP

var Time [8]byte
var Query_Type uint8
var Query_Code [10]byte
var MT_TLMsg uint32 // 接收消息总数
var MT_TLusr uint32 // 接收用户总数
var MT_Scs uint32   // 转发成功总数
var MT_WT uint32    // 待转发总数
var MT_FL uint32    // 转发失败总数
var MO_Scs uint32   // 发送成功总数
var MO_WT uint32    // 待发送总数
var MO_FL uint32    // 发送失败总数

送交短信 CMPP_DELIVER

从网关发送出来的消息

var Msg_Id uint64
var Dest_Id [21]byte            // 目的号码
var Service_Id [10]byte
var TP_pid uint8
var TP_udhi uint8
var Msg_Fmt uint8
var Src_terminal_Id [21]byte    // 源号码
var Registered_Delivery uint8
var Msg_Length uint8
var Msg_Content []byte          // Msg_Length
var Reserved [8]byte
var Msg_Id uint64
var Stat [7]byte
// DELIVRD 送达
// EXPIRED 过期
// DELETED 删除
// UNDELIV 未送达
// ACCEPTD 接收
// UNKNOWN 非法
// REJECTD 拒绝
var Submit_time [10]byte
var Done_time [10]byte
var Dest_terminal_Id [21]byte
var SMSC_sequence uint32

SP 等待状态报告 48 小时。

CMPP_DELIVER_RESP

var Msg_Id uint64
var Result uint8

删除短信 CMPP_CANCEL

var Msg_Id uint64

CMPP_CANCEL_RESP

var Success_Id uint8 // 0 成功, 1 失败

链路检测 CMPP_ACTIVE_TEST

无消息体。

CMPP_ACTIVE_TEST_RESP

var Reserved [8]byte

#770 Git 提交信息

2022-03-11

关于提交信息相关的规范中,被最多提及的应该是 Angular 的 Commit Message Format

<type>(<scope>): <short summary>
  │       │             │
  │       │             └─⫸ Summary in present tense. Not capitalized. No period at the end.
  │       │
  │       └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
  │                          elements|forms|http|language-service|localize|platform-browser|
  │                          platform-browser-dynamic|platform-server|router|service-worker|
  │                          upgrade|zone.js|packaging|changelog|docs-infra|migrations|
  │                          devtools
  │
  └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
  • build 构建
  • ci 持续集成
  • docs 文档(documentation)
  • feat 新功能(feature)
  • fix BUG 修复
  • perf 性能优化
  • refactor 重构
  • test 测试代码

Vue 也有一个 Git Commit Message Convention

/^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types)(\(.+\))?: .{1,50}/

比 Angular 增加了:

  • polish 优化
  • style 样式修改
  • workflow 工作流改进
  • chore 构建过程或辅助工具的变动

  • style: 格式(不影响代码运行的变动)

  • refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)

    • improvement: 改进
    • types: 类型定义文件修改
  • chore:构建过程或辅助工具的变动

    • build: 打包
    • ci: 持续集成
  • 其他

    1. revert: 撤销,版本回退
    2. wip: 开发中

参考

  • Using Git Commit Message Templates to Write Better Commit Messages
    https://gist.github.com/lisawolderiksen/a7b99d94c92c6671181611be1641c733
  • git commit 提交信息规范入门(配合 vscode)
    https://blog.csdn.net/weixin_40780243/article/details/108900691
  • 技巧 - 如何写好一个 Git 提交信息及几种不同的规范
    https://zhuanlan.zhihu.com/p/27501055
  • Commit message 和 Change log 编写指南
    https://ruanyifeng.com/blog/2016/01/commit_message_change_log.html

#767 CopyQ: 跨平台剪贴板

2022-03-08

安装和使用

Ubuntu 上安装:

wget https://github.com/hluk/CopyQ/releases/download/v6.1.0/copyq_6.1.0_Debian_11-1_amd64.deb -P ~/Resources/
sudo gdebi ~/Resources/copyq_6.1.0_Debian_11-1_amd64.deb

点击打开之后,在右上角会有一个小图标。

Windows 版本下载地址:

https://github.com/hluk/CopyQ/releases/download/v6.1.0/copyq-6.1.0-setup.exe

PS: Windows 版本安装程序不支持中文,但是安装之后支持中文。

就是会监听剪切板,在第一个标签页中(默认只开一个标签,不会显示标签页切换栏)新建条目。

不只是剪贴板,可以用来存储各种需要复制粘贴的小片段。可以固定,加标记,支持搜索。
太可惜了,我的工作中不需要导出复制粘贴内容,对我的工作效率帮助不会很大。
但是用来跨平台同步剪贴板,还是非常不错的。

这个小工具真是不错,要是可以进一步提升颜值,改进体验,今后大有发展。

配置

  • Windows:C:\Users\用户名\AppData\Roaming\copyq
    PS:Portable(压缩包,免安装)版本直接就是在 copyq 子目录。
  • Linux:~/.config/copyq

如何同步剪切板

首选项 > 条目 > 同步

指定标签页,和存储目录,然后就会自动将当前剪贴板的内容在存储目录创建一个文件(txt/html/png),每次剪贴板内容更新就会再新建一个。
PS: 文件名包含当前 UTC 时间,精确到毫秒, 比如 copyq_20220308085614369.txt

将存储目录选定为一个同步目录,比如我用的坚果云,直接同步到多台机器上。然后多台机器都配置相同的同步目录就行了。

PS:默认的那个标签名是 &Clipboard,用来同步剪切板。可以选中任意一个标签同步剪贴板。

其他

主题我用 wine,感觉看着还行。
solarized-dark 主体会导致一些复制的代码看不清。

#766 国产操作系统

2022-03-02
  • 麒麟

  • 银河麒麟 Kylin,国防科技大学研发 kylinos.cn
    据说早些年基于 FreeBSD,后来改成 Linux
    2019 年,银河麒麟(天津麒麟信息技术有限公司)收购中标麒麟(中标软件有限公司),改名麒麟软件有限公司,是中国电子旗下子公司。
    中标麒麟主打民用,银河麒麟主打军用。
    星光麒麟:嵌入式 OS
    开放麒麟(OpenKylin):社区

  • 中标麒麟 NeoKylin
  • 优麒麟,Ubuntu Kylin(Ubuntu 中国定制版)
    工信部软件与集成电路促进中心、国防科技大学
    搞了个自己的界面,叫 UKUI
    不知道什么时候开始,也变成了麒麟软件的项目

    优麒麟是由麒麟软件有限公司主导开发的全球开源项目,专注于研发“友好易用,简单轻松”的桌面环境,致力为全球用户带来更智能的用户体验,成为 Linux 开源桌面操作系统新领航!

  • 湖南麒麟信安 Kylinsec

  • 普华,中国电科(军工)旗下企业

  • 统信

  • 深度,开始是基于 Ubuntu,后来基于 Debian,现在是完全使用 Linux Kernel 和开源组件独立构建,“建设中国桌面操作系统根社区”。

  • UOS,分成 A 版和 E 版,分别基于阿里龙蜥(Anolis)和华为欧拉(Eular)。据说还有一个 D 版,基于 Debian,但是官网没有看到。

  • 红旗

  • 2019 年,中科红旗chinaredflag.cn)被大连五甲万京信息科技产业集团收购

  • 发现有一家公司叫做北京红旗软件redflag-os.com),也是做红旗操作系统,不知道和中科红旗有什么关系
  • 中科红旗向北京红旗软件道歉
    北京红旗软件有限公司与中科红旗(北京)信息科技有限公司一审民事判决书

  • 中科方德

  • 中兴新支点
  • 起点 startos.com

  • 华为 鸿蒙

  • 华为 欧拉
  • 阿里 龙蜥
  • 腾讯 CloudLinux

  • LingmoOS(Cutefish)

  • RT-Thread RTOS

#765 转载:一道正确率只有15%的命名返回值和闭包的问题

2022-03-01

哈喽,大家好,我是asong。今天新注册了twitter,在里面没事瞎逛的时候,发现了一道有意思的题,他是由Redhat的首席工程师、Prometheus开源项目维护者 Bartłomiej Płotka 发出的,经调查显示,这道题的正确率只有15.2%,惨目忍睹,接下来我们就一起来看一下这道题~
原文地址:https://twitter.com/bwplotka/status/1495002204163678211

题目:下面这段代码输出结果是多少?

func aaa() (done func(), err error) {
    return func() { print("aaa: done") }, nil
}

func bbb() (done func(), _ error) {
    done, err := aaa()
    return func() { print("bbb: surprise!"); done() }, err
}

func main() {
    done, _ := bbb()
    done()
}
  • A. aaa: done
  • B. bbb: surprise!aaa: done
  • C. 永远不会结束
  • D. 编译错误

解析

答案:C 永远不会结束

这道题考查的点就是命名返回值 + 闭包,把上面的代码换成等效的匿名返回值代码你就明白了:

func aaa() (func(), error) {
    var done func()
    done = func() {
        print("aaa: done")
    }
    return done, nil
}

func bbb() (func(), error) {
    var done func()
    done, err := aaa()
    done = func() {
        print("bbb: surprise!");
        done()
    }
    return done, err
}

func main() {
    done, _ := bbb()
    done()
}

这其实是 Go 语言设计上一个 feature,当 Go 语言的返回值赋给我们特殊的"返回参数"时,如果它们被命名了,在 return 之后,我们可以在函数主体完成后的任何执行过程中引用那些带有这些名称的值,在 defer 或闭包中一样。

我们在说回这道题,在 bbb() 函数内我们使用了命名返回值 done func(), _ error,使用短变量声明 done, err := aaa() 接收 aaa() 的返回值,这里变量 done 并不是一个新变量,这就要说到Go语言的短变量声明的语法糖了,在多变量声明中,如果其中一个变量是新的,可以使用 := 声明,编译器会进行类型推断和赋值,已经声明的变量不会重新声明,直接在原变量上赋值;之后我们return的是一个闭包函数,闭包里的done值并不会被提前解析,在bbb()函数结束后,实际对应的代码就成了这样,变成了递归。

done = func() {
    print("bbb: surprise!");
    done()
}

如果我们把代码在改成这样:

func bbb() (func(), error) {
    var done func()
    done, err := aaa()
    return func() {
        print("bbb: surprise!");
        done()
    }, err
}

答案就是【B】:bbb: surprise!aaa: done

总结

一道看似简单的题,其中蕴涵的知识点确有很多,这就说明了解设计原理是多么的重要,Go语言资深工程师的路上任重道远呀~。

#764 转载:怎么会无法招聘到优秀程序员?

2022-02-27

阮一峰的文章摘自英文博客 Hiring Developers: How to avoid the best

今天的招聘市场上,每个人都想招到优秀程序员。但是,有一些因素,会让你错失他们,只招到没有其他地方可去的程序员。

  1. 缓慢的招聘流程

优秀工程师非常抢手,一出现在市场上就会被抢走。只要你把招聘流程放慢,每个阶段都要1到2周才给出回复,他们就会中途被其它公司雇佣。

  1. 面试一些他们平时不做的事情

面试时,你问一些他们根本不拿手的、其它领域的问题,比如让前端程序员回答如何调试 MySQL 的性能。优秀程序员会对这种毫无意义的面试感到沮丧,从而自己放弃。

另一种情况是,你让他们把一个需要好多天工作的技术难题,带回家解答。优秀程序员根本不会有时间,用来解这种题目。技术难题耗费的时间越长、越复杂,优秀程序员越会直接拒绝这样做。

  1. 只在本地面试

你撒的网越小,发现宝物的机会也越小。如果限制只在一个小城市招聘,几乎就不会有机会遇到优秀程序员。尤其是还不提供远程工作。

另外,要求所有申请人自费到公司进行30分钟的面试,而你的公司又在很偏僻的地方,也很难招到优秀程序员。因为如果申请人愿意自费长途跋涉,这表明他们迫切需要一份工作。如果你不提供搬家费,但是申请人愿意从另一个城市搬到公司所在地,这也不是一个好的迹象。只有真正绝望的程序员才会自费搬家,为一家不知名的小公司工作。

  1. 毫无激情的工作内容

优秀程序员对自己所做的事情,或者对于技术有很大的热情。他们通常把空闲时间都花在技术社区的活动,或者钻研 IT 技术和开源项目。如果你的工作把他们的时间占满,同时工作内容又无法让他们感到自豪或产生激情,优秀程序员就会产生反感。

  1. 少付报酬

优秀程序员理应得到丰厚的报酬,要是你付给他们工资低于竞争对手,股权也很少,或者要在公司待很长时间才能得到股权,如果他们足够聪明,就会意识到为这点报酬,要待这么长时间是不值得的。

上面这些因素,保证让你雇佣不到优秀的程序员。但是,如果你把每一个因素都反过来做,那么很有可能雇佣到他们。

  • 快速招聘,迅速做出决定。
  • 重视程序员的专长。
  • 珍惜并尊重申请人的时间。
  • 提供远程工作和搬迁费用。
  • 不占用下班时间,为他们留出生活和业余爱好的空间。
  • 支付公平的薪水,并让他们有涨薪的期望。