#260 free 输出解读

2018-06-12
  • total 内存总数
  • used 使用内存
  • free 未使用内存
  • shared Memory used (mostly) by tmpfs (Shmem in /proc/meminfo)
  • buffers Memory used by kernel buffers (Buffers in /proc/meminfo)
  • cached Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
  • available 可用内存

#259 短信长度到底是怎么规定的

2018-06-08

短信的通信协议从 2G 时代一直到现在基本没有什么变化。

  1. 支持 140 个字节(140 Byte = 140 * 8 bit = 1120 bit)
  2. 支持 GSM-7 编码,8bit 编码,UCS-2 编码
  3. GSM-7 每个字符 7 bit,支持英语和西欧语言,配合 National Language Shift Table 功能,能够支持一些其他字母文字的语言。
  4. UCS-2 每个字符 2 Byte(16 bit),传递一些 GSM-7 框架不支持的语言,比如中文(字符太多,7bit 方案反倒不合适)。
  5. 8bit 用来传递图片等二进制数据。
  6. 根据短信协议,如果长度超出范围,需要在短信前面加上一个二进制头部,叫做 UDH(User Data Header)。
  7. UDH 一共 6 个字节
  8. 包含一些 SMS 拓展字段
  9. 对于短信拆分来讲,里面有这一批短信的总条数,和当前这条短信的序号

所以,

  1. 如果是中文短信,用 UCS-2 编码,每个字符 2 Byte,一条短信最长支持 70 个中文字符(140 Byte / 2)。
  2. 注意:所有字符,包括 ASCII 中的英文、数字、半角的标点,全部需要转换成 UCS-2 的 2 Byte 编码。
  3. 如果长于 70 个字符,就需要切割成多条,UDH 头部需要 6 Byte,每条短信还剩 134 Byte,除以二,还能装 67 个字符。
  4. 例如:140 个中文字符的短信,需要切割成 67 + 67 + 6 三条短信。
  5. 如果是英文短信,或者部分西欧语言,可以使用 GSM-7 编码,每个字符 7 bit,一条短信最长支持 160 个字符(1120 bit / 7)。
  6. 如果长于 160 个字符,也是需要切割,除去 UDH 的 6 Byte = 6 _ 8 bit = 48 bit,还剩 1120 bit - 48 bit = 1072 bit,
    1072 bit = 7 bit _ 153 + 1 bit,也就是说最多发送 153 个字符(多的那 1 bit 置 0,不参与计费)。
  7. 例如,320 个英文字符,需要切割成 153 + 153 + 14 三条短信。
  8. 注意:GSM-7 编码中,9 个拓展字符 ^ ~ \ | { } [ ] € 需要算两个字符。非常重要
  9. 数据通信中都是使用 Byte 为单位,用 GSM-7 编码可能会除不尽,也就是说剩余 1 ~ 7 bit。比如:
    1. 发送 33 个字,33 * 7 bit = 231 bit,需要 29 Byte (232 bit) 来装,就多了 1 bit
    2. 同样的方法计算,发送 34 个字多了 2 bit,... 发送 39 个字剩余 7 bit,发送 40 个字正好 35 Byte,没有多余的 bit...
    3. 根据协议,多余的 bit 需要置零,除非是多出来 7 bit,7 个 0 会和 GSM-7 的 @ 字符冲突,这时候应该置为 0001101,也就是 GSM-7 中的 \r 回车字符。

最后:

  1. 关于 GSM-7 的详细信息,可以参考:2022/01/05,GSM-7 编码
  2. 关于 UDH 的详细信息,可以参考:2022/05/13,CMPP: UDHI 头
  3. https://en.wikipedia.org/wiki/Concatenated_SMS

补充:

  1. 部分通道采用 7 Byte 的 UDH (短信批次标识由 1 Byte 改成 2 Byte),所以短信切割长度是 152 个字符((140 - 7) * 8 / 7)。
  2. 如果短信采用 GSM-7 编码,UDH 需要按 7 bit 对齐,也就是说如果是 6B 的 UDH,最后需要占用 49 bit,UDH 后面的 1 bit 填 0。

#258 Python 打包上传到 PyPI

2018-05-26

首先你得有 PyPI 的账号,没有注册的话,不用搞了。
然后你肯定要先准备 setup.py 了,如果这个都没弄就不用搞了。

算了,先贴一个简单的样板吧。

setup.py

# -*- coding: utf-8 -*-

from setuptools import setup

setup(
    name='PageageName',
    version='0.0.1',
    author='YourName',
    author_email='YourEmail',
    url='PackageSite (e.g. GitHub)',
    description='....',
    long_description=open('README.md').read(),
    long_description_content_type='text/markdown',
    packages=['PackageDir'],
    install_requires=[
        'requests>=2.18.4',
        'balabala',
    ],
    extras_require={  # 可选
        'dev': [
            'balabala...',
        ],
        'test': [
            'balabala...',
        ],
    },
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'License :: OSI Approved :: MIT License',
    ],
)

流程

python3 -m pip install twine

rm -rf dist

python3 setup.py sdist bdist_wheel
python2 setup.py sdist bdist_wheel

twine upload dist/*

twine 会询问账号密码,如果不想每次这么麻烦,可以创建 ~/.pypirc 文件。

.pypirc 文件

[distutils]
index-servers =
    pypi

[pypi]
repository = https://upload.pypi.org/legacy/
username = your_username
password = your_password

参考资料

#257 MIME 编码

2018-05-19

说明

多用途互联网邮件扩展
Multipurpose Internet Mail Extensions

最初的电子邮件标准 RFC 822 只支持发送 ASCII 字符文本内容,通过 MIME 这个拓展(RFC 2822),可以发送所有类型的内容。
后面的 HTTP 协议也是在 MIME 这个框架内构建的,Web 开发者比较熟悉的那几个 Content-xxx 头就是从 MIME 里面来的。

规范文件

RFC No. Type Title
RFC 2045 Standards Track Multipurpose Internet Mail Extensions (MIME) Part One:
Format of Internet Message Bodies
RFC 2046 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Two:
Media Types
RFC 2047 Standards Track MIME (Multipurpose Internet Mail Extensions) Part Three:
Message Header Extensions for Non-ASCII Text
RFC 2048 Best Current Practice Multipurpose Internet Mail Extensions (MIME) Part Four:
Registration Procedures
RFC 2049 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Five:
Conformance Criteria and Examples

语法

MIME-Version: 1.0                           // MIME 版本
Content-Type: [type]/[subtype]; parameter   // 内容类型
Content-Transfer-Encoding: [encoding]       // 内容传输编码
Content-Disposition: [disposition]          // 内容配置

内容类型 Content-Type

又叫互联网媒体类型(Internet media type)或者 MIME 类型(MIME type)。
类型信息的注册事宜,由 IANA(Internet Assigned Numbers Authority)统一管理。

按照注册来源分成几种类型,只用关心标准数,这个了解一下就行了:

  • 标准树 类型名 / 子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 厂商树 类型名 / vnd.子类型名 [ + 后缀 ] [ ; 可选参数 ],例如:application/vnd.debian.binary-package
  • 个人树 类型名 / prs.子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 未注册的 x.树 类型名 / x.子类型名 [ + 后缀 ] [ ; 可选参数 ]

常见的类型

详细类型在 IANA 官网有,链接在下面参考资料部分我贴了一个。

Type Subtype Description
text plain 文本
text html HTML
text xml XML
text javascript JavaScript
text css CSS
text csv CSV
text vcard vCard 电子名片
image bmp
image jpeg
image png
image gif
image webp
image svg+xml
image icon
audio mpeg
audio mp4
audio ogg
audio webm
audio flac
video mpeg
video mp4
video ogg
video webm
application xml XML
application json JSON
application ecmascript
application javascript
application zip
application gzip
application pdf
application rss+xml
application atom+xml
application octet-stream

内容传输编码

编码类型主要是这几种:“7bit”,“8bit”,“binary”,“quoted-printable”,“base64”。

#256 SMTP 认证

2018-05-18

最常见的三种 SMTP 认证方法:

  • PLAIN
  • LOGIN
  • CRAM-MD5

#255 Python 应用: 简易 SMTP 服务器

2018-05-07

Python2

python2 -m smtpd -h
# An RFC 2821 smtp proxy.
# Usage: /usr/lib/python2.7/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
#     --nosetuid, -n
#     --version, -V
#     --class classname, -c classname
#     --debug, -d
#     --help, -h

Python3

python -m smtpd -h
# An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
# Usage: /usr/lib/python3.9/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
#     --nosetuid, -n  默认会设置用户为 nobody,如果不是 root 会因权限不足失败
#     --version, -V
#     --class classname, -c classname 默认: PureProxy
#     --size limit, -s limit 消息大小限制(RFC 1870 SIZE extension),默认是 33554432 字节,即 32MB
#     --smtputf8, -u 启用 SMTPUTF8 扩展(RFC 6531)
#     --debug, -d
#     --help, -h

# 如果不指定主机,就使用 localhost
# 如果主机是 localhost,端口使用 8025
# 如果是其他主机,端口使用 25
python3 -m smtpd -n

# 默认的 PureProxy 会给转信出去,正常情况会被服务器拒绝
python3 -m smtpd -n -c smtpd.DebuggingServer

Python 3.9 的 PureProxy 有 BUG,会报 process_message() got an unexpected keyword argument 'mail_options'

自定义黑洞服务器

blackhole.py

import smtpd
import time
class BlackHoleServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        print('%s %s %s -> %s' % (time.strftime('%Y-%m-%d %H:%M:%S'), peer, mailfrom, rcpttos))

setup.py

import setuptools
setuptools.setup(name="blackhole", py_modules=["blackhole"])

附件下载:blackhole.zip

python setup.py install --user
python -m smtpd -n -c blackhole.BlackHoleServer

测试

import smtplib
smtp = smtplib.SMTP('localhost', 8025)
from_addr = 'admin@markjour.com'
to_addr = 'you@markjour.com'
smtp.sendmail(from_addr, to_addr,
    f"""From: {from_addr}\nTo: {to_addr}\nSubject: just4fun\n\nhello, world!""")

#253 Python 应用: 简易 HTTP 服务器

2018-05-07

Python2

在当前目录起 HTTP 服务,可以用于测试和临时性的文件下载服务。

# Default bind to 0.0.0.0:8000
python -m SimpleHTTPServer

# Maybe you want to use port 8080
python -m SimpleHTTPServer 8080

Python3

除了可以指定端口,还可以指定绑定地址、工作目录。

# Also bind to 0.0.0.0:8000
python -m http.server

python -m http.server -h
# usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]
#
# positional arguments:
#   port                  Specify alternate port [default: 8000]
#
# optional arguments:
#   -h, --help            show this help message and exit
#   --cgi                 Run as CGI Server
#   --bind ADDRESS, -b ADDRESS
#                         Specify alternate bind address [default: all interfaces]
#   --directory DIRECTORY, -d DIRECTORY
#                         Specify alternative directory [default:current directory]

python -m http.server 9999
python -m http.server --bind=127.0.0.1
python -m http.server --bind=127.0.0.1 9999
python -m http.server -d ~/Pictures

#252 Python Redis Debug

2018-05-06

如果可以的话,使用 redis-cli monitor 命令来输出所有 Redis 命令也很方便。

有时,条件不允许,或者 Redis 需要处理其他的连接,我希望将自己代码调用的 Redis 命令输出到日志中,方便调试。

#251 邮件格式 (RFC 822)

2018-05-05

相关的文章:

邮件是由纯文本组成,其详细的格式有很多 RFC 规范需要遵守。我这里只能对我所了解的,也是基础的 —— 或者说最核心的 —— 格式做一个说明。

最核心的部分是 1982 年的 RFC 822 (STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES),之后又做过一些更新, 比如 RFC 2822RFC 5322 (Internet Message Format) 和一堆补丁更新。本文要讲的基本格式,从开始到现在并没有什么明显变化。

  1. 邮件是一种纯文本格式,最开始只包含 ASCII 字符,后来引入了 MIME 之后,可以制定别的编码,比如 UTF-8 等。

  2. 换行符是 \r\n,也就是 CR + LF

  3. 整体来说,一封邮件由邮件头(Headers)和邮件体(Payload)组成。

  4. 邮件头包含若干个头字段

  5. 邮件头和邮件体之间用一个空行隔开

  6. RFC2882 和 RFC5322 都规定了电子邮件每一行的长度,排除行末 CRLF,不可以超过 998 个字符,建议不超过 78 个字符。

There are two limits that this specification places on the number of
characters in a line.  Each line of characters MUST be no more than
998 characters, and SHOULD be no more than 78 characters, excluding
the CRLF.

如果太长,应该拆分成多行,下一行行首加上至少一个空格或者制表符,表示是上一行的延续。

邮件示例

From: Bob <bob@markjour.com>
To: Mark <mark@markjour.com>
Subject: Hello

Hello, Bob,

Would you like to join me for dinner?

--
Mark

RFC 822 中关于字符的定义

                                            ; (  Octal, Decimal.)
# 字符
CHAR        =  <any ASCII character>        ; (  0-177,  0.-127.)
# 字母
ALPHA       =  <any ASCII alphabetic character>
                                            ; (101-132, 65.- 90.)
                                            ; (141-172, 97.-122.)
# 数字
DIGIT       =  <any ASCII decimal digit>    ; ( 60- 71, 48.- 57.)
# 控制字符
CTL         =  <any ASCII control           ; (  0- 37,  0.- 31.)
                character and DEL>          ; (    177,     127.)
# 回车
CR          =  <ASCII CR, carriage return>  ; (     15,      13.)
# 换行
LF          =  <ASCII LF, linefeed>         ; (     12,      10.)
# 空格
SPACE       =  <ASCII SP, space>            ; (     40,      32.)
# 制表符
HTAB        =  <ASCII HT, horizontal-tab>   ; (     11,       9.)
# 引号
<">         =  <ASCII quote mark>           ; (     42,      34.)
# 回车换行
CRLF        =  CR LF
# 空白
LWSP-char   =  SPACE / HTAB                 ; semantics = SPACE
# 连贯空白, 折行空白
linear-white-space =  1*([CRLF] LWSP-char)  ; semantics = SPACE
                                            ; CRLF => folding
# 特殊字符
specials    =  "(" / ")" / "<" / ">" / "@"  ; Must be in quoted-
            /  "," / ";" / ":" / "\" / <">  ;  string, to use
            /  "." / "[" / "]"              ;  within a word.
# 分隔符
delimiters  =  specials / linear-white-space / comment
# 文本
text        =  <any CHAR, including bare    ; => atoms, specials,
                CR & bare LF, but NOT       ;  comments and
                including CRLF>             ;  quoted-strings are
                                            ;  NOT recognized.
# 原子字符
atom        =  1*<any CHAR except specials, SPACE and CTLs>
quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
                                            ;   quoted chars.
qtext       =  <any CHAR excepting <">,     ; => may be folded
                "\" & CR, and including
                linear-white-space>
domain-literal =  "[" *(dtext / quoted-pair) "]"
dtext       =  <any CHAR excluding "[",     ; => may be folded
                "]", "\" & CR, & including
                linear-white-space>
# 注释
comment     =  "(" *(ctext / quoted-pair / comment) ")"
ctext       =  <any CHAR excluding "(",     ; => may be folded
                ")", "\" & CR, & including
                linear-white-space>
quoted-pair =  "\" CHAR                     ; may quote any char
phrase      =  1*word                       ; Sequence of words
word        =  atom / quoted-string

对应上 ASCII:

  0 -  31 控制字符, 其中包括常用的:
          - HT ( 9)  水平制表符
          - LF (10) 换行
          - CR (13) 回车
       32 空格
 33 -  47 符号 !"#$%&'()*+,-./
 48 -  57 数字
 58 -  64 符号 :;<=>?@
 65 -  90 大写字母
 91 -  96 符号 [\]^_`
 97 - 122 小写字母
123 - 126 符号 {|}~
      127 控制字符(DEL)

CHAR 0-127
CTL  0-37 + 127

符号中:
()<>[]@,;:\".
    13 个被视作特殊字符,需要转义
!#$%&'*+-/=?^_`
    15 个就是普通符号

atom = 数字 + 字母 + 普通符号

邮件头格式

field       =  field-name ":" [ field-body ] CRLF
     field-name  =  1*<any CHAR, excluding CTLs, SPACE, and ":">
     field-body  =  field-body-contents
                    [CRLF LWSP-char field-body]
     field-body-contents =
                   <the ASCII characters making up the field-body, as
                    defined in the following sections, and consisting
                    of combinations of atom, quoted-string, and
                    specials tokens, or else consisting of texts>

字段名称允许使用的字符范围非常宽泛,而且大小写不敏感,

但是一般实践中:

  • 由字母和中划线组成
  • 首字母大写,或全部大写

常用字段

  • Received MTA 轨迹(传输过程中的相关信息)
  • Date 发信时间,格式:Fri, 21 Nov 1997 09:55:06 -0600
  • Sender Mail From 地址
  • From 发件人
  • Subject 邮件标题
  • To 收件人
  • Cc 抄送
  • Bcc 密送
  • Reply-To 回复地址
  • Message-ID 邮件标识
  • References 回复邮件标识,逗号隔开
  • In-Reply-To 回复邮件标识(会话发起的第一封)
  • Return-Path 发信任地址(2020/07/31,邮件的 Return-Path 头是什么
  • Comments 说明
  • Keywords 关键字

一般采用 X- 开头的字段名称表示自定义字段,或者叫拓展字段:

常见的拓展字段:

  • X-Mailer