大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

chrome_debug

发表于 2020-07-09 更新于 2020-12-02 分类于 frontend

调色盘

针对标签的 css 颜色,可以使用调色盘调色 chrome-debug_调色盘

调试伪类样式

当我们想调试 dom 伪类样式 chrome-debug_操作设置.png

也可以直接在 elements 中代码片段上右键使用force state

chrome-debug_force_state.png
chrome-debug_force_state.png

查看 css 加载顺序

查看 css 选择器的加载顺序,查看哪些声明被覆盖了 chrome-debug_css查看.png

选择 dom 节点自动定位源代码

鼠标点击某个 dom 节点时,elements 中自动高亮对应的代码片段,

chrome-debug_自动定位源代码.png
chrome-debug_自动定位源代码.png

搜索

  1. 根据选择器,xpath,字符串在 elements 中搜索源代码,快捷键一般是command + f chrome-debug_搜索源代码.png
  2. 全局搜索,搜索打开的所有文件,仅支持文本搜索但支持正则,快捷键一般是command + shitf +f
  3. 在 dev 界面,搜索所有文件名,一般快捷键是command +p

展开全部代码

在 elements 界面 dom 节点的小箭头上按住alt在点击,会展开所有子节点

查看资源在哪里被加载的

network 面板中某个资源的initiator

chrome-debug_initiator.png
chrome-debug_initiator.png

console

在 console 控制台使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 监听方法调用,当方法被调用时会打印出方法的请求参数
monitor(func);

var dom = document.getElementById("id");
//监听dom对象事件,任何有关该对象的事件都会被打印
monitorEvents(dom);
// 可仅监听某些事件
monitorEvents(dom, ["click"]);

//上次变量的值
$_;

//最近在elements中选择过的dom节点
$0;
$1;
$2;
$3;
$4;

//根据xpath选择dom节点
$x;

//获取dom节点的所有事件,例如最近的一个dom节点
getEventListeners($0);

断点

断点可设置 condition,也可执行方法

chrome-debug_设置断点.png
chrome-debug_设置断点.png

在 console 控制台,在执行指定方法时直接进入 debug 模式

1
debug(func)

其他小技巧

  1. 刷新按钮可长按,选择重新加载的方式

freeswitch

发表于 2020-07-04 更新于 2020-12-02 分类于 ivr

官方文档

安装 freeswtich

在 centos7 上安装,参考官方安装文档

1
2
3
4
5
6
7
8
9
10
11
12
13

# 可能需要安装的前置依赖
yum install -y subversion autoconf automake libtool gcc-c++
yum install -y ncurses-devel make libtiff-devel libjpeg-devel

yum install -y https://files.freeswitch.org/repo/yum/centos-release/freeswitch-release-repo-0-1.noarch.rpm epel-release
yum install -y freeswitch-config-vanilla
# 语言和语音可按需安装
yum search freeswitch-lang-
yum search freeswitch-sounds-
# 根据需要安装
# yum install - y freeswitch-lang-*
# yum install - y freeswitch-sounds-*

源码编译

默认安装有很多模块不会安装,我们通过源码安装,加载自己所需要的模块,源码可从官方链接下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 添加freeswtich镜像
yum install -y https://files.freeswitch.org/repo/yum/centos-release/freeswitch-release-repo-0-1.noarch.rpm epel-release
# 安装freeswitch的依赖库,有些依赖可能默认镜像上没有,可以使用阿里云镜像
yum-builddep -y freeswitch
yum install -y yum-plugin-ovl centos-release-scl rpmdevtools yum-utils git
# centos7默认的gcc版本过低,安装一个高版本的gcc 实际情况可能devtoolset-4-gcc系列,可以安装更高版本的例如devtoolset-7-gcc
yum install -y devtoolset-4-gcc*
# 使用高版本gcc环境
scl enable devtoolset-4 'bash'

./bootstrap.sh -j
./configure --enable-portable-binary \
--prefix=/usr --localstatedir=/var --sysconfdir=/etc \
--with-gnu-ld --with-python --with-erlang --with-openssl \
--enable-core-odbc-support --enable-zrtp

make
make -j install

#下面两个是下载8000 16000 32000 48000hz的语音文件,并且将其解压拷贝至/usr/share/freeswitch/sounds/目录下

#分别下载的是freeswitch-sounds-en-us-callie和freeswitch-sounds-music
make -j cd-sounds-install
make -j cd-moh-install

默认安装的是英文语言文件,其他语言链接

以下是部分语音文件的下载地址,下载后我们将其拷贝至 freeswitch 的安装目录的 sounds 下即可

  1. freeswitch-sounds-en-us-callie-8000-1.0.51.tar
  2. freeswitch-sounds-music-8000-1.0.52.tar.gz

新增模块

修改在源码目录下 build/modules.conf.in 文件,将需要安装的模块的#去掉

1
2
3
formats/mod_sndfile
formats/mod_shout <--- NEW
#languages/mod_perl

执行

1
./configure && make install

源码编译一些问题

1
2
3
4
# 禁用libvpx
./configure --disable-libvpx
#或者在libs/libvpx下手动编译
./configure --enable-pic --enable-static --enable-shared --as=yasm --target=generic-gnu && make clean && make

最新版本将 sofia-sip 依赖从 freeswitch 的依赖树中移除了,先按照 1.6 版本

启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 以系统服务启动freeswitch
systemctl enable freeswitch
systemctl start freeswitch

# 直接启动
# -nc 后台启动
# -nonat 关闭uPnP
freeswitch -nc -nonat

# 查看freeswitch进程
ps -ef|grep freeswitch
# 查看相关端口是否被占用,默认使用的是5060端口
# -p 能直接得到freeswitch的进程号(需root权限)
netstat -anp|grep 5060


# 进入freeswtich
# 如果报错,说明没有启动成功,尝试使用 root 启动
fs_cli -rRS

安装一个 sip 软电话

telephone mac,也可安装其他软件

安装完成后配置账号,FreeSWITCH 默认配置了 1000 ~ 1019 共 20 个用户,你可以随便选择一个用户进行配置:

1
2
3
4
5
Display Name: 1000
User name: 1000
Password: 1234
Authorization user name: 1000
Domain: freeswitch的IP地址(默认使用5060端口)

密码默认为 1234,可在 var.xml 中更改

入门测试

freeswitch 默认测试号码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
------------------
号码 | 说明
----------------------
9664 | 保持音乐
9196 | echo,回音测试
9195 | echo,回音测试,延迟5秒
9197 | milliwatte extension,铃音生成
9198 | TGML 铃音生成示例
5000 | 示例IVR
4000 | 听取语音信箱
33xx | 电话会议,48K(其中xx可为00-99,下同)
32xx | 电话会议,32K
31xx | 电话会议,16K
30xx | 电话会议,8K
2000-2002 | 呼叫组
1000-1019 | 默认分机号

表一: 默认号码及说明

freeswitch cli 简介

参数

  1. -x 执行一条命令后退出 fs_cli -x "version" > FreeSWITCH Version 1.10.3-release.5~64bit (-release.5 64bit)

连接其他服务器

在用户根目录下编辑配置文件.fs_cli_conf

1
2
3
4
5
[server1]
host => 192.168.0.1
port => 8081
password => password
debug => 7

配置好后即可使用fs_cli sever1连接

客户端特殊命令

fs_cli 中,有几个特殊命令,以/开头,这些命令并不直接发送到服务端,而由fs_cli直接处理

例如 fs_cli 中,我们将日志级别设置到合适的级别

1
2
3
4
5
6
7
8
9
10
11
freeswitch@CentOS7> /help
Command Description
-----------------------------------------------
/help Help
/exit, /quit, /bye, ... Exit the program.
/event, /noevents, /nixevent Event commands.
/log, /nolog Log commands.
/uuid Filter logs for a single call uuid
/filter Filter commands.
/logfilter Filter Log for a single string.
/debug [0-7] Set debug level.

快捷键

在fs_cli中,我们可以使用F1-F12的快捷键,快捷键的功能定义在配置文件autoload_configs/switch.conf.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<cli-keybindings>
<key name="1" value="help"/>
<key name="2" value="status"/>
<key name="3" value="show channels"/>
<key name="4" value="show calls"/>
<key name="5" value="sofia status"/>
<key name="6" value="reloadxml"/>
<key name="7" value="console loglevel 0"/>
<key name="8" value="console loglevel 7"/>
<key name="9" value="sofia status profile internal"/>
<key name="10" value="sofia profile internal siptrace on"/>
<key name="11" value="sofia profile internal siptrace off"/>
<key name="12" value="version"/>
</cli-keybindings>

配置简介

我安装的 freeswitch 的配置文件在/etc/freeswitch目录下

我们可以在 fs_cli 中通过命令eval $${variable} 查看变量的值

1
2
fs_cli> eval $${log_dir}
/usr/local/freeswitch/log

这里是一些场景的变量

  • hostname
  • local_ip_v4
  • local_mask_v4
  • local_ip_v6
  • switch_serial
  • base_dir
  • recordings_dir
  • sound_prefix
  • sounds_dir
  • conf_dir
  • log_dir 日志目录
  • run_dir
  • db_dir sqllite 数据文件目录
  • mod_dir
  • htdocs_dir
  • script_dir
  • temp_dir
  • grammar_dir
  • certs_dir
  • storage_dir
  • cache_dir
  • core_uuid
  • zrtp_enabled
  • nat_public_addr
  • nat_private_addr
  • nat_type

配置文件概述

