关于 SSH 端口转发

SSH 的端口转发很实用,但我总觉得难以理解和记忆,直到最近才有所好转。 因为又派上用场了。以前基本只做做内网穿透,现在更多地拿来绕过防火墙。自己的服务器,大多数端口虽然都是被禁用的(至少禁止入网,这也是正常的安全措施),但是想要连接的话直接本地端口转发就可以了。 TL;DR 本地端口转发在当前机器上设置,然后从本机出发,通过另一台机器,连接其他的机器。适用于防火墙的绕过、多重 SSH 登录等。 远程端口转发在当前机器上设置,然后从另外一台机器出发,通过当前机器,连接本机或者其他的机器。适用于 NAT 网络穿透、暴露内部网络服务等。 本质上都是先建立 SSH 会话,形成隧道,然后在上面进行正向或反向的数据传输。 Local Port Forwarding 为什么叫做本地呢,我想有两个原因: 转发的端口在当前(执行 SSH 命令这台)机器上 请求是从当前机器发出的 当前机器就是我的笔记本,另外一台是服务器。比如,在服务器上部署一个应用,开放给 8000 端口,但是被墙掉了,没办法在本地调试,怎么办?防火墙肯定开放了 SSH 登录的端口,比如 22,那么就让请求从本地的端口发送到服务器的 22 端口,再转发到 8000 端口,最后原路返回。我可以设置本地的端口也是 8000,这样直接用 localhost:8000 来访问应用就好了。 转发的重点在于本地的 8000 端口和服务器的 22 端口之间,因为请求到了服务器之后可以给应用的 8000,也可以给其他的机器,只要服务器能连接到: # ssh -L local_port:dest_addr:dest_port server # Local 8000 < -- > Server 22 < -- > Server 8000 # -fNT 让 ssh 不要打开服务器 shell,并且转为后台运行 # server 隐含了使用 22 端口登录,当然也可以在 ssh config 中设置任意登录端口 ssh -fNT -L 8000:localhost:8000 server 注意这里的 dest 对应的 src 是 server,也就是说 localhost 及后面的 8000 都是 server 的 IP 和端口。可以理解为 server 是中介,整条通路是 local -> server -> dest....

12-22 · 2 min

From BitTorrent to Firewall

服务器能做什么?在 Awesome-Selfhosted 里可以找到上百种答案。如果带宽不算太小的话,那么 BT 下载是个不错的尝试。借着 No Time to Die 的上映我开始重温 007 系列,从皇家赌场到幽灵党,在服务器上的下载体验是很好的。 BitTorrent 在此之前,我基本上把 BT、种子、磁力、迅雷下载当成同一种东西。下载电影?先找种子或者磁力链接,打开迅雷下载,然后视速度决定要不要开个会员。 实际上这完全曲解了 BT 下载。 首先 BitTorrent 是一种网络协议。还记得计算机网络一开始就提到过除了 C/S 架构之外,还有 P2P(Peer-to-peer),也就是网络中的各个节点都扮演了同等的角色,既是客户端也是服务器。BT 基于 P2P 实现了去中心化的文件分享,让网络数据的传输不再仅限于服务器的能力,而是共享带宽,每个人下载的同时也在上传,所以越多人参与速度就越快。 类似于 HTTP 和 FTP,BT 也是基于 TCP/IP 的一种应用层协议。基本上它是这么运作的: 我有一部电影,想把资源分享到网络,要先提供一个种子文件 种子文件实际上就是个文本文件,里面主要记录两部分信息 Trackers: 就是 Tracker 服务器的地址,这个服务器不是用来下载资源的,而是用于获取其他 Peers 的联系方式 Files: 一个视频文件会被(逻辑)划分为很多个虚拟分块,每块的索引和验证码都包含在这里 接下来我把种子文件发布出去,等待别人下载 这时候有人获取到种子了,于是开启了 BT 客户端下载 客户端先解析种子文件中的信息,找到 Tracker,然后询问有哪些 Peers 因为是第一个下载者,所以 Tracker 告知 Peer 目前只有我,也就是发布者 之后对方会尝试与我互连,然后根据 Files 信息交换数据,这里基本就是我上传给对方 下载的过程会以分块为单位进行,每块完成下载后会根据验证码再做校验 如果这时又有一个人开始下载,那么我和这第一个下载者都会贡献上传 随着更多用户的参与,(上传)下载的速度就会越来越快 可以发现,整个过程中 Tracker 是很关键的一步,如果没有有效的 Tracker 提供 Peers,后面的下载都无法开始。所以如果你的 BT 下载没有速度,首先要尝试多添加一些 Tracker 服务器,比如 TrackersList....

12-20 · 2 min

Python heapq 源码阅读

Heap 作为一种重要的数据结构,有许多应用场景,比如优先级队列,每次出队的都是最大值或者最小值的元素。很多语言都集成了相关实现,比如 Java 的 PriorityQueue,而 Python 提供了 heapq 模块。 因为 Heap 通常用数组而不是链表存储,所以 Python 里面的 Heap 实质上就是一个列表,而 heapq 提供的几个函数也是以列表对象作为参数的: from heapq import heappush, heappop, heappify, heapreplace, heappushpop heap = [] heappush(heap, 1) item = heap[0] # 第一个元素代表堆顶元素 heappop(heap) heapify([3, 2, 1, 5, 6, 4]) # 把普通列表转化为堆结构 [1, 2, 3, 4, 5, 6] heapreplace([3, 4, 5], 1) # 直接将堆顶元素 3 替换为 1,最后堆结构为 [1, 4, 5] heappushpop([3, 4, 5], 1) # 先将 1 插入堆中,再 pop 出堆顶元素,最后堆结构为 [3, 4, 5] 为什么 heapq 提供的是最小堆而不是更常见的最大堆呢?这就得从源码中找答案了。...

11-29 · 4 min

Python OrderedDict 实现 LRU 缓存

LRUCache 是一种经典的缓存机制,它的基本思路是按照最近使用的时间对元素排序,在清理时优先把搁置最久的删除掉。 如果不想给每个缓存元素都记录一个时间戳的话,可以应用哈希链表来简单地实现 LRU 算法。也就是对一个哈希表中的所有元素增加指针,从而串起一个双链表,这样既可以快速 get value,又可以通过把最近使用过的元素放到头部来维护顺序,删除的时候从末尾开始就好了。 手写双链表并不困难,但是借助 OrderedDict 的话,可以写出非常简短的代码: from collections import OrderedDict class LRUCache: def __init__(self, capacity): self.capacity = capacity self.hashtable = OrderedDict() def get(self, key: int) -> int: if key in self.hashtable: self.hashtable.move_to_end(key, last=False) return self.hashtable[key] return -1 def put(self, key: int, value: int) -> None: self.hashtable[key] = value self.hashtable.move_to_end(key, last=False) if len(self.hashtable) > self.capacity: self.hashtable.popitem() 其中最神奇的就是 move_to_end 和 popitem 方法(后者默认是弹出最后面的元素)的使用,这也得益于 OD 可以保证 key-value pair 的顺序。那么 OD 是如何做到的呢?其实还是双链表,下面是它的 Python 实现:...

11-28 · 3 min

Numeric Strings in Python

Python 的字符串自带了三种判断字符是否为数字的方法,但实际用处却相差很多。 TL;DR 三种方法 isdecimal < isdigit < isnumeric,即包含的范围越来越大 除了 ASCII 字符以外,对于 Unicode 的字符也都覆盖在内 三种方法对于小数点和负号都会返回 False 三种方法对于空字符串都会返回 False 比较简便判断数字字符串的方法:直接使用 float 方法并检测 ValueError Decimal & Digit & Numeric 对于 isdecimal, isdigit 和 isnumeric 三种方法,目的并不是判断字符串是不是一个有效数字,而是针对每一个字符的校验: isdecimal: 判断字符串中的字符是否都为 Decimal,也就是在 Unicode 中类别为 Nd 的字符 isdigit: 除了 isdecimal 包含的范围之外,还会判断字符是否都为 Digit,即 Unicode 的 Numeric_Type 为 Digit 或 Decimal isnumeric: 除了 isdigit 包含的范围之外,还会判断字符是否都为 Numeric,即 Numeric_Type 为 Numeric 所以这三种方法覆盖的字符范围,每一个都是前一个的超集。对于超出 ASCII 字符之外的效果,比如: “0123456789” 这种 Full-width 字符串 isdecimal 会判定为 True,后两个方法也一样 “⓪①②③④⑤⑥⑦⑧⑨” 这种 Circled-digit 字符串 isdecimal 判定 False,但 isdigit 和 isnumeric 为 True “一二三四五六七八九十壹貳參肆伍陸柒捌玖拾” 这种中文数字字符串只有 isnumeric 才会判定为 True 总之这几种方法有更广泛的用途,根本不是为了简单的 ASCII 数字字符串的判断。即使用来做判断的话,局限性也非常大,因为如果包含小数点和负号,三个方法都会返回 False....

11-21 · 1 min