1
2
3
4
5
6
7
8
9
文件                               |    说明
---------------------------------------------------
freeswitch.xml | 核心配置文件,整合所有配置文件
vars.xml | 全局变量
dialplan/default.xml | 缺省的拨号计划
directory/default/*.xml | SIP用户,每用户一个文件
sip_profiles/internal.xml | 一个SIP profile,或称作一个SIP-UA,监听在本地IP及端口5060,一般供内网用户使用
sip_profiles/externa.xml | 另一个SIP-UA,用作外部连接,端口5080
autoload_configs/modules.conf.xml | 配置当FreeSWITCH启动时自动装载哪些模块
  • 配置文件中X-PRE_PROCESS标签,是 FreeSwitch 特有的,它称为预处理指令,用于设置一些变量和引入其他配置文件,在 XML 加载阶段,FreeSwitch 的 XML 解析器会将所有预处理命令展开.
  • 在 FreeSwitch 内部生成一个大的 XML 文档。log/freeswitch.xml.fsxml是 FreeSwitch 内部 XML 的一个内存镜像,它对调试非常有用,可以了解运行中的 FreeSwitch 的配置。
  • X-PRE_PROCESS是一个预处理指令,是 FreeSwitch 在加载阶段只针对其文本内容进行简单替换,而不是在解析 xml 阶段进行替换,因此注释X-PRE_PROCESS指令时,遇到嵌套的注释时会产生错误的 XML
  • 通过X-PRE_PROCESS设置的变量都称为全局变量,在加载 vars.xml 之前,FreeSwitch 已经设置了一些全局变量。在 xml 中使用$${var}引用全局变量,${var}引用局部变量。可使用global_getvar查看全局变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fs_cli> global_getvar
    hostname=CentOS7
    local_ip_v4=10.211.55.6
    local_mask_v4=255.255.255.0
    local_ip_v6=fdb2:2c26:f4e4:0:4436:96d:47c1:1aa9
    base_dir=/usr
    recordings_dir=/var/lib/freeswitch/recordings
    sounds_dir=/usr/share/freeswitch/sounds
    conf_dir=/etc/freeswitch
    log_dir=/var/log/freeswitch
    run_dir=/var/run/freeswitch
    db_dir=/var/lib/freeswitch/db
    mod_dir=/usr/lib64/freeswitch/mod
    htdocs_dir=/usr/share/freeswitch/htdocs
    ...

全局变量在预处理阶段(系统启动时或 reloadxml 时)被求值。局部变量在每次执行时都求值。

媒体编码相关配置

在 vars.xml 中配置支持的媒体编码类型

1
2
<X-PRE-PROCESS cmd="set" data="global_codec_prefs=OPUS,G722,PCMU,PCMA,H264,VP8"/>
<X-PRE-PROCESS cmd="set" data="outbound_codec_prefs=OPUS,G722,PCMU,PCMA,H264,VP8"/>
1
2
3
4
5
freeswitch@CentOS7> sofia status profile internal
=============================================================
# 节选部分
CODECS IN OPUS,G722,PCMU,PCMA,H264,VP8
CODECS OUT OPUS,G722,PCMU,PCMA,H264,VP8

用户目录

用户目录默认配置文件在conf/directory下

IVR 相关配置

默认的语言文件存放在sounds目录下,配置文件中用于定义语言文件的具体路径

1
<X-PRE-PROCESS cmd="set" data="sound_prefix=$${sounds_dir}/en/us/callie"/>

也可以在 Dialplan 中针对每一个 Channel 进行改变

1
<variable name="sound_prefix" value="$${sounds_dir}/en/us/callie"/>

另外该变量也可以设置到用户目录中,当特定的用户拨打电话时就能使用该变量。

重新加载配置文件

fs_cli> reloadxml

基础知识

呼叫字符串

呼叫字符串的格式类型/参数/参数,其中第一部分是字符串的类型。一般来说,每种 Endpoint 都会提供相应的呼叫字符串,每种呼叫字符串的类型都属于一个 Endpoint Interface,其中一些类型又类似一种‘高级’的呼叫字符串,如 user 和 group,属于底层的 sofia Endpoint Interface

1
2
3
# 从本地的注册用户中查找该用户的联系地址
user/1000
group/1000

我们可以使用逗号(,)或者竖线符合(|)将多个呼叫字符串隔开。

  • 同振 哪个先接听则接通哪个,另一路自动挂断

    1
    freeswitch> originate user/1000,user/10001 &echo
  • 顺振 第一个号码呼叫失败则呼叫第二个

    1
    freeswitch> originate user/1000|user/10001 &echo

呼叫字符串可以携带通道变量

1
2
#更改主叫名为li,主叫号为888888
freeswitch> originate {origination_caller_id_name=li,origination_caller_id_number=888888}user/1001 &park

Channel

对于每一次呼叫,FreeSwitch 都会启动一个 Session,用于控制整个呼叫,他会一直持续到通话结束。其中,每个 Session 都控制这一个 Channel(通道,又称信道),是一对 UA 间通信的实体,相当于 FreeSwitch 的一条腿。每个 channel 都用一个唯一的 UUID 来标示,称为 channel UUID。。Channel 上可以绑定一些呼叫参数,称为 Channel Variable(通道变量)。Channel 也可用来传输媒体流。通话时 FreeSwitch 的作用是将两个 Channel 桥接(bridge)到一起,使双方可以通话。这两路桥接的通话(两条腿)在逻辑上组成一个通话,称为一个 Call 一个通道有两端,常用的端

  • park 挂起
  • hold 挂起并播放提示音
  • record 录音
  • playback 播放提示音
  • bridge 桥接其他用户

桥接

bridge 相当于一座桥,它的作用是将两条腿 1000 和 1001 给桥接起来,在这里,为了能连接到 1001,FreeSwitch 作为一个 SIP UAC,向 1001 这个 SIP UA(UAS)发起一个 INVITE 请求,并建立一个新 Channel,就是我们的 b-leg。1001 开始振铃,bridge 把回铃音传回给 1000,因此 1000 就能够提到回铃音。当然实际的情况可能更复杂,因为在呼叫之前,FreeSwitch 首先要查找 1001 这个用户是否已经注册,否则,会直接返回 USER_NOT_REGISTERED,而不会建立 b-leg。 当 1001 振铃后,可能出现以下情况

  • 被叫应答
  • 被叫忙
  • 被叫无应答
  • 被叫拒绝
  • 其他情况

我们考虑以下被叫应答的情况,1001 接电话,这个时候 bridge 一直是阻塞的,也就是说,bridge 这个 APP 一直等待 b-leg(10001)挂机(或者其他错误)后才返回,这时才有可能继续执行下面的 Action。最后,无论哪一方挂机,bridge 就算结束了。 如果 1000(主叫)先挂机,则 FreeSwitch 将挂机原因(Hangup Cause,一般是 NORMAL_RELEASE)发送给 1001,同时释放 b-leg。由于 a-leg 已经没了,Dialplan 就再也没有往下执行的必要。 如果 1001(被叫)先挂机,b-leg 就消失了。但 a-leg 还存在,b-leg 会将挂机原因传到 a-leg,在 a-leg 决定是否继续往下执行。

如果由于种种原因 1001 可能没有接电话,如 1001 可能拒绝(返回 CALL_REJECTED),忙(USER_BUSY),无应答(NO_ANSWER 或 NO_USER_RESPONSE)等,出现这些情况,FreeSwitch 认为这是不成功的 bridge,因此就不管 hangup_after_bridge 变量,这时它会检查 continue_on_fail 的值决定是否执行下面的 Action。continue_on_fail 可能的值有:

  • NORMAL_TEMPORARY_FAILURE 临时故障
  • USER_BUSY 超时
  • NO_ANSWER 无应答
  • NO_ROUTE_DESTINATION 呼叫不可达
  • USER_BUSY 用户忙

更多的值参考SIP 挂机原因 也可以给 continue_on_fail 设置为 true,表示无论什么原因导致 bridge 失败,都执行接下来的 Action

网关

网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。使用在不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。与网桥只是简单地传达信息不同,网关对收到的信息要重新打包,以适应目的系统的需求。

其他

  • RTP 实时传输协议
  • ringback 回铃音,电话在 answer 之前播放的提示音
  • transfer_ringback 转接电话时的回铃音
  • sip capture FreeSwitch 内置了 Homer Capture Agent 用于 SIP 抓包
  • NAT 网络地址转换协议,NAT 转换是将私有地址转换成公有地址的一种方式,是将 内网 ip:内网端口与外网 ip:外网端口映射起来。
  • STUN(Session Traversal Utilities for NAT,NAT 会话穿越应用程序)是一种网络协议,它允许位于 NAT(或多重 NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的 NAT 之后以及 NAT 为某一个本地端口所绑定的 Internet 端端口。这些信息被用来在两个同时处于 NAT 路由器之后的主机之间建立 UDP 通信
  • PSTN ( Public Switched Telephone Network )定义:公共交换电话网络,一种常用旧式电话系统。即我们日常生活中常用的电话网。工作原理 公共交换电话网络是一种全球语音通信电路交换网络,包括商业的和政府拥有的。
  • Early Media 呼叫话机或软电话时,对方回复 180 或 183 SIP 指令时,通常会返回 Early Media,也就是前期的振铃音或彩铃,在呼叫失败时也可能会返回 Early Media
  • PBX 交换机
  • MRCP 是交换机与 ASR 或 TTS 引擎传输数据的一种协议。V2 版本使用 SIP 进行会话的初始化,传输媒体流数据使用的是 RTP 协议
  • ACD automatic call distribution 自动电话分配

常用命令

sofia

  1. sofia status profile internal 查看某个 Profile 的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    freeswitch@CentOS7> sofia status profile internal
    =================================================================================================
    Name internal
    Domain Name N/A
    Auto-NAT false
    DBName sofia_reg_internal
    Pres Hosts 10.211.55.6,10.211.55.6
    Dialplan XML
    Context public
    Challenge Realm auto_from
    RTP-IP 10.211.55.6
    SIP-IP 10.211.55.6
    Ext-SIP-IP 114.87.232.243
    URL sip:mod_sofia@114.87.232.243:5060
    BIND-URL sip:mod_sofia@114.87.232.243:5060;maddr=10.211.55.6;transport=udp,tcp
    WS-BIND-URL sip:mod_sofia@10.211.55.6:5066;transport=ws
    WSS-BIND-URL sips:mod_sofia@10.211.55.6:7443;transport=wss
    HOLD-MUSIC local_stream://moh
    OUTBOUND-PROXY N/A
    CODECS IN OPUS,G722,PCMU,PCMA,H264,VP8
    CODECS OUT OPUS,G722,PCMU,PCMA,H264,VP8
    TEL-EVENT 101
    DTMF-MODE rfc2833
    CNG 13
    SESSION-TO 0
    MAX-DIALOG 0
    NOMEDIA false
    LATE-NEG true
    PROXY-MEDIA false
    ZRTP-PASSTHRU true
    AGGRESSIVENAT false
    CALLS-IN 0
    FAILED-CALLS-IN 0
    CALLS-OUT 0
    FAILED-CALLS-OUT 0
    REGISTRATIONS 2
  2. sofia status profile internal reg查看注册用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Call-ID:    03frJ9rXlraWYhLWGiu-PzxSW.9VWb6S
    User: 1000@10.211.55.6
    Contact: "1000" <sip:1000@10.211.55.2:57146;ob>
    Agent: Telephone 1.4.6
    Status: Registered(UDP)(unknown) EXP(2020-07-17 08:04:09) EXPSECS(337)
    Ping-Status:Reachable
    Ping-Time: 0.00
    Host: CentOS7
    IP: 10.211.55.2
    Port: 57146
    Auth-User: 1000
    Auth-Realm: 10.211.55.6
    MWI-Account:1000@10.211.55.6

    Contact地址可得知用户 1000 的 sip 地址:sip:1000@10.211.55.2,当我们使用 originate 命令呼叫user/1000这个呼叫字符串时,FreeSWITCH 便会在用户目录中查找 1000 这个用户,找到他的 dial-string 参数,dial-string 通常包含 alice 实际 Contact 地址的查找方法

  3. sofia status gateway gw1 列出网关状态
  4. profile 操作 下面这些指令隐含 reloadxml
    • sofia profile internal start
    • sofia profile internal stop
    • sofia profile internal restart
    • sofia profile internal rescan 重新扫描参数,不影响通话
    • sofia profile external killgw gw1 删除一个网关,rescan 后重新加载网关
    • sofia profile external register gw1 立即注册
    • sofia profile external unregister gw1 立即注销
  5. sofial profile internal siptrace on 开启该 profile 的 SIP 跟踪功能抓取 SIP 报文
  6. sofial global siptrace on 开启全局 的 SIP 跟踪功能抓取 SIP 报文

debug 相关

sofia 日志类型是 console,会打印到控制台

  • sofia loglevel all 9 开启最低级别的日志
  • sofia loglevel nua 9 开启指定模块最低级别的日志

可以指定一个日志类型,

  • sofia tracelevel debug 开启日志输出到 trace 上

originate

用于 FreeSwitch 向外发起一个呼叫,originate 在收到媒体指令就返回。

1
2
freeswitch> originate
-USAGE: <call_url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]
  • call_url 呼叫字符串(Dial String)
  • exten 可以认为是一个分机号,在向外发起一个呼叫并等待对方接听后,建立一个 Channel,对方接听后,FreeSwitch 会转入 Dialplan 去路由,路由要查找的目的地就是 exten。
  • &application app_args 直接使用内部 APP
  • dialplan 它是 Dialplan 的类型,如果不设置,默认就是 XML
  • cid_name 显示的主机名称
  • cid_num 显示的主机号码
  • timeout_sec 接收 INVITE 后不回复 100 Tring 消息的超时的秒数,一般来说这种情况是 IP 地址不可达

示例

  1. originate 发起呼叫

    1
    2
    3
    4
    5
    6
    freeswitch> originate user/1000 &echo
    #和上一条命令等价,省略了Dialplan类型和Context
    #完整语句为freeswitch> originate user/1000 9196 XML public
    freeswitch> originate user/1000 9196
    #内联命令 和上一条命令等价
    freeswitch> originate user/1000 echo inline
  2. originate 修改主叫名称,主叫号码

    1
    freeswitch> originate user/1000 &echo XML default 'leaderli' '888888'

    上述命令产生的 INVITE 命令报文如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INVITE sip:46598071@10.211.55.2:51032 SIP/2.0
    Via: SIP/2.0/UDP 10.211.55.6;rport;branch=z9hG4bKZHy7Uavj84veD
    Max-Forwards: 70
    From: "leaderli" <sip:888888@10.211.55.6>;tag=6NUtt7DvQF96N
    To: <sip:46598071@10.211.55.2:51032>
    Call-ID: 3bc3ac68-6cb4-1239-6db5-001c426cb7b3
    CSeq: 25244563 INVITE
    Contact: <sip:mod_sofia@10.211.55.6:5060>
    User-Agent: FreeSWITCH-mod_sofia/1.10.3-release.5~64bit
    Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
  3. originate INVITE 超时

    我们呼叫一个不存在的 IP 地址

    1
    freeswitch> originate  sofia/internal/1000@192.168.0.1 &echo XML default 'hello' 111111111 10

    开启 siptrace 的部分日志,在 FreeSwitch 发出 INVITE 请求后,由于没有收到 100 Trying 回复,于是在 1 秒,2 秒,4 秒后重发,由于我们指定了 10 秒超时,因此该呼叫于 10 秒后失败,返回 NO_ANSWER

    1
    2
    3
    4
    5
    send 1328 bytes to udp/[192.168.0.1]:5060 at 10:44:13.598228:
    send 1328 bytes to udp/[192.168.0.1]:5060 at 10:44:14.598478:
    send 1328 bytes to udp/[192.168.0.1]:5060 at 10:44:16.599835:
    send 1328 bytes to udp/[192.168.0.1]:5060 at 10:44:20.599839:
    -ERR NO_ANSWER
  4. bridge 桥接(先桥接,后呼叫)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    freeswitch> originate user/1000 &bridge(user/10001)
    我们也可以使用另一种方式来建立他们之间的连接(先呼叫,后桥接)

    freeswitch> originate user/1000 &park
    freeswitch> originate user/1001 &park
    freeswitch> show channels
    freeswitch> uuid_bridge <1000_uuid> <1001_uuid>

    # 忽略早期媒体,在用户真正接听后才对他进行放音
    originate {ignore_early_media=true}sofia/gateway/gw/13800000000 &playback(/tmp/test.wav)
    # 桥接b-leg时,对于183指令a-leg可以听到b的回玲音,而b-leg的180消息,FreeSwitch无法向 a-leg发送180,只能在收到b-leg的180指令时,播放一个假的回铃音。有时候等待b-leg返回180 Ringing也需要很长时间,可使用instant_ringback立即播放回铃音
    originate {transfer_ringback=/tmp/ring.wav,instant_ringback=true}user/1000 &bridge(user/1001)

    #修改b-leg的主叫号码
    originate user/1000 &bridge({origination_caller_id_number=88888}user/1001)
    # b-leg的参数来源于a-leg,b-leg的主叫号码来源于a-leg的effective_caller_id_number
    originate {effective_caller_id_number=888888}user/1000 &bridge(user/1001)

originate 可以使用通道变量

多个变量使用,分隔,当有的通道变量值会有逗号,可以使用\,来转义,或者使用^^:将,替换为: 也可以使用多个大括号来

1
2
3
originate {origination_caller_id_name='leaderli',origination_caller_id_number=888888}user/1001 &echo
originate {absolute_codec_string=G729\,PCMU}user/1001 &echo
originate {absolute_codec_string=^^:G729:PCMU}user/1001 &echo

IVR 相关

录音

  • record 单腿录音,对一个 channel 进行路由,录音为单声道,是阻塞的 API 命令
  • uuid_record 两腿录音,一通正常的通话通常由两个 Channel 组成,在一个 Channel 中,语音有两个方向。对于 SIP 客户端或话机而言,两个方向分别为说和听,对于 FreeSwitch 而言,则分别为读和写。

    1
    2
    3
    4
    5
    # 开始录音
    uuid_record <channel_uuid> start /tmp/record.wav
    # 结束录音,该录音会包含两个声道,playback无法直接播放双声道,会将该录音混音,变成一个声道后播放。
    # 录音过程中电话挂断了,所有录音也就自动停止了
    uuid_record <channel_uuid> stop /tmp/record.wav
  • record_session 在 Dialplan 中使用 record_session 开始对当前的 Channel 进行录音。它和 uuid_record 一样是非阻塞的,都是通过为当前的 Channel 添加一个 Media Bug,因而可以实时录音。

录音过程可以设置各种参数

  • RECORD_STEREO 是否录成立体音,默认 false
  • RECORD_WRITE_ONLY 只录写方向,即 FreeSwitch 发出,对端能够听到的声音
  • RECORD_READ_ONLY 只录读方向,即 FreeSwitch 能听到的声音

录音时不指定录音的文件格式,则会使用原生的格式进行录音,即 Channel 默认支持的 codec 格式。

放音

1
<action application="playback" data="/tmp/test.wav"/>

playback 的参数是一些音频源,这些音频源大部分是由 Format 定义的,即文件接口。这些文件接口一般会有打开(Open)和关闭(Close)、读(Read)和写(Write)等属性,playback 会从文件中读,而录音实际上也使用这些文件接口,它会往文件中写。 这些文件接口有以下几类

  • 声音文件,对大部分声音文件的支持都是在 mod_sndfile 模块中实现的。该模块直接调用了 libsndfile 库,因此 libsndfile 库所支持的文件 FreeSwitch 都能支持。典型的如 WAV,AU,AIFF,VOX 等,libsndfile 不支持的声音文件则由其他模块实现的。如 mod_shout 模块实现了对 MP3 文件的支持。也可以直接播放由 mod_native_file 模块支持的原生文件--只要在播放时不带扩展名,FreeSwitch 会自己查找与本 Channel 语音编码一致的文件。

    1
    2
    <!-- 如果Channel使用的是PCMU编码,则播放test.PCMU-->
    <action application="playback" data="/tmp/test"/>
  • local_stream 是在 mod_local_stream 中实现的。该模块实现了一些 Stream,即’流‘。他与文件类似,不同的是,每个流在整个系统中只有一个实例,但可以同时被多个 Channel 读取。这样,当系统中有成千上万个 Channel 时,便能节省很多系统资源。

    1
    <action application="playback" data="local_stream://moh"/>

    上述代码中的流的名字是在conf/autoload_configs/local_stream.conf.xml中定义的

    1
    2
    3
    4
    5
    6
    7
    8
    <!--path 流文件路径 -->
    <directory name="moh/8000" path="$${sounds_dir}/music/8000">
    <param name="rate" value="8000"/>
    <param name="shuffle" value="true"/>
    <param name="channels" value="1"/>
    <param name="interval" value="20"/>
    <param name="timer-name" value="soft"/>
    </directory>

    FreeSwitch 支持多种采样频率,如果当前 Channel 为 8000Hz,名字local_stream://moh便自动对应到moh/8000这个流

  • silence_stream 静音流,与 sleep 不同的时,静音流不中断 RTP 流程的传输。

    1
    2
    <!-- 1000 静音时长毫秒数 1400 舒适噪声的参数,参数越小噪音越大-->
    <action application="playback" data="silence_stream://1000,1400"/>
  • tone_stream 铃流,它是在 mod_tone_stream  模块中实现的。他可以使用 TGML语言生成各种信号音。
  • file_string 由 mod_dptools 模块中实现。它相当于一种更高级的文件格式,可以将多个文件串联起来

    1
    <action application="playback" data="file_string:///tmp/file1.wav!/tmp/file2.wav!/tmp/file3.wav"/>

    !作为多个文件的分隔符

    1
    2
    3
    4
    5
    <!-- 修改多个文件的分隔符-->
    <action application="set" data="playback_delimiter=|"/>
    <!-- 修改多个文件的播放间隔毫秒数-->
    <action application="set" data="playback_sleep_val=500"/>
    <action application="playback" data="file_string:///tmp/file1.wav|/tmp/file2.wav|/tmp/file3.wav"/>
  • vlc 由模块 mod_vlc 实现的一个文件接口,vlc 是一个跨平台的多媒体播放器。

    1
    2
    3
    4
    5
    <action application="playback" data="vlc:///tmp/test.mp3"/>
    <!-- 播放视频中的音频部分 -->
    <action application="playback" data="vlc:///tmp/test.mp4"/>
    <!-- 播放http服务器上的文件 -->
    <action application="playback" data="vlc://http://localhost:/test.mp4"/>
  • http 由 mod_httapi 模块直接实现的一个 HTTP 接口,与 vlc 不同的是,它可以将远程文件缓存到本地

    1
    <action application="playback" data="http://localhost:/test.mp3"/>
  • say TTS 功能

    1
    <action application="playback" data="say:tts_commandline:Ting-Ting:欢迎使用FreeSwitch"/>

    或

    1
    2
    3
    <action application="set" data="tts_engine=tts_commandline"/>
    <action application="set" data="tts_voice=Ting-Ting"/>
    <action application="playback" data="say:欢迎使用FreeSwitch"/>

    也可以使用 speak 实现同样的功能

    1
    <action application="speak" data="tts_commandline|Ting-Ting|欢迎使用FreeSwitch"/>

循环播放

1
2
3

<action application="endless_playback" data="/tmp/test.wav">
<action application="loop_playback" data="+3 /tmp/test.wav">

TTS

mod_flite 是 FreeSwitch 基于 Flite 语音合成引擎的一个 TTS 模块。目前该模块仅支持英文。首先,我们需要在 FreeSwitch 源码目录中编译安装该模块。然后在 FreeSwitch 中加载该 TTS 模块。 然后我们就可以直接使用了

1
2
# flite 引擎名称 kal 嗓音  目前仅支持 kal,awb,rms,slt
freeswitch> originate user/1001 &speak('flite|kal|welcome to FreeSwitch')
1
2
3
4
5
6
<action application="speak" data="flite|kal|welcome to FreeSwtich"/>

<!-- tts的参数可以通过通道变量指定 -->
<action application="set" data="tts_engine=flite"/>
<action application="set" data="tts_voice=kal"/>
<action application="speak" data="welcome to FreeSwtich"/>

TTS

使用录音替换 tts 播报

使用Say

1
2
<!-- en表示语种 number 表示我们要播放的数据的类型 iterated表示播放的方式,这里指代数字要逐个读出 1234表示要读的内容 -->
<action application="say" data="en number iterated 1234">

使用 macro

我们定义一个宏

1
2
3
4
5
6
7
8
9
<include>
<macro name="USER_BUSY">
<input pattern="(.*)">
<match>
<action function="speak-text" data="分机 $1 正在通话,请稍后再拨"/>
</match>
</input>
</marco>
</include>

我们在 Dialplan 中就可以使用,这样我们就可以在用户忙的时候播放相应的提示音

1
<action application="playback" data="phrase:${originate_disposition}:$1"/>

mrcp

mod_unimrcp 是在 UniMRCP 基础上在 FreeSwitch 中实现的一个模块。它同时支持 TTS 和 ASR。如果使用 unimrcp,在 Dialplan 中的 tts 引擎写法是unimrcp:server,server 是 mrcp 服务器的配置项的名称

配置项内容如下

主配置文件在autoload_configs/modules.conf.xml,各个 mrcp 服务器的配置在 mrcp_profiles 目录下

1
2
3
4
5
6
7
8
9
10
11
12
<configuration name="unimrcp.conf" description="UniMRCP Client">
<settings>
<param name="default-tts-profile" value="voxeo-prophecy8.0-mrcp1"/>
<param name="default-asr-profile" value="voxeo-prophecy8.0-mrcp1"/>
<param name="log-level" value="DEBUG"/>
<param name="max-connection-count" value="100"/>
<param name="offer-new-connection" value="1"/>
</settings>
<profiles>
<X-PRE-PROCESS cmd="include" data="../mrcp_profiles/*.xml"/>
</profiles>
</configuration>

其中一个 profile 的示例,其服务器的配置项名称为 mrcpserver01,不同 mrcp 服务需要的报文格式可能有所不同,具体参考对应 mrcp 服务相关的文档。修改 mrcp 的配置需要重启服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<include>
<profile name="mrcpserver01" version="1">
<param name="server-ip" value="10.10.5.1"/>
<param name="server-port" value="554"/>
<param name="resource-location" value=""/>
<param name="speechsynth" value="synthesizer"/>
<param name="speechrecog" value="recognizer"/>
<param name="rtp-ip" value="10.10.5.2"/>
<param name="rtp-port-min" value="4000"/>
<param name="rtp-port-max" value="5000"/>
<param name="codecs" value="PCMU PCMA L16/96/8000"/>
<synthparams>
</synthparams>
<recogparams>
<param name="start-input-timers" value="false"/>
</recogparams>
</profile>
</include>

示例

1
2
3

# nana tts嗓音
originate user/1000 &speak(unimrcp:mrcpserver01|nana|hello freeswitch)

使用脚本

1
<action application="lua" data="/tmp/xxx.lua"/>

其它常用命令

  1. show 显示运行时的各种信息

    • codec
    • endpoint
    • application 显示所有 app 命令
    • api 显示所有 api 命令
    • dialplan
    • file
    • timer
    • calls
    • channels 当前通道的通话
    • calls
    • detailed_calls
    • bridged_calls
    • detailed_bridged_calls
    • aliases
    • complete
    • chat
    • management
    • modules 显示加载的模块
    • nat_map
    • say
    • interfaces
    • interface_types
    • tasks
    • limits
    • status
  2. <uuid> <read|write|both|vread|vwrite|vboth|all> <on|off> 调试媒体传输是否正常
  3. bgapi 使用后台进程启动命令,配合其他阻塞的命令使用

架构

freeSWTICH 由一个稳定的核心(Core)及一些外围模块组成。FreeSwitch 内部使用线程模型来处理并发请求,每个连接都在单独的线程中进行处理,不同的线程间通过 Mutex 互斥访问共享资源,并通过消息和异步事件等方式进行通信。FreeSwitch 的核心非常短小精悍,绝大部分应用层的功能都在外围的模块中实现。外围模块可以动态加载(以及卸载)。外围模块与核心模块通过核心提供的 Public API 与核心进行通信,而核心通过回调(或称钩子)机制执行外围模块中的代码。

freeswitch_架构.png
freeswitch_架构.png

数据库

FreeSwitch 使用一个核心的数据库(默认的存放位置是/usr/local/freeswitch/db/core.db)来记录系统的接口(interfaces),任务(tasks)以及当前的通道(channels),通话(calls)等实时数据。某些模块,如 mod_sofia,有自己的数据库(表),一般情况下,这些模块提供相关的 API 用于从这些表里查询数据。

公共应用程序接口

FreeSwitch 在核心层实现了一些 Public API,这些 Public API 可以被外围的模块调用。包含一些通用的工具函数,如生成 JSON 格式的函数,RTP 等与呼叫相关。

接口

FreeSwitch 提供了很多抽象接口,这些接口对同类型的逻辑或功能实体进行了抽象,但没有具体实现。核心层通过回调(钩子)方式调用具体的实现代码或函数。

例如 FreeSwitch 核心层定义了以下接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef enum {
SWITCH_ENDPOINT_INTERFACE,
SWITCH_TIMER_INTERFACE,
SWITCH_DIALPLAN_INTERFACE,
SWITCH_CODEC_INTERFACE,
SWITCH_APPLICATION_INTERFACE,
SWITCH_API_INTERFACE,
SWITCH_FILE_INTERFACE,
SWITCH_SPEECH_INTERFACE,
SWITCH_DIRECTORY_INTERFACE,
SWITCH_CHAT_INTERFACE,
SWITCH_SAY_INTERFACE,
SWITCH_ASR_INTERFACE,
SWITCH_MANAGEMENT_INTERFACE,
SWITCH_LIMIT_INTERFACE,
SWITCH_CHAT_APPLICATION_INTERFACE,
SWITCH_JSON_API_INTERFACE,
SWITCH_DATABASE_INTERFACE,
} switch_module_interface_name_t;

外围模块可以选择并实现其中一个或多个接口,并向核心层注册这些接口,核心层在需要这些接口时,会回调这些接口中约定的回调函数。

事件

FreeSwitch 内部使用消息和事件机制进行进程间和模块间的通信。事件进制既可以在内部使用,也可在外部使用。事件机制是一种生产者-消费者模型,事件的产生和处理是异步的。这些事件可以在 FreeSwitch 内部通过绑定(Bind)一定的回调函数进行捕获,即 FreeSwitch 的核心事件系统会依次回调这些回调函数,完成相应的功能。另外,在嵌入式脚本中也可以订阅相关的事件进行处理。 在 FreeSwitch 外部,也可以通过 Event Socket 等接口订阅相关的事件,通过这种方式了解 FreeSwitch 内部发生了什么,如当前呼叫的状态等。fs_cli 就是一个典型的外呼程序,通过 Event Socket 与 FreeSwitch 通信,可以对 FreeSwitch 进行控制和管理,也可以订阅相关的事件对 FreeSwitch 的运行情况进行监控。订阅事件最简单的方法是:

1
2
3
4
5
6
# 订阅所有事件
fs_cli> /event plain ALL

# 单独订阅某类事件
fs_cli> /event plain CHANNERL_ANSWER
fs_cli> /event plain CUSTOM sofia:register

当我们使用登录软电话时,我们可以看到sofia:register信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
fs_cli> /event plain CUSTOM sofia::register
+OK event listener enabled plain

#登录后
fs_cli@CentOS7>
RECV EVENT
Event-Subclass: sofia::register
Event-Name: CUSTOM
Core-UUID: 11995eb7-bd00-492b-96c2-73a3256a383c
FreeSWITCH-Hostname: CentOS7
FreeSWITCH-Switchname: CentOS7
FreeSWITCH-IPv4: 10.211.55.6
FreeSWITCH-IPv6: fdb2:2c26:f4e4:0:4436:96d:47c1:1aa9
Event-Date-Local: 2020-07-17 15:03:21
Event-Date-GMT: Fri, 17 Jul 2020 07:03:21 GMT
Event-Date-Timestamp: 1594969401524081
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_handle_register_token
Event-Calling-Line-Number: 2007
Event-Sequence: 1161
profile-name: internal
from-user: 1000
from-host: 10.211.55.6
presence-hosts: 10.211.55.6,10.211.55.6
contact: "1000" <sip:26759801@10.211.55.2:63078>
call-id: nEgIVmMHRtKSzdh6yv04x2SB.YkEJB84
rpid: unknown
status: Registered(UDP)
expires: 600
to-user: 1000
to-host: 10.211.55.6
network-ip: 10.211.55.2
network-port: 63078
username: 1000
realm: 10.211.55.6
user-agent: sipsimple 3.0.0
sip_number_alias: 1000
sip_auth_username: 1000
sip_auth_realm: 10.211.55.6
number_alias: 1000
user_name: 1000
domain_name: 10.211.55.6
record_stereo: true
default_gateway: example.com
default_areacode: 918
transfer_fallback_extension: operator
toll_allow: domestic,international,local
accountcode: 1000
user_context: default
effective_caller_id_name: Extension 1000
effective_caller_id_number: 1000
outbound_caller_id_name: FreeSWITCH
outbound_caller_id_number: 0000000000
callgroup: techsupport

目录结构

目录 说明
bin 可执行程序
db 系统数据库(sqlite)
htdocs HTTP Server 根目录
lib 库文件
mod 可加载模块
run 运行目录,存放 FreeSwitch 运行时的 PID
sounds 声音文件,使用 playback()默认的寻找路径
grammar 语法,用于 ASR
include 头文件
log 日志,CDR 等
recordings 录音,使用 record()时默认的存放路径
scripts 嵌入式语言写的脚本,如 lua()等默认寻找的路径
storage 语音留言(Voicemail)的录音
conf 配置文件

变量

变量详情说明文档

通道变量列表 可通过如下命令查看变量值

1
2
freeswitch@lofa> eval ${variable_name}
$ fs_cli -x '${variable_name}'

在 Dialplan 中可以取消某些 Variable 的定义

1
2
<action application="set" data="var1=_undef_"/>
<action application="unset" data="var1"/>

可以截取字符串

1
2
3
4
5
6
${var}            #1234567890  原始变量的值
${var:0:1} #1 从第一个位置截取,取一个字符
${var:1} #234567890 从第二位置截取到尾
${var:-4} #7890 从倒数第四个位置截到尾
${var:-4:2} #78 从倒数第四个位置截两位
${var:4:2} #56 从第四个位置截两位

APP

Action 通常有两个属性,一个是 Application,代表要执行的 App,一个是 data,代表 App 的参数。

  1. set 将变量设置到当前的 Channel,即 a-leg hello=1 可以使用export_vars将变量同步到 b-leg

    1
    <action application="set" data="export_vars=var1,var2,var3"/>

    可以取消某些 Variable 的定义

    1
    2
    <action application="set" data="var1=_undef_"/>
    <action application="unset" data="var1"/>
  2. export 除了具备 set 的功能外,还会将变量设置到 b-leg,可以使用nolocal:修饰,表示仅设置到 b-leg hello=1 nolocal:hello=1
  3. hash 一个内存中的哈希表数据结构

    其 api 如下

    hash insert/realm/key/value hash insert_ifempty/realm/key/value hash delete/realm/key hash delete_ifmatch/realm/key/value hash select/realm/key

    realm 和 key 是随意命名的,可以将 realm 看作是一个 hash 结构的组合容器

    在 xml 配置中示例

    1
    2
    <action application="hash" data="delete_ifmatch/realm/key/value"/>
    <action application="set" data="var=${hash(select/realm/key)}"/>
  4. bind_meta_app 在 channel 上绑定 DTMF 按键, 语法

    1
    <action application="bind_meta_app" data="KEY LISTEN_TO FLAGS APPLICATION[::PARAMETERS]"/>

    参数解释:

    • KEY 监听的 DTMF 按键,仅允许为 0-9 中的一位*,#会被转换为 0
    • LISTEN_TO 监听的 leg,仅接受a,b,ab
    • FLAGS 标记行为

      • a - A leg
      • b - B leg
      • o - 相反的 leg
      • s - 同样的 leg
      • i - 立即执行,而不是在执行阶段去执行
      • 1 - 仅使用一次
    • APPLICATION 指定运行的 app
    • PARAMETERS app 需要的参数

    示例:

    1
    2
    3
    4
    <action application="bind_meta_app" data="1 b s execute_extension::dx XML features"/>
    <action application="bind_meta_app" data="2 b s record_session::$${recordings_dir}/${caller_id_number}.${strftime(%Y-%m-%d-%H-%M-%S)}.wav"/>
    <action application="bind_meta_app" data="3 b s execute_extension::cf XML features"/>
    <action application="bind_meta_app" data="4 b s execute_extension::att_xfer XML features"/>
  5. bridge 桥接,其参数为呼叫字符串

    1
    <action application="bridge" data="user/${dialed_extension}@${domain_name}"/>
  6. conference 会议,

    1
    <action application="conference" data="$1-${domain_name}@default"/>

    其中@前面的$1-${domain_name}是会议的名称,@后面的 default 表示是一个会议的 profile,它定义了这个会议的相关参数。其具体配置在conf/autoload_configs/conference.conf.xml中,

  7. execute_extension 临时执行指定的 extension

    如下示例,即是去 Dialplan 下的 features.xml 中执行名为dx的 extension

    1
    <action application="bind_meta_app" data="1 b s execute_extension::dx XML features"/>

    dx

    1
    2
    3
    4
    5
    6
    7
    8
    9
     <context name="features">
    <extension name="dx">
    <condition field="destination_number" expression="^dx$">
    <action application="answer"/>
    <action application="read" data="11 11 'tone_stream://%(10000,0,350,440)' digits 5000 #"/>
    <action application="execute_extension" data="is_transfer XML features"/>
    </condition>
    </extension>
    ...
  8. transfer,将当前通话重新转移到 ROUTING 阶段,重新去 Dialplan 中进行路由 语法为

    1
    transfer <destination_number> [<dialplan> [<context>]]
    • destination_number 转接的号码
    • dialplan 拨号计划,默认为XML,代表conf/dailplan/default.xml
    • context 拨号计划的 Context,默认为default

    示例

    1
    <action application="transfer" data="1001"/>

    还可以在 destination_number 前使用参数,表示那个 leg 会重新路由,默认是 a-leg,可接受的参数-aleg, -bleg,-both

  9. info info 级别日志

    1
    2
    3
    4
    5
    <extension name="show channel variable">
    <condition field="destination_number" expression="^9916$">
    <action application="info" data=""/>
    </condition>
    </extension>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    57da11b8 EXECUTE [depth=0] sofia/internal/1000@10.211.55.6 info()
    57da11b8 2020-07-18 07:23:43.214675 [INFO] mod_dptools.c:1885 CHANNEL_DATA:
    Channel-State: [CS_EXECUTE]
    Channel-Call-State: [RINGING]
    Channel-State-Number: [4]
    Channel-Name: [sofia/internal/1000@10.211.55.6]
    Unique-ID: [57da11b8-3197-45c4-bc31-bbe90935819e]
    Call-Direction: [inbound]
    Presence-Call-Direction: [inbound]
    Channel-HIT-Dialplan: [true]
    Channel-Presence-ID: [1000@10.211.55.6]
    Channel-Call-UUID: [57da11b8-3197-45c4-bc31-bbe90935819e]
    Answer-State: [ringing]
    Caller-Direction: [inbound]
    Caller-Logical-Direction: [inbound]
    Caller-Username: [1000]
    Caller-Dialplan: [XML]
    Caller-Caller-ID-Name: [1000]
    Caller-Caller-ID-Number: [1000]
    Caller-Orig-Caller-ID-Name: [1000]
    Caller-Orig-Caller-ID-Number: [1000]
    Caller-Network-Addr: [10.211.55.2]
    Caller-ANI: [1000]
    Caller-Destination-Number: [9916]
    Caller-Unique-ID: [57da11b8-3197-45c4-bc31-bbe90935819e]
    Caller-Source: [mod_sofia]
    Caller-Context: [default]
    ...

    info 也可以打印指定变量

    1
    <action application="info" data="INFO the destination is  ${destination_number}"/>

    info 打印的所有通道变量在 xml 中引用时,使用的名称和 info 打印出来的是不一致的,详见通道变量 wiki 参照表

  10. answer 应答一路呼叫。在 FreeSwitch 做被叫时,如果想主动给主叫放音,则必须应答后才可以。在 SIP 中是 200 消息,有些 APP 会隐含应答

  11. playback 用于给 channel 放音,其参数为录音地址,如果需要播放多个文件,可以串联操作

    1
    2
    3
    <action application="playback" data="/tmp/test1.wav"/>
    <action application="playback" data="/tmp/test2.wav"/>
    <action application="playback" data="/tmp/test3.wav"/>

    也可以使用file_string协议串联

    1
    <action application="playback" data="file_string:///tmp/test1.wav!/tmp/test2.wav!/tmp/test3.wav"/>

    !是文件名的分隔符,可使用 Chan Var 来重新指定,也可指定多个文件的播放间隔

    1
    2
    <action application="set" data="playback_delimiter=|"/>
    <action application="set" data="playback_sleep_val=500"/>

    通过 mod_shout 模块,也可以支持本地或远程 HTTP 服务

    1
    <action application="playback" data="http://localhost/test3.wav"/>

    在播放过程中我们可以通过设定 Chan Var 使用按键停止播放,也可以使用none设定为不可停止,其语法为

    1
    playback_terminators=123456789*0# | any | none

    默认的打断按键是*

  12. sleep 用于设置可以等待或暂停的一段时间,单位默认是毫秒

    1
    <action application="sleep" data="1000"/>
  13. ring_ready 用于在 SIP 中回复 180 消息,即通知对方可以振铃了

    1
    <action application="ring_ready" data="1000"/>
  14. pre_anser 用于在 SIP 中回复 183 消息,后续的 playback 之类的动作将作为早期媒体(Early Media)给对方发过去,如彩铃音

    1
    2
    <action application="pre_anser" />
    <action application="playback" data="music.wav"/>

    虽然 FreeSwitch 可以将媒体发给对方,但如果在一定时间内(通常 60 秒)没有应答,对端通常也会挂断该通话。

  15. read 用于实现播放声音并且等待接收 DTMF 按键,它的格式是:

    1
    <min> <max> <sound_file> <variable_name> <timeout> <terminators>
    • min:最少收号位数
    • max:最大收号位数
    • sound_file:要播放的录音文件
    • variable_name:收到用户按键后保存在哪个变量中
    • timeout:等待每一位输入的超时毫秒数
    • terminators:收号小于 min 位时,按该按键可以提前结束,通常是#

    例如

    1
    <action application="read" data="3 4 '/tmp/input-id-card.wav' digits 30000 #"/>
  16. play_and_get_digits 类似 read,但支持更多特性

    1
    <min> <max> <tries>  <timeout> <terminators> <sound_file> <invalid_file> <variable_name> <regex>
    • min:最少收号位数
    • max:最大收号位数
    • tries: 重试次数
    • timeout:收集全部位数的超时
    • terminators:收号小于 min 位时,按该按键可以提前结束,通常是#
    • sound_file:要播放的录音文件
    • invalid_file:错误提示音
    • variable_name:收到用户按键后保存在哪个变量中
    • regex 输入的正则表达式
    • digit_timeout(可选):位间间隔
    • failure_ext(可选):最后错误转到该 Extension
    • failure_db(可选):输入错误转到的 Dialplan
    • failure_context(可选):输入错误时转到的 Dialplan Context

    例如

    1
    <action application="play_and_get_digits" data="3 4 3 10000 #  '/tmp/input-id-card.wav' '/tmp/invalid.wav' id_card_num  (^\d{15}$)"/>

API

在 Dialplan 中一般执行的是 APP,在某些特殊情况下,Dialplan 也需要调用某些 API 提供的能力,这可以通过类似变了引用的方式来实现,如:${status()}

1
2
3
4
<action application="set" data="api_status=${status()}"/>
<action application="set" data="api_status=${version()}"/>
<action application="set" data="api_status=${strtime()}"/>
<action application="set" data="api_status=${expr(1+1)}"/>

最后的 expr 类似 unix 中的 expr 命令

拨号计划

拨号计划(Dialplan)是 FreeSwitch 中至关重要的一部分,它的主要作用就是对电话进行路由(从这一点上来说,相当于一个路由表),决定和影响通话的流程。路由查找和执行分别属于一路通话的不同阶段,当 channel 状态进入执行阶段后,才开始依次执行所有的 Action。

拨号计划的配置文件在conf/dailplan目录下,拨号计划由多个 Context 组成,每个 Context 中有多个 Extension。一个 Context 中的 Extension 与其他 Context 中的 Extension 在逻辑上是隔离的。在 Extension 中可以对一些 condition 进行判断,如果满足条件所指定的表达式,则执行对应的 Action。Action 通常有两个属性,一个是 Application,代表要执行的 App,一个是 data,代表 App 的参数。

Dialplan 的按顺序执行,为了避免与提供的例子冲突,建议将自己写的 Extension 放在最前面。 默认情况下一旦有 Extension 满足匹配规则,就不会再去查找其他的 Extension。我们可以使用参数continue="true"来继续执行其他 Extension

1
2
3
4
5
<extension name="tod_example" continue="true">
<condition wday="2-6" hour="9-18">
<action application="set" data="open=true"/>
</condition>
</extension>

系统默认提供的配置文件包括三个 Context,分别是 default,features,public。default 是默认的 Dialplan,一般来说注册用户都可以通过它来打电话,如拨打其他分机和外部电话等。而 public 一般用户接受外来呼叫。

根据日志查看拨号详情

将日志级别调整为 DEBUG,拨打 9196 测试号码,截取整理部分日志

1
2
3
4
5
6
7
8
9
10
11
1 mod_dialplan_xml.c:637 Processing 1000 <1000>->9196 in context default
2 parsing [default->unloop] continue=false
3 Regex (PASS) [unloop] ${unroll_loops}(true) =~ /^true$/ break=on-false
4 Regex (FAIL) [unloop] ${sip_looped_call}() =~ /^true$/ break=on-false
5 parsing [default->tod_example] continue=true
...
44 Regex (FAIL) [global] ${default_password}(10086) =~ /^1234$/ break=never
45 Regex (PASS) [echo] destination_number(9196) =~ /^9196$/ break=on-false
46 Action answer()
47 Action echo()
48 State Change CS_ROUTING -> CS_EXECUTE
  1. 第一行:Processing 说明是在处理 Dialplan,其中 1000,是 sip 客户端软件注册的用户名
  2. 第二行,呼叫进入 parsing(解析 XML)阶段,它首先根据呼叫的来源找到 XML 中的一个 Context,此处是 default。它找到的第一个 Extension 的 name 是 unloop。 其实际 xml 配置如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <extension name="unloop">
    <condition field="${unroll_loops}" expression="^true$"/>
    <condition field="${sip_looped_call}" expression="^true$">
    <action application="deflect" data="${destination_number}"/>
    </condition>
    </extension>
    <extension name="echo">
    <condition field="destination_number" expression="^9196$">
    <action application="answer"/>
    <action application="echo"/>
    </condition>
    </extension>
  3. 第三行,由于此处 Extension 有一个 Condition,判断变量 unroll_loops 是否为 true,条件满足
  4. 第四行,由于此处 Extension 有一个 Condition,判断变量 sip_looped_call 是否为 true,条件不满足,不执行 Action
  5. 第五行,执行下一个 name 为 tod_example 的 Extension
  6. 第 45 行,判断被叫号(destination_number)是否满足 9196,条件满足,执行 answer 和 echo。answer 是一个 FreeSwitch 的 App,用于回复 200 OK 的 sip 信令。
  7. 第 48 行,说明 FreeSwitch 进入执行阶段。

condition

condition 使用正则表达式匹配测试一个变量是否满足预设的正则表达式。可测试的变量如 | 变量| 说明| | :---- | :---- | |context|Dialplan 当前的 Context| |rdnis|被转移的号码| |destination_number|被叫号码| |dialplan|Dialplan 模块的名称| |caller_id_name|主叫名称| |caller_id_number|主叫号码| |ani|主叫的自动号码识别| |aniii|主叫类型,如投币电话| |uuid|本 Channel 的唯一标示| |source|呼叫源,来自 FreeSwitch 的哪一个模块| |chan_name|channel 的名称| |network_addr|主叫的 IP 地址| |year|当前的年| |yday|一年中的第几天 1~366| |mon|月 1 ~ 12| |mday|日 1 ~ 31| |week|一年中的第几周 1 ~ 53| |mweek|本月的第几周 1 ~ 6| |wday|一周的第一天 1 ~ 7 周日代表 1| |hour|小时 0 ~ 23| |minute|分钟 0 ~ 59| |minute-of-day|一天中的第几分钟 1 ~ 1440|

除此之外,还可以使用用户目录设置的变量。但需要使用${}引用

condition 只要满足即执行其中的 action,否则执行其中的 anti-action。

condition 不可以嵌套,但可以迭加,通过属性 break 的值,我们可以确定是否继续执行接下来的 condition

break 的值的含义(假设我们有两个 condition,分别为 A 和 B):

  • on-false(默认值) 在第一次匹配失败时停止,即当 A 为 false 时,直接完成当前 Extension
  • on-true 在第一次匹配成功时停止,即当 A 为 false 时,才会去测试 B
  • always 不管是否匹配都停止
  • never 不管是否匹配都继续

Dialplan 工作机制

channel 的状态机

graph LR
    NEW       --> INIT
    INIT      --> ROUTING
    ROUTING  --> EXECUTE
    EXECUTE   --> HANGUP
    EXECUTE   -.->|transfer|ROUTING
    HANGUP    --> REPORTING
    REPORTING --> DESTORY

当新建(NEW)一个 Channel 时,它首先会进行初始化(INIT),然后进入路由(ROUTING)阶段,也就是我们查找解析 Dialplan 的阶段。我们称为 Parsing 或 Hunting(传统交换机称为选线,这里我们称为选路),解析完毕后会得到一些 Action,然后进入执行(EXECUTE)阶段,依次执行所有的动作(Action),最后无论哪一方挂机,都会进入(HANGUP)阶段。后面的报告(REPORTING)阶段一般用于进行统计,计费等。最后将 Channel 销毁(DESTORY),释放系统资源。 在 EXECUTE 状态,可能会发生转移(Transfer,非呼叫转移),它可以转移到其他的 extension,此时会重新进入 ROUTING 阶段,重新 Hunting Dialplan。

Extension 中的赋值的临时变量一般情况下在执行(EXECUTE)才会真正去执行,所以在路由(ROUTING)阶段进行解析判断是无法取到赋值后的临时变量的值,我们可以在使用inline="true"来使 action 直接执行

1
<action inline="true" application="set" data="greeting=hello.wav"/>

并不是所有 action 都支持 inline,支持的有

  • check_acl
  • eval
  • event
  • export
  • enum
  • log
  • presence
  • set
  • set_global
  • lcr
  • set_profile_var
  • set_user
  • sleep
  • unset
  • nibblebill
  • verbose_events
  • cidlookup
  • curl
  • easyroute
  • odbc_quer

inline 会打乱执行顺序,inline 的 action 会先于所有非 inline 的 action 先执行

inline Dailplan

短小,简介的方便在脚本中动态生成的 Dialplan,与 XML Dialplan 不同,它没有 Extension,也没有复杂的 Condition,只是简单叠加 Action,它的语法格式:

1
app:arg1,app2:arg2,app3:arg3

当我们拨打 9916 时,9916 这个测试号,他的 XML Dialplan 的 Action 依次为 answer,playback,record

1
originate user/1000 9916

我们可以使用inline Dialplan语法直接进行

1
originate user/1000 answer,playback:/tmp/hello.wav,record:/tmp/record.wav inline

App 的参数有可能有都好,可以使用m语法将分隔符临时指定为其他

1
'm:^:app1:arg1^app2:arg2^bridge:{origination_uuid=asdf,ignore_early_media_true}/sofia/gateway/mygw/1234' inline

其他 Dialplan

1
2
3
4
5
6
freeswitch@CentOS7> show dialplan
type,name,ikey
dialplan,XML,mod_dialplan_xml
dialplan,enum,mod_enum
dialplan,inline,mod_dptools
dialplan,signalwire,mod_signalwire

呼叫队列

使用 mod_fifo 模块实现了一些简单的 ACD 功能

FreeSwitch 默认的 Dialplan 中提供了使用这种方式进行电话分配的例子。

当我们拨打 5900,就可以将电话停在一个泊位(5900@${domain_name})上,并播报 hold_music

1
2
3
4
5
6
<extension name="park">
<condition field="destination_number" expression="^5900$">
<action application="set" data="fifo_music=$${hold_music}"/>
<action application="fifo" data="5900@${domain_name} in"/>
</condition>
</extension>

使用命令fifo list显示当前队列的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fs_cli> fifo list
<fifo_report>
<fifo name="cool_fifo@10.211.55.6" consumer_count="0" caller_count="0" waiting_count="0" importance="0" outbound_per_cycle="1" outbound_per_cycle_min="1" ring_timeout="60" default_lag="30" outbound_priority="5" outbound_strategy="ringall">
<outbound></outbound>
<callers></callers>
<consumers></consumers>
<bridges></bridges>
</fifo>
<fifo name="manual_calls" consumer_count="0" caller_count="0" waiting_count="0" importance="0" outbound_per_cycle="0" outbound_per_cycle_min="0" ring_timeout="0" default_lag="0" outbound_priority="5" outbound_strategy="ringall">
<outbound></outbound>
<callers></callers>
<consumers></consumers>
<bridges></bridges>
</fifo>
<fifo name="5900@10.211.55.6" consumer_count="0" caller_count="1" waiting_count="1" importance="0" outbound_per_cycle="0" outbound_per_cycle_min="0" ring_timeout="0" default_lag="0" outbound_priority="5" outbound_strategy="ringall">
<outbound></outbound>
<callers>
<caller uuid="acd3434e-5759-403d-96e9-42668930bb1a" status="WAITING" caller_id_name="centos7" caller_id_number="1001" timestamp="2020-11-13 10:35:49" position="1" slot="0"></caller>
</callers>
<consumers></consumers>
<bridges></bridges>
</fifo>
</fifo_report>

播报 5901,将来电从队列中取出来,nowait,它表示如果队列中没有电话在等待,坐席端的电话就没必要在这里等待了。如果使用wait(默认值为 wait),或不加参数,那么坐席端就会在这里继续等待,知道队列里来了一路通话,它便可以立即得到服务。

使用 nowait 的坐席又被称为 onhook(挂机)坐席,使用 wait 的坐席被称为 offhook(摘机)坐席。摘机坐席省去了呼叫坐席的时间,因而能够更迅速的为客户提供服务。

1
2
3
4
5
6
<extension name="unpark">
<condition field="destination_number" expression="^5901$">
<action application="answer"/>
<action application="fifo" data="5900@${domain_name} out nowait"/>
</condition>
</extension>

签入与迁出

book 为 fifo 的队列名,添加成功后可以通过fifo list查看名为 book 的队列。

当来电停在 book 队列上时,FreeSwitch 会轮询呼叫 book 队列签入的坐席。

1
2
fs_cli> fifo_member add book user/1007
fs_cli> fifo_member del book user/1007
1
<action application="set" data="result=${fifo_member(add book user/1001)}">

呼叫队列相关通道变量

  • fifo_priority 呼叫优先级,默认为 5,共有 10 个优先级,高优先级的将被排在队列前面。
  • fifo_bridge_uuid

在 fifo 应用中,通话中的两条腿都是独立建立的,只有这样才能进行桥接。因而,在普通的通话中靠 a-leg 上的变量去影响 b-leg 的做法在这里就不适用了。那么,为了在 b-leg 上设置我们想要的通道变量。可以在想 b-leg 发起呼叫的呼叫字符串上设置。例如

将主叫号改为 7777

1
<member>{origination_call_id_name=7777}</member>

呼叫队列相关事件

通过事件消息,有一个 FIFO-Action 消息头事件标志了事件实际的动作,一通来话从入队到挂机,大致经过以下几个事件。

  1. push 来话入队时产生
  2. pre-dial 呼叫坐席之前产生
  3. post-dial 呼叫坐席之后产生
  4. consumer_start 消费者(即坐席)开始
  5. caller_pop 主叫出队
  6. consumer_pop 消费者出队
  7. bridge-consumer-start 消费者 Channel 开始 bridge
  8. bridge-caller-start 主叫 Channel 开始 bridge
  9. bridge-consumer-stop 消费者 Channel 停止 bridge
  10. bridge-caller-stop 主叫 Channel 停止 bridge
  11. consumer_stop 消费者停止
  12. Channel-consumer-start 消费者 Channel 开始
  13. Channel-consumer-stop 消费者 Channel 停止

呼叫中心模块

mod_callcenter

会议

mod_conference 提供了一个 conference APP,因此我们可以直接在 Dialplan 中使用它

下面是 FreeSwitch 提供的默认的 Dialplan

1
2
3
4
5
6
 <extension name="nb_conferences">
<condition field="destination_number" expression="^(30\d{2})$">
<action application="answer"/>
<action application="conference" data="$1-${domain_name}@default"/>
</condition>
</extension>

用户可以呼叫 3000~3099 之间的号码进入该 Dialplan,电话进入该 Dialplan,首先执行 answer 动作对来话应答(可以省略),然后就可以执行到 conference 进入会议了。

conference 的语法规则<conference_name>@<profile>+<pin>+flags{<flag1>|<flag2>}

例如

  • 3000: 3000 是一个会议名称
  • 3000+1234:3000 是会议名称,使用默认的 Profile default ,1234 是密码。所有成员在进入会议时都会提示输入密码。
  • 3000@default+1234: 同上,只是明确指定了 Profile
  • 3000@default+1234+flags{ute|waste}:在上面的基础上,又增加了 mute 和 waste 参数。
  • 3000@default+flags{endcomf|moderator}:与上面的类似,只是参数换成了 endconf 和 moderator

FreeSwitch 也提供了一个 conference API 命令用于对会议进行各种控制

相关数据索引

  1. 挂机原因表
  2. 通道变量

问题

拨打电话无声音

观察日志输出发现,可在 freeswitch 控制台或/var/log/freeswitch/freeswitch.log中查看

1
Error Opening File [/usr/share/freeswitch/sounds/en/us/callie/ivr/ivr-to_call_the_freeswitch_conference.wav] [System error : No such file or directory.]

缺少相关语音包,我们可以安装对应的语音即可。

1
yum install -y freeswitch-sounds-en-us-callie-8000.noarch

freeswitch 反应迟钝

测试中发现呼叫请求服务器处理的特别慢,后来跟踪发现在/etc/freeswitch/dialplan/default.xml 中有个 sleep 10s 的处理,

1
2
3
4
5
6
7
<condition field="${default_password}" expression="^1234$" break="never">
        <action application="log" data="CRIT WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING "/>
        <action application="log" data="CRIT Open $${conf_dir}/vars.xml and change the default_password."/>
        <action application="log" data="CRIT Once changed type 'reloadxml' at the console."/>
        <action application="log" data="CRIT WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING "/>
        <action application="sleep" data="10000"/>
</condition>

我们可以注释掉这个sleep,或者修改默认密码,/etc/freeswitch/,编译 vars.xml,把默认的密码 1234 改成其他。

1
<X-PRE-PROCESS cmd="set" data="default_password=10086"/>

客户端拨打电话后 30s 挂断

因为 NAT 的关系,我们将 internal.xml 中的配置修改为如下,使其可以穿透 NAT

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- external_sip_ip
Used as the public IP address for SDP.
Can be an one of:
ip address - "12.34.56.78"
a stun server lookup - "stun:stun.server.com"
a DNS name - "host:host.server.com"
auto - Use guessed ip.
auto-nat - Use ip learned from NAT-PMP or UPNP
-->
<!-- <param name="ext-rtp-ip" value="$${external_rtp_ip}"/> -->
<!-- <param name="ext-sip-ip" value="$${external_sip_ip}"/> -->
<param name="ext-rtp-ip" value="auto-nat"/>
<param name="ext-sip-ip" value="auto-nat"/>

也可以使用 stun server 进行 NAT 穿透

centos

发表于 2020-07-04 更新于 2020-12-02 分类于 linux

安装 iso 镜像文件

虚拟机内 centos 挂载了 iso 的镜像后,使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#可以看到挂载的iso文件
$ blkid
/dev/sr0: UUID="2018-09-07-13-27-15-00" LABEL="Parallels Tools" TYPE="iso9660"
/dev/sda1: UUID="0ae18c31-cc28-4d90-bf6a-f65a96b2f57a" TYPE="xfs"
/dev/sda2: UUID="yaZCkD-Sek2-Xluo-rItp-Ed3V-vWuv-jOdAfy" TYPE="LVM2_member"
/dev/mapper/centos-root: UUID="3e9bbf71-0636-4505-b38f-7b88784faaab" TYPE="xfs"
/dev/mapper/centos-swap: UUID="8362ec6f-0e9a-4baa-8187-ea0d434de878" TYPE="swap"

$ mount /dev/sr0 /media/iso/
mount: /dev/sr0 is write-protected, mounting read-only
$ ls /media/iso/
install install-gui installer kmods tools version

#可以开始安装了,安装过程中需要下载依赖,需要保持网络链接正常
$ ./media/iso/install

上述安装过程可能会失败,可能相关依赖无法自动安装成功,根据提示信息,将缺失的包依次安装后再次安装即可。

虚拟机无法链接网络

会自动获取 IP

1
2
#需要以root用户执行
$ dhclient -v

yum

yum 禁用 fastestmirror

1
2
3
$ sudo vi /etc/yum/pluginconf.d/fastestmirror.conf
#将`enabled=1`更改为`enabled=0`后
$ sudo yum clean all

yum 安装依赖

使用 yum-builddep 提前安装某个库的依赖库

1
2
# 安装freeswitch的依赖库
yum-builddep -y freeswitch

yum 清理无用依赖

1
package-cleanup

清理未完成的事务

安装过程失败或中断等,使用 yum-complete-transaction 清理未完成事务,需要 yum-utils 支持

1
2
3
yum install yum-utils
yum clean all
yum-complete-transaction --cleanup-only

node模块

发表于 2020-07-04 更新于 2020-12-02 分类于 nodejs

后台运行库

pm2 是一个进程管理工具,可以用它来管理你的 node 进程,并查看 node 进程的状态,当然也支持性能监控,进程守护,负载均衡等功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 全局安装
npm install -g pm2
# 启动进程/应用
pm2 start app.js
# 重命名进程/应用
pm2 start app.js --name wb123
# 添加进程/应用 watch
pm2 start bin/www --watch
# 结束进程/应用
pm2 stop www
# 结束所有进程/应用
pm2 stop all
# 删除进程/应用
pm2 delete www
# 删除所有进程/应用
pm2 delete all
# 列出所有进程/应用
pm2 list
# 查看某个进程/应用具体情况
pm2 describe www
# 查看进程/应用的资源消耗情况
pm2 monit
# 查看pm2的日志
pm2 logs
# 若要查看某个进程/应用的日志,使用
pm2 logs www
# 重新启动进程/应用
pm2 restart www
# 重新启动所有进程/应用
pm2 restart all

镜像源管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ npm install -g nrm

# *表示当前正在使用的源
$ nrm ls
* npm -------- https://registry.npmjs.org/
yarn ------- https://registry.yarnpkg.com/
cnpm ------- http://r.cnpmjs.org/
taobao ----- https://registry.npm.taobao.org/
nj --------- https://registry.nodejitsu.com/
npmMirror -- https://skimdb.npmjs.com/registry/
edunpm ----- http://registry.enpmjs.org/

# 新增源
$ nrm add verdaccio http://centos7:4873
# 切换源
$ nrm use verdaccio

测试源响应速度

1
2
3
4
5
6
7
8
9
10
11
12
$ nrm test taobao
taobao - 407ms
# 测试所有
nrm test
npm ---- 336ms
yarn --- 334ms
cnpm --- 810ms
taobao - 545ms
nj ----- Fetch Error
npmMirror 794ms
edunpm - Fetch Error
* verdaccio 41ms

搭建私人仓库

  1. 安装 verdaccio,使用 npm 全局安装即可。

    1
    npm install –global verdaccio
  2. 安装完成后,直接输入 verdaccio 命令即可运行

    1
    2
    3
    4
    5
    6
    $ verdaccio
    warn --- config file - /home/li/.config/verdaccio/config.yaml
    warn --- Verdaccio started
    warn --- Plugin successfully loaded: verdaccio-htpasswd
    warn --- Plugin successfully loaded: verdaccio-audit
    warn --- http address - http://localhost:4873/ - verdaccio/4.7.2

    config.yaml是 verdaccio 的默认配置文件,为了能让外部访问,我们在其中添加

    1
    listen: 0.0.0.0:4873

    我们使用 pm2 后台启动

    1
    pm2 start verdaccio
  3. 在自定义模块中,发布应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 链接私有仓库
    nrm add verdaccio http://centos7:4873
    # 切换源
    nrm use verdaccio
    # 注册用户
    npm adduser
    # 发布
    npm publish
    # 下载我们发布的应用
    npm install test

    发布不成功,尝试使用最简格式package.json

    例如 node模块_私有仓库.png

  4. verdaccio 存储 nodejs 包的地址 ~/.local/share/verdaccio/storage

http

http post 请求

默认情况下请求报文格式为 jsonContent-type: application/json

1
2
3
4
5
6
7
8
9
10
11
var request = require('request')
request(
{
url: url,
method: "POST",
json: requestData,
},
function (error, response, body) {
...
}
);

fs

读取文件为 base64

1
2
3
4
const fs = require("fs");

let buff = fs.readFileSync("stack-abuse-logo.png");
let base64data = buff.toString("base64");

moment

时间格式化模块

1
npm install -S -D moment
1
2
3
import moment form 'moment'

moment().format('YYYY-MM-DD HH:mm:ss')

js-beautify

格式化 js、html、css 代码片段用的插件

express

拦截/请求,并打印请求报文

1
2
npm install --save express
npm install --save body-parser
1
2
3
4
5
6
7
8
9
10
11
12
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post("/", (req, res) => {
console.log(req.body);
res.send("ok");
});
app.listen(5000, () => {
console.log("start server at 5000");
});

child_process

调用 shell 命令

1
npm install --save child_process

exec 的回调函数在命令执行后才会返回。

1
2
3
4
5
6
7
8
9
const { exec } = require("child_process");
exec("cat *.js missing_file | wc -l", (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});

我们也可以通过on监听 shell 命令的管道来实时输出返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
const { exec } = require("child_process");
let tail = exec("tail -f 1.log");
//data为byte数组
tail.stdout.on("data", (data) => {
console.log(`${data}`);
});
tail.stderr.on("data", (data) => {
console.log(`${data}`);
});

tail.on("close", (code) => {
console.log(`子进程退出码:${code}`);
});

深入git

发表于 2020-06-28 更新于 2020-12-02 分类于 code

文件结构

当使用git init创建一个新的仓库时,Git 会创建一个.git目录。目录结构如下:

1
2
3
4
5
6
7
8
HEAD
config
description
hooks/
index
info/
objects/
refs/
  • HEAD 当前被检出分支的指针
  • config 项目特有的配置
  • description 描述文件
  • hooks 钩子脚本
  • index 保存暂存区信息,在首次git add后才会生成
  • info 包含一个全局性排除文件
  • objects 存储所有数据内容
  • refs 所有分支的提交对象的指针

Working Directory

除.git 目录的其他目录和文件,不包含.gitignore 排除的目录及文件

对象结构

Git 是一个内容寻址文件系统。其核心部分时一个简单的键值对数据库。你可以想 Git 仓库插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回内容。

Git 有数据对象(blob),树对象(tree),引用(refs)

blob

每个文件的数据内容以二进制的形式存储在 Git 仓库中,我们可以使用命令git hash-object -w <file>的方式,手动向 git 数据库中插入该数据(即在.git/objects目录下,写入该对象),而它只会返回存储在 Git 仓库的唯一键。此命令输出一个长度为 40 的 sha1 哈希值,前 2 位字符用于命名子目录,后 38 位则用作文件名 例如:

1
2
3
4
5
6
7
$ find .git/objects/ -type f
$ echo 'test content' |git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
$ find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

git 使用 sha1 时,在可区分唯一 sha1 时最少可只用前四位就可以

一开始.git/objects下没有存储任何数据对象,当我们使用git hash-object命令存入一条内容时,可以观察到.git.objects目录下,生成了 sha1 相对应的子目录和文件名。通过git cat-file -p <sha1>查看数据对象时,我们可以看到之前存入的文本内容

我们使用git add <file>时,就是 Git 内部做了git hash-object的命令,将<file>的内容存入到.git/objects中。

1
2
3
4
5
6
$ echo 'version 1' > 1.txt
$ git add .
$ find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
$ git cat-file -p 83baae

使用git add后,我们可以看到index文件的生成

1
2
3
4
5
6
7
8
$ find .git/ -type f
...
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/index
# 查看index内容
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt

我们可以看到 index 保存了工作区 add 的 blob 对象的 sha1

blob 对象是针对数据内容的,不区分文件

例如:

1
2
3
4
5
6
7
8
9
$ echo 'version 1' > 2.txt
$ git add 2.txt
$ find .git/objects/ -type f
# 可以看到blob对象并没有增加
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt

tree

Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。 所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。

当我们使用git commit后,我们可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

$ git commit -m 'commit 1'
[master (root-commit) 9f1224b] commit 1
2 files changed, 2 insertions(+)
create mode 100644 1.txt
create mode 100644 2.txt

# 查看.git目录
$ find .git/ -type
.git/refs/heads/master
...
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/1d/49be59d2805f145b66c15641a11e945a5d021a
.git/objects/9f/1224bca340fb2e4235adfa3e42297033c26ec9
.git/index
.git/COMMIT_EDITMSG
...

# 我们可以看到生成了两个新的对象
# tree
$ git cat-file -p 1d49be
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 1.txt
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 2.txt

# commit commit中包含了tree对象的sha1
$ git cat-file -p 9f1224
tree 1d49be59d2805f145b66c15641a11e945a5d021a
author leaderli <429243408@qq.com> 1593549136 +0800
committer leaderli <429243408@qq.com> 1593549136 +0800

commit 1

# 查看所有对象
$ git cat-file --batch-check --batch-all-objects
1d49be59d2805f145b66c15641a11e945a5d021a tree 66
83baae61804e65cc73a7201a7252750c76066a30 blob 10
9f1224bca340fb2e4235adfa3e42297033c26ec9 commit 163
d670460b4b4aece5915caf5c68d12f560a9fe3e4 blob 13

commit 工作原理

commit 对象指向的 tree 对象,遍历 tree 对象查找到的所有 blob 对象,与缓存区中的所有 blob 进行 diff 对比,若有差异,则可以进行下一次 commit。

refs

如果你对仓库中从一个提交(比如 9f1224)开始往前的历史感兴趣,那么可以运行 git log 9f1224 开始往前的历史感兴趣,那么可以运行这样的命令来显示历史,不过你需要记得 9f1224b 是你查看历史的起点提交。 如果我们有一个文件来保存 SHA-1 值,而该文件有一个简单的名字, 然后用这个名字指针来替代原始的 SHA-1 值的话会更加简单。

在 Git 中,这种简单的名字被称为“引用(references,或简写为 refs)”。 你可以在 .git/refs 目录下找到这类含有 SHA-1 值的文件。 在目前的项目中,这个目录没有包含任何文件,但它包含了一个简单的目录结构:

1
2
3
4
5
$ find .git/refs -type f
.git/refs/heads/master

$ more .git/refs/heads/master
9f1224bca340fb2e4235adfa3e42297033c26ec

HEAD 引用

HEAD 文件通常是一个符号引用(symbolic reference),指向目前所在的分支。 所谓符号引用,表示它是一个指向其他引用的指针。我们通常使用git checkout来更改 HEAD 的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ more .git/HEAD
ref: refs/heads/master

# 当我们切换分支后
$ git checkout dev
$ more .git/HEAD
ref: refs/heads/dev

# 当我们`git checkout <commit>`时,此时HEAD未指向一个分支,git会提醒我们去创建一个新的分支,否则会在切换分支时丢失掉这个HEAD
$ git checkout 9f1224b
Note: checking out '9f1224b'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 9f1224b commit 1

# 我们查看下现在的HEAD内容,其指向的不是分支的引用
$ more .git/HEAD
9f1224bca340fb2e4235adfa3e42297033c26ec9

标签引用

标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。 主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。 它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git tag V1.0 9f1224b -m 'commit 1 tag v1.0'

$ find .git/refs -type f
.git/refs/heads/master
.git/refs/tags/V1.0

$ more .git/refs/tags/V1.0
5dfd1ba5bf0614a54f43269524881d7cc3434d49

$ git cat-file -p 5dfd1ba5bf0614a54f43269524881d7cc3434d49
object 9f1224bca340fb2e4235adfa3e42297033c26ec9
type commit
tag V1.0
tagger leaderli <429243408@qq.com> 1593582664 +0800

commit 1 tag v1.0

远程引用

如果你添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。

git gc

当使用git gc或者git push时,git 会自动将.git/objects/下文件进行压缩,在.git/objects/pack/下生成.idx和.pack文件。

分支

git 的 commit 是一个有向无环图,其只包含父类 commit 的 sha1,分支仅仅是一个指针其值为 commit 的 sha1

查看.git 内的文件可以看到生成了.git/refs/heads/master,可以发现其指向 commit 1

1
2
$ more .git/refs/heads/master
9f1224bca340fb2e4235adfa3e42297033c26ec9

我们可以通过查看.git/logs/refs/heads/master,查看分支的历史记录,这也是git log命令的原理

1
2
3
4
5
$ more .git/logs/refs/heads/master
0000000000000000000000000000000000000000 9f1224bca340fb2e4235adfa3e42297033c26ec9 leaderli <429243408@qq.com> 1
593549136 +0800 commit (initial): commit 1
9f1224bca340fb2e4235adfa3e42297033c26ec9 014901f4a370ec3d0db58fc7a4cf9950d2111fbe leaderli <429243408@qq.com> 1
593555980 +0800 commit: commit 2

我们新增子目录以及子文件,并添加到 stage 区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ mkdir dir
$ echo "version 3" > dir/3.txt
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)

dir/

nothing added to commit but untracked files present (use "git add" to track)
# 添加到缓存区

$ git add dir/
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: dir/3.txt

git diff

git diff --cache是比较 index 和 HEAD 的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 我们查看HEAD的内容,发现HEAD指向master的commit 1
$ more .git/HEAD
ref: refs/heads/master
$ more .git/refs/heads/master
9f1224bca340fb2e4235adfa3e42297033c26ec9

# commit 1指向的tree对象的内容为
$ git cat-file -p 9f1224bca340fb2e4235adfa3e42297033c26ec9
tree 1d49be59d2805f145b66c15641a11e945a5d021a
author leaderli <429243408@qq.com> 1593549136 +0800
committer leaderli <429243408@qq.com> 1593549136 +0800

commit 1
$ git cat-file -p 1d49be59d2805f145b66c15641a11e945a5d021a
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 1.txt
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 2.txt

# index
$ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt

# 使用diff查看差异内容
$ git diff --cached
diff --git a/dir/3.txt b/dir/3.txt
new file mode 100644
index 0000000..7170a52
--- /dev/null
+++ b/dir/3.txt
@@ -0,0 +1 @@
+version 3

提交 commit 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ git commit -m 'commit 2'
[master 014901f] commit 2
1 file changed, 1 insertion(+)
create mode 100644 dir/3.txt

# HEAD当前执行commit 2
$ more .git/HEAD
ref: refs/heads/master
$ more .git/refs/heads/master
014901f4a370ec3d0db58fc7a4cf9950d2111fbe
$ git cat-file -p 014901f4a370ec3d0db58fc7a4cf9950d2111fbe
tree 162488e047cda08c69e932e81722f7ce191057ee
# commit 2的父节点为commit 1
parent 9f1224bca340fb2e4235adfa3e42297033c26ec9
author leaderli <429243408@qq.com> 1593555980 +0800
committer leaderli <429243408@qq.com> 1593555980 +0800

commit 2

# 查看commit 2 指向的tree
$ git cat-file -p 162488e047cda08c69e932e81722f7ce191057ee
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 1.txt
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 2.txt
040000 tree b57d4c5b3c1f31f87d0d5e7343db0b53f8f650c2 dir

$ git cat-file -p b57d4c5b3c1f31f87d0d5e7343db0b53f8f650c2
100644 blob 7170a5278f42ea12d4b6de8ed1305af8c393e756 3.txt

# HEAD执行的tree和index的一致了
$ git diff --cached

从概念上讲,此时 Git 内部存储的数据有点像这样: 深入git_内部数据存储结构.png

git diff <commit1> <commit2>的原理都是差不多的

切换分支

切换分支本质上就是将 HEAD 的引用指向对应的分支

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git sw -b dev
Switched to a new branch 'dev'
## 引用中多了dev
$ find .git/ -type f
.git/refs/heads/master
.git/refs/heads/dev
# HEAD 指向了dev分支
$ more .git/HEAD
ref: refs/heads/dev

#dev分支指向commit2
$ more .git/refs/heads/dev
014901f4a370ec3d0db58fc7a4cf9950d2111fbe

回退

回退工作区

1
2
3
4
# 修改2.txt
$ echo 'version 4' > 2.txt
# 新增4.txt
$ echo 'version 5' > 4.txt

git diff 是用来比较缓存区和工作区的差异的,但仅比较已在 git 版本控制下的文件,即仅比较缓存区已经有的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ git diff
diff --git a/2.txt b/2.txt
index 83baae6..96ac8f8 100644
--- a/2.txt
+++ b/2.txt
@@ -1 +1 @@
-version 1
+version 4

# 使用status可以看到新增还未添加到缓存区的文件
$ git status
On branch dev
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: 2.txt

Untracked files:
(use "git add <file>..." to include in what will be committed)

4.txt

no changes added to commit (use "git add" and/or "git commit -a")

# 我们尝试回退工作区改动
$ git checkout -- 2.txt
# 发现无差异了
$ git diff
# 新增还未增加到缓存区的文件,可以直接rm删除即可

拉取别的分支或 commit 的文件

git checkout <branch> <file>或git checkout <branch> <file>,分支的引用最终也指向一个 commit 对象,因此这两种方法都是类似的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$ more 1.txt
version 1
$ git checkout dev
$ echo 'dev 1' >> 1.txt
$ git add 1.txt
$ git commit -m 'dev commit 3'

# 查看提交历史记录
$ git log --graph --pretty=oneline --abbrev-commit
* c49b52f (HEAD -> dev) dev commit 3
* 014901f (master) commit 2
* 9f1224b (tag: V1.0) commit 1

# 根据commit3的sha1查找到1.txt的内容
$ git cat-file -p c49b52f
tree fc533492986c55db20a5c18d39c0fb9eb3412de5
parent 014901f4a370ec3d0db58fc7a4cf9950d2111fbe
author leaderli <429243408@qq.com> 1593605963 +0800
committer leaderli <429243408@qq.com> 1593605963 +0800

dev commit 3

$ it cat-file -p fc5334
100644 blob 258afb705ca6ffa8f0d56818ee9381cce55a2f8b 1.txt
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 2.txt
040000 tree b57d4c5b3c1f31f87d0d5e7343db0b53f8f650c2 dir

$ git cat-file -p 258afb
version 1
dev 1

# 同理,我们也可以看到commit1中1.txt的内容,省略细节
$ git cat-file -p 83baae
version 1

# 新增改动并添加到缓存区
$ echo 'dev 2' >> 1.txt
$ git add 1.txt
# 新增改动不添加到缓存区
$ echo 'dev 3' >> 1.txt

# 查看缓存区内容
$ git ls-files --stage
100644 b5efe718e4230081f57ee53504d377652e3507ce 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt
$ git cat-file -p b5efe7
version 1
dev 1
dev 2

# 工作区内容
$ more 1.txt
version 1
dev 1
dev 2
dev 3

# 拉取commit1的1.txt
$ git checkout 9f1224b 1.txt

# 当前工作区内容
$ more 1.txt
version 1

# 当前缓存区内容
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt
$ git cat-file -p 83baae
version 1

# commit3无任何变动,可以看到工作区和缓存区的1.txt文件和commit1保持一致

撤销(reset)

git reset 首先是将 HEAD 指向的引用的 commit 对象修改为新的 commit 对象,然后根据参数( --soft,--mixed(default),--hard)的不同,决定是否同步缓存区和工作区的内容。

大致的方式如下图所示 深入git_reset细节.png

我们通过实例验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
$ echo 'D' > state
$ git add state
$ git cm 'D'
[dev 66b4377] D
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 state
$ echo 'C' > state
$ git ca 'C'
[dev 995cdd0] C
1 file changed, 1 insertion(+), 1 deletion(-)
$ echo 'B' > state
$ git add state
$ echo 'A' > state

$ git log --graph --pretty=oneline --abbrev-commit
* 995cdd0 (HEAD -> dev) C
* 66b4377 D
* c49b52f dev commit 3
* 014901f (master) commit 2
* 9f1224b (tag: V1.0) commit 1

# soft
$ git reset --soft 66b4377

# working dir
$ more state
A
# stage
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt
100644 223b7836fb19fdf64ba2d3cd6173c6a283141f78 0 state
$ git cat-file -p 223b78
B
# HEAD 指向的是D的commit
more .git/refs/heads/dev
66b4377d2633dfd8fde71e2d231c5e3843708ad9


# mixed
$ git reset --mixed 66b4377

# working dir
$ more state
A
# stage
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt
100644 178481050188cf00d7d9cd5a11e43ab8fab9294f 0 state
$ git cat-file -p 1784810
D
# HEAD 指向的是D的commit
more .git/refs/heads/dev
66b4377d2633dfd8fde71e2d231c5e3843708ad9


# hard
$ git reset --hard 66b4377

# working dir
$ more state
D
# stage
$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0 1.txt
100644 83baae61804e65cc73a7201a7252750c76066a30 0 2.txt
100644 7170a5278f42ea12d4b6de8ed1305af8c393e756 0 dir/3.txt
100644 178481050188cf00d7d9cd5a11e43ab8fab9294f 0 state
$ git cat-file -p 1784810
D
# HEAD 指向的是D的commit
more .git/refs/heads/dev
66b4377d2633dfd8fde71e2d231c5e3843708ad9

python-tips

发表于 2020-06-27 更新于 2020-12-02 分类于 python

截取字符串或数组

w = '1' 当使用 w[1:],会得到一个空串,而不会报错

python dict 根据 value 找对应的 key

1
2
3
dicxx = {'a':'001', 'b':'002'}
list(dicxx.keys())[list(dicxx.values()).index("001")]
#'a'

使用守护进程的方式后台启动

1
2
# 后台启动
$ python background_test.py >log.txt 2>&1 &

监听文件是否有变动

1
2
3
4
5
6
7
8
9
10
11
12
13
# --coding:utf-8--
import os
import time

filename = '.' # 当前路径
last = time.localtime(os.stat(filename).st_mtime)
while True:
new_filemt = time.localtime(os.stat(filename).st_mtime)
if last != new_filemt:
last = new_filemt
print('change')
#os.system('nginx -s reload')
time.sleep(1)

替换字符串

类似 java 的 replace

1
2
3
4
5
import re

origin='/1/2'
re.sub('^/','',origin)
# 1/2

常用数学符号

发表于 2020-06-27 更新于 2020-06-30 分类于 杂七杂八

数学希腊字母表

大写 小写 希腊语 英语 发音
Α α Alpha a al-fa
Β β Beta b be-ta
Γ γ Gamma g ga-ma
Δ δ Delta d del-ta
Ε ε Epsilon e ep-si-lon
Ζ ζ Zeta z ze-ta
Η η Eta h eh-ta
Θ θ Theta th te-ta
Ι ι Iota i io-ta
Κ κ Kappa k ka-pa
Λ λ Lambda l lam-da
Μ μ Mu m m-yoo
Ν ν Nu n noo
Ξ ξ Xi x x-ee
Ο ο Omicron o o-mee-c-ron
Π π Pi p pa-yee
Ρ ρ Rho r row
Σ σ Sigma s sig-ma
Τ τ Tau t ta-oo
Υ υ Upsilon u oo-psi-lon
Φ φ Phi ph f-ee
Χ χ Chi ch kh-ee
Ψ ψ Psi ps p-see
Ω ω Omega o o-me-ga

集合符号

Ø 空集合

扬抑符(circumflexus)

拉丁文字的扬抑符是个山形符号 ( ˆ )、而希腊文扬抑符则可以是波浪号 ( ˜ ) 或反转的短音符 ( ̑ ).在汉语拼音,ê 用来代表独立的 /ɛ/,通常用于"欸、诶"等叹词。在英文,扬抑符就如其他附加符号一样用于外来语,例如 rôle。此符号在数学也有有用,称为 hat(帽子)或 roof(屋顶)。

例如: δ^\hat{\delta}δ^ 读作 hat delta

ECharts简介

发表于 2020-06-25 更新于 2020-12-02 分类于 javascript

安装

echarts 只需引入一个 js 文件即可

1
wget https://raw.githubusercontent.com/apache/incubator-echarts/4.8.0/dist/echarts.min.js

教程

官方教程已经说的非常详细了,这里只是摘抄一些简单的部分

echarts 的使用者,使用 option 来描述其对图表的各种需求,包括:有什么数据、要画什么图表、图表长什么样子、含有什么组件、组件能操作什么事情等等。简而言之,option 表述了:数据、数据如何映射成图形、交互行为。详情可见option 详细配置 echarts.setOption(option)可重复调用,仅覆盖更新,而不是整个替换 option。因此我们可以异步加载数据

setOption(option,true)覆盖更新,而不是 merge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var myChart = echarts.init(document.getElementById("main"));

$.get("data.json").done(function (data) {
myChart.setOption({
title: {
text: "异步数据加载示例",
},
tooltip: {},
legend: {
data: ["销量"],
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
});
});

当我们需要动态更新数据时,我们不断的调用 setOption 填充最新的数据即可。

入门示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ECharts</title>
<!-- 引入 echarts.js -->
<script src="echarts.min.js"></script>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById("main"));

// 指定图表的配置项和数据
var option = {
title: {
text: "ECharts 入门示例",
},
tooltip: {},
legend: {
data: ["销量"],
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
};

// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>

这样你的第一个图表就诞生了! ECharts简介_echarts入门示例.png

echarts 实例

一个网页中可以创建多个 echarts 实例。每个 echarts 实例 中可以创建多个图表和坐标系等等(用 option 来描述)。准备一个 DOM 节点(作为 echarts 的渲染容器),就可以在上面创建一个 echarts 实例。每个 echarts 实例独占一个 DOM 节点。

组件(component)

在系列之上,echarts 中各种内容,被抽象为“组件”。例如,echarts 中至少有这些组件:xAxis(直角坐标系 X 轴)、yAxis(直角坐标系 Y 轴)、grid(直角坐标系底板)、angleAxis(极坐标系角度轴)、radiusAxis(极坐标系半径轴)、polar(极坐标系底板)、geo(地理坐标系)、dataZoom(数据区缩放组件)、visualMap(视觉映射组件)、tooltip(提示框组件)、toolbox(工具栏组件)、series(系列)、...

我们注意到,其实系列(series)也是一种组件,可以理解为:系列是专门绘制“图”的组件。

如下图,右侧的 option 中声明了各个组件(包括系列),各个组件就出现在图中。 ECharts简介_组件细节.png

坐标系

一个坐标系,可能由多个组件协作而成。我们以最常见的直角坐标系来举例。直角坐标系中,包括有 xAxis(直角坐标系 X 轴)、yAxis(直角坐标系 Y 轴)、grid(直角坐标系底板)三种组件

x 轴和 y 轴基本等同,下面介绍一些常用的配置

  1. type 坐标轴类型。 可选:
    • value 数值轴,适用于连续数据。
    • category 类目轴,适用于离散的类目数据。为该类型时类目数据可自动从 series.data 或 dataset.source 中取,或者可通过 xAxis.data 设置类目数据。
    • time 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
    • log 对数轴。适用于对数数据。用于解决数据差异过大的问题
  2. min 坐标轴刻度最小值。 可以设置成特殊值 'dataMin',此时取数据在该轴上的最小值作为最小刻度。 不设置时会自动计算最小值保证坐标轴刻度的均匀分布。 在类目轴中,也可以设置为类目的序数(如类目轴 data: ['类 A', '类 B', '类 C'] 中,序数 2 表示 从'类 C'开始。也可以设置为负数,如 -3)表示第四个坐标为‘类 A’。 当设置成 function 形式时,可以根据计算得出的数据最大最小值设定坐标轴的最小值。如:

    1
    2
    3
    4
    min: function (value) {
    return value.min - 20;

    }

    其中 value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数可返回坐标轴的最小值,也可返回 null/undefined 来表示“自动计算最小值”(返回 null/undefined 从 v4.8.0 开始支持)。

  3. max 坐标轴刻度最大值。类似 min
  4. splitNumber 坐标轴分割段数,需要注意的是这个分割段数只是个预估值,最后实际显示的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整。

  5. minInterval 自动计算的坐标轴最小间隔大小
  6. maxInterval 自动计算的坐标轴最大间隔大小

一个 echarts 实例中,有多个 grid,每个 grid 分别有 xAxis、yAxis,他们使用 xAxisIndex、yAxisIndex、gridIndex 来指定引用关系: ECharts简介_多grid.png

dataset

ECharts 4 开始支持了 dataset 组件用于单独的数据集声明,从而数据可以单独管理,被多个组件复用,并且可以基于数据指定数据到视觉的映射。这在不少场景下能带来使用上的方便

维度(dimension)

常用图表所描述的数据大部分是“二维表”结构,上述的例子中,我们都使用二维数组来容纳二维表。现在,当我们把系列(series)对应到“列”的时候,那么每一列就称为一个“维度(dimension)”,而每一行称为数据项(item)。反之,如果我们把系列(series)对应到表行,那么每一行就是“维度(dimension)”,每一列就是数据项(item)。在 series 中我们可以使用属性 seriesLayoutBy 来决定按行还是按列做映射

维度可以有单独的名字,便于在图表中显示。维度名(dimension name)可以在定义在 dataset 的第一行(或者第一列)。例如上面的例子中,'score'、'amount'、'product' 就是维度名。从第二行开始,才是正式的数据。dataset.source 中第一行(列)到底包含不包含维度名,ECharts 默认会自动探测。当然也可以设置 dataset.sourceHeader: true 显示声明第一行(列)就是维度,或者 dataset.sourceHeader: false 表明第一行(列)开始就直接是数据。

维度的定义,也可以使用单独的 dataset.dimensions 或者 series.dimensions 来定义,这样可以同时指定维度名,和维度的类型(dimension type): 大多数情况下,我们并不需要去设置维度类型,因为会自动判断。但是如果因为数据为空之类原因导致判断不足够准确时,可以手动设置维度类型。

维度类型(dimension type)可以取这些值:

  • number: 默认,表示普通数据。
  • ordinal: 对于类目、文本这些 string 类型的数据,如果需要能在数轴上使用,须是 'ordinal' 类型。ECharts 默认会自动判断这个类型。但是自动判断也是不可能很完备的,所以使用者也可以手动强制指定。
  • time: 表示时间数据。设置成 'time' 则能支持自动解析数据成时间戳(timestamp),比如该维度的数据是 '2017-05-10',会自动被解析。如果这个维度被用在时间数轴(axis.type 为 'time')上,那么会被自动设置为 'time' 类型。时间类型的支持参见 data。
  • float: 如果设置成 'float',在存储时候会使用 TypedArray,对性能优化有好处。

  • int: 如果设置成 'int',在存储时候会使用 TypedArray,对性能优化有好处。

示例:

1
2
3
4
5
6
7
8
9
10
series: {
type: 'xxx',
dimensions: [
null, // 如果此维度不想给出定义,则使用 null 即可
{type: 'ordinal'}, // 只定义此维度的类型。'ordinal' 表示离散型,一般文本使用这种类型。
// 如果类型没有被定义,会自动猜测类型。
{name: 'good', type: 'number'},
'bad' // 等同于 {name: 'bad'}
]
}

数据到图形的映射(encode)

encode 声明的基本结构如下,其中冒号左边是坐标系、标签等特定名称,如 'x', 'y', 'tooltip' 等,冒号右边是数据中的维度名(string 格式)或者维度的序号(number 格式,从 0 开始计数),可以指定一个或多个维度(使用数组)。通常情况下,下面各种信息不需要所有的都写,按需写即可。各种类型的图表具体格式有所不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 在任何坐标系和系列中,都支持:
encode: {
// 使用 “名为 product 的维度” 和 “名为 score 的维度” 的值在 tooltip 中显示
tooltip: ['product', 'score']
// 使用 “维度 1” 和 “维度 3” 的维度名连起来作为系列名。(有时候名字比较长,这可以避免在 series.name 重复输入这些名字)
seriesName: [1, 3],
// 表示使用 “维度2” 中的值作为 id。这在使用 setOption 动态更新数据时有用处,可以使新老数据用 id 对应起来,从而能够产生合适的数据更新动画。
itemId: 2,
// 指定数据项的名称使用 “维度3” 在饼图等图表中有用,可以使这个名字显示在图例(legend)中。
itemName: 3
}

// 直角坐标系(grid/cartesian)特有的属性:
encode: {
// 把 “维度1”、“维度5”、“名为 score 的维度” 映射到 X 轴:
x: [1, 5, 'score'],
// 把“维度0”映射到 Y 轴。
y: 0
}
// 单轴(singleAxis)特有的属性:
encode: {
single: 3
}

// 极坐标系(polar)特有的属性:
encode: {
radius: 3,
angle: 2
}

// 地理坐标系(geo)特有的属性:
encode: {
lng: 3,
lat: 2
}

// 对于一些没有坐标系的图表,例如饼图、漏斗图等,可以是:
encode: {
value: 3
}

series

为了方便组织数据,我们统一使用 dataset 管理数据 在 echarts 里,系列(series)是指:一组数值以及他们映射成的图,一个 系列 包含的要素至少有:

图表类型(series.type)、

  • line(折线图)
  • bar(柱状图)
  • pie(饼图)
  • scatter(散点图)
  • graph(关系图)
  • tree(树图)
  • ...

一组数值

每个图表类型所需要的数据格式是不同的,例如 ECharts简介_每个系列说需要的数据.png

如果 series.data 没有指定,并且 dataset 存在,那么就会使用 dataset。datasetIndex 指定本系列使用那个 dataset。

其他的关于这些数据如何映射成图的参数

每个图标类型所提供的参数是有区别的,例如当使用 dataset 取数据时 ECharts简介_dataset取数据.png

一些其他常用参数

  1. data 系列中的数据内容数组。数组项通常为具体的数据项。通常来说,数据用一个二维数组表示。如下,每一列被称为一个『维度』。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    series: [
    {
    data: [
    // 维度0(X) 维度1(Y) 其他维度 ...
    [3.4, 4.5, 15, 43],
    [4.2, 2.3, 20, 91],
    [10.8, 9.5, 30, 18],
    [7.2, 8.8, 18, 57],
    ],
    },
    ];

    在 直角坐标系 (grid) 中『维度 X』和『维度 Y』会默认对应于 xAxis 和 yAxis。

  2. xAxisIndex 表示使用那个 x 轴坐标,默认指定第一个
  3. yAxisIndex 表示使用那个 y 轴坐标,默认指定第一个
  4. seriesLayoutBy 默认为'column',可指定为'row',则 dataset 中的二维数组每一行称为一个维度,对 data 无效
  5. encode 指定相关组件使用哪个维度
    • x 表示维度*映射到 x 轴。
    • y 表示维度*映射到 x 轴。
    • tooltip 表示维度*映射到 tooltip
  6. dimensions 给维度命名,方便 encode 中可直接引用名称,而不是角标

事件和行为

在 ECharts 中事件分为两种类型,一种是用户鼠标操作点击,或者 hover 图表的图形时触发的事件,还有一种是用户在使用可以交互的组件后触发的行为事件,例如在切换图例开关时触发的 'legendselectchanged' 事件(这里需要注意切换图例开关是不会触发'legendselected'事件的),数据区域缩放时触发的 'datazoom' 事件等等。

ECharts 支持常规的鼠标事件类型,包括 'click'、'dblclick'、'mousedown'、'mousemove'、'mouseup'、'mouseover'、'mouseout'、'globalout'、'contextmenu' 事件

如下是一个绑定点击操作的示例。

1
2
3
4
myChart.on("click", function (params) {
// 控制台打印数据的名称
console.log(params.name);
});

所有的鼠标事件包含参数 params,这是一个包含点击图形的数据信息的对象,如下格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
// 当前点击的图形元素所属的组件名称,
// 其值如 'series'、'markLine'、'markPoint'、'timeLine' 等。
componentType: string,
// 系列类型。值可能为:'line'、'bar'、'pie' 等。当 componentType 为 'series' 时有意义。
seriesType: string,
// 系列在传入的 option.series 中的 index。当 componentType 为 'series' 时有意义。
seriesIndex: number,
// 系列名称。当 componentType 为 'series' 时有意义。
seriesName: string,
// 数据名,类目名
name: string,
// 数据在传入的 data 数组中的 index
dataIndex: number,
// 传入的原始数据项
data: Object,
// sankey、graph 等图表同时含有 nodeData 和 edgeData 两种 data,
// dataType 的值会是 'node' 或者 'edge',表示当前点击在 node 还是 edge 上。
// 其他大部分图表中只有一种 data,dataType 无意义。
dataType: string,
// 传入的数据值
value: number|Array
// 数据图形的颜色。当 componentType 为 'series' 时有意义。
color: string
}

使用 query 只对指定的组件的图形元素的触发回调:

1
chart.on(eventName, query, handler);

query 可为 string 或者 Object。

  1. 如果为 string 表示组件类型。格式可以是 'mainType' 或者 'mainType.subType'。例如:

    1
    2
    3
    4
    chart.on('click', 'series', function () {...});
    chart.on('click', 'series.line', function () {...});
    chart.on('click', 'dataZoom', function () {...});
    chart.on('click', 'xAxis.category', function () {...});

    这里的 mainType 即表示组件的名称,subType 表示鼠标事件中参数 componentType 的值

  2. 如果为 Object,可以包含以下一个或多个属性,每个属性都是可选的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    <mainType>Index: number // 组件 index
    <mainType>Name: string // 组件 name
    <mainType>Id: string // 组件 id
    dataIndex: number // 数据项 index
    name: string // 数据项 name
    dataType: string // 数据项 type,如关系图中的 'node', 'edge'
    element: string // 自定义系列中的 el 的 name
    }

    例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    chart.setOption({
    // ...
    series: [
    {
    name: "uuu",
    // ...
    },
    ],
    });
    chart.on("mouseover", { seriesName: "uuu" }, function () {
    // series name 为 'uuu' 的系列中的图形元素被 'mouseover' 时,此方法被回调。
    });

nginx入门

发表于 2020-06-19 更新于 2020-12-02 分类于 linux

安装

基于 centos

  1. 安装依赖工具 sudo yum install yum-utils
  2. 配置 yum 的源 新建文件/etc/yum.repos.d/nginx.repo,并写入以下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [nginx-stable]
    name=nginx stable repo
    baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=1
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true

    [nginx-mainline]
    name=nginx mainline repo
    baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=0
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true
  3. 若需要安装最新版,而非调试版本 sudo yum-config-manager --enable nginx-mainline
  4. 安装 nginx sudo yum install nginx

启动

nginx 有一个 master 线程和多个 worker 进程,master 进程用来读取,解析配置文件以及管理 worker 进程,worker 进程才是真正处理请求的进程。worker 进程的个数可通过配置文件去配置或自动设置为 cpu 的核心数。可通过修改配置设定

1
worker_processes 1;

使用nginx即可启动,一旦 nginx 启动,就可以使用如下命令去控制 nginx

nginx -s signal

  • stop — fast shutdown
  • quit — graceful shutdown
  • reload — reloading the configuration file
  • reopen — reopening the log files

当更改配置文件后,可以使用nginx -s reload,使配置文件生效。 当 master 进程接收到 reload 的信号后,它首先会校验配置文件语法是否正确然后才会接收新的配置文件,若配置文件修改成功,master 进程会启动新的 worker 进程,并向旧的 worker 进程发送停止命令,旧的 worker 进程会以旧的配置处理请求,在处理完旧的请求后便会退出。

配置

可用变量

官方文档可用变量

常用变量

  • $arg_<name> url 请求请求参数 name 的值
  • $query_string url 整个请求参数字符串,即?后面的参数,例如:a=1&b=2
  • $upstream_http_<header> 返回报文的头,header 请求头的名称
  • $http_<header> 请求报文的头,header 请求头的名称

配置文件结构

nginx 的配置文件名为 nginx.conf,一般位于目录/usr/local/nginx/conf, /etc/nginx, 或 /usr/local/etc/nginx.下 nginx 的组件由配置文件中的指令构成,指令的基本格式有两种

  1. 简单的命令:由 name 和 parameters 以及;结尾
  2. 块命令: 由 name 和一个由大括号`{}包裹的命令的集合,同时也被称为 context
  3. #后面的视为注释
  4. 不在任何 context 内的命令则被视为在 main context中

监听请求

我们通过配置server来处理请求

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name example.org www.example.org;
...
}

server {
listen 80;
server_name example.net www.example.net;
...
}

server {
listen 80;
server_name example.com www.example.com;
...
}

其中port表示监听的端口号,server_name表示监听的host(即 IP 地址或域名地址),server_name 与 host 匹配的优先级关系如下

  1. 完全匹配
  2. 通配符在前的,如*.test.com
  3. 在后的,如 www.test.*
  4. 正则匹配,如~^.www.test.com$

如果都不匹配

  1. 优先选择 listen 配置项后有 default 或 default_server 的
  2. 找到匹配 listen 端口的第一个 server 块

通过curl -v,我们可以看到 host

处理请求

在server下配置location

  1. location = /uri    =开头表示精确匹配,只有完全匹配上才能生效。
  2. location ^~ /uri   ^~ 开头对 URL 路径进行前缀匹配,并且在正则之前。
  3. location ~ pattern  ~开头表示区分大小写的正则匹配。
  4. location ~* pattern  ~*开头表示不区分大小写的正则匹配。
  5. location /uri     不带任何修饰符,也表示前缀匹配,但是在正则匹配之后。
  6. location /      通用匹配,任何未匹配到其它 location 的请求都会匹配到,相当于 switch 中的 default。

映射静态资源

root 指向静态资源的路径 index 默认首页

1
2
3
4
5
6
7
8
server {
listen 8888;
server_name "test";
location ~ \.(gif)$ {
root /home/li/tomcat/webapps/manager;
index index.html;
}
}

增加请求头

配置,这样在服务器端的 headers 中就可以看到名为 name 的指定 header,需要注意的是,当值为空时,nginx 不会发送该请求头

1
2
3
4
5
6
7
8
server {
listen 18080;
location / {
proxy_pass http://f5;
proxy_set_header name $arg_name;

}
}

负载均衡

测试用脚本

一个测试用的 http 服务端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# --coding:utf-8--

from http.server import BaseHTTPRequestHandler, HTTPServer
from os import path
import sys
from urllib.parse import urlparse

port = 8081
class Handler(BaseHTTPRequestHandler):

def do_GET(self):
global port
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.send_header('port', port)
self.end_headers()
self.wfile.write(b'')
print(port,self.headers.get('Host'), self.path,flus
h=True)

def do_POST(self):
self.do_Get()


def run():
if len(sys.argv) > 1:
global port
port = int(sys.argv[1])
print('port',port)
server_address = ('', port)
httpd = HTTPServer(server_address, Handler)
print('running server...', port)
httpd.serve_forever()


if __name__ == '__main__':
run()

启动多个 http 服务的 shell 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
start(){
echo 'start'$1
python http_nginx.py $1 >> nginx.log 2>&1 &
echo "kill -9 $! # $1" >> nginx.pid
}
stop(){
echo 'stop'
ps -ef|grep http_nginx|grep -v grep|grep -v start|awk '{print "kill -
9 "$2}'|sh
}

stop
touch nginx.log
touch nginx.pid
:>nginx.log
:>nginx.pid

args=$@
if [ ! -n "$args" ] ;then
args='18081 18082 18083'
fi
for arg in $args
do
start $arg &
done

tail -f nginx.log

负载均衡与 session 保持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream f5 {

hash $arg_a;
server localhost:18081;
server localhost:18082;
server localhost:18083;
}
server {
listen 18080;
location / {
proxy_pass http://f5;

}
}

使用测试脚本不断访问

1
2
3
4
5
while [ 1 ]
do
curl localhost:18080/hello?a='123456&b=abc'
sleep 1
done

通过观察 http 服务端的日志输出,我们可以发现所有请求都指向了同一个服务器

当请求 url 不带参数a时,可以观察到所有请求均衡的分配在每台服务器上

18082 f5 /hello 18081 f5 /hello?a=123456&b=abc 18083 f5 /hello 18081 f5 /hello?a=123456&b=abc 18081 f5 /hello 18082 f5 /hello 18081 f5 /hello?a=123456&b=abc 18083 f5 /hello 18081 f5 /hello?a=123456&b=abc

当负载服务器停止服务时,nginx 会自动重新计算 hash

日志

我们可配置http|log_format来控制 nginx 的 access_log 日志的输出内容,你可以打印所有 nginx 中有关的变量值

1
2
3
4
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
}

禁用浏览器缓存

通过添加返回头

1
2
3
4
5
6
7
8
server {
listen 80;
server_name example.org www.example.org;

# 设置禁用浏览器缓存
add_header Cache-Control no-cache;
...
}

iterm2

发表于 2020-06-19 更新于 2020-06-30 分类于 工具

切换编辑模式

1
2
3
4
5
# Emacs mode
bindkey -e

# Vi mode
bindkey -v

自动完成

iTerm2 可以自动补齐命令,输入若干字符,按 ⌘+;弹出自动补齐窗口,列出曾经使用过的命令

分屏

垂直分屏:command + d

水平分屏:command + shift + d

切换屏幕:command + option + 方向键 command + [ 或 command + ]

根据编号切换屏幕 iterm2_根据编号切换.png

1…345…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun