用Python操作nanomsg(四)——Pair&curses

先上仓库地址 https://github.com/r00t1900/nano-chat.git

按照之前的节奏,第四章应该就是介绍PAIR模式的用法并且顺利更新之前的LAN-Chat Program后就可以完事儿了。今天这章到今天才完成,实属是自己一根筋,想着把curses利用进来,让命令行程序的可操作性更好一些。


tmux左右分屏展示程序运行的样子
nano-chat

最初的“梦想”实现了,就是花的时间太长了,看git的提交记录才发现在curses库整合测试成功的这条commit之前整整三天没有提交过一次commit下面来一点一点跟大家掰这个程序的完成过程,和完事儿后一些自己的思考。

关于Pair

此模式的用法很简单,新建一个pair模式socket所需要的代码为:

pair_socket = nnpy.Socket(nnpy.AF_SP, nnpy.PAIR)
# set send and recv timeout to 1s
pair_socket.setsockopt(nnpy.SOL_SOCKET, nnpy.SNDTIMEO, config.C_SEND_TIMEOUT)
pair_socket.setsockopt(nnpy.SOL_SOCKET, nnpy.RCVTIMEO, config.C_RECV_TIMEOUT)

其中的C_SEND_TIMEOUTC_RECV_TIMEOUT我都设置为200即200ms,需要根据不同情况进行设置。

最大的收获

说实话这一次写(写代码的写,不是写本文的写),私以为劳动量还是挺大的。


看commit时间才知道被curses耽误了许久

以前写一些分析、数据爬取或者CV类的系统动辄就是千行以上,而这次的整个代码规模估计没有超过500行但是依然觉得劳动量比之前写过的都大,无外乎是因为以前写的项目其实是冗余太多、dulicated code fragment太多、结构设计不好的项目罢了,那时的perfect这时看来只能是normal。而现在,懂得了控制规模、优化代码,更重要的一点是:代码项目一定要有结构,并且尽量把这个结构做到最好。在后期规模扩大的时候感觉非常明显,结构设计不好,就只能空有一丝重构的冲动了:

对,只能是空有——冲动是因为觉得现在版本写的太烂;空有是重构后未必能写的没这么烂所以又不敢动,你能保证这一版本用来绕过大部分bugs的代码不会在你重构的时候卡擦卡擦给KO了?

不要轻易重构,绝对真理。

回顾

讲了这么多闲的,接着讲这次的journery of curses & nanomsg programming

目录结构

先来讲一下目录分工,使用命令tree -h -I __pycache__

.
├── [2.4K] chat.py
├── [4.0K] conf
│ └── [2.6K] config.py
├── [4.0K] logs
├── [4.0K] modules
│ ├── [ 966] common.py
│ ├── [4.0K] communication
│ │ ├── [ 122] __init__.py
│ │ └── [5.2K] nanomsg_pair.py
│ ├── [ 121] __init__.py
│ ├── [2.5K] logger.py
│ ├── [1.3K] tools.py
│ └── [4.0K] ui
│ ├── [ 576] cmd.py
│ ├── [10.0K] curses.py
│ ├── [ 122] __init__.py
│ └── [ 23K] windows.py
└── [4.0K] tools
├── [ 241] curses_demo_client.py
├── [ 241] curses_demo_server.py
└── [ 364] find_key_name.py

6 directories, 15 files

整个程序目录分为5个部分:配置、日志、模块、工具和程序入口,

分别对应conf、logs、modules、tools和chat.py,使用方法还是跟早期版本一样,运行程序入口chat.py,并使用子命令对号入座,如果一切配置都没问题,那么就顺利进入curses产生的交互界面了:

$ python3 chat.py bind tcp 127.0.0.1:2020
进入curses产生的交互界面

按Ctrl + D退出时只显示:

$ python3 chat.py bind tcp 127.0.0.1:2020
comm module stopped:1

则表示成功关闭了nanomsg,这点非常重要,因为如果没有成功关闭将会造成资源占用。

(勘误:图中的帮助Ctrl- ^D后来发现有误,^D就已经代表了Ctrl + D了)

Code Level1

因为具体变动是在子命令的绑定函数,我们来看看子命令的绑定函数现在是神马样子:

子命令的绑定函数

可以见到这里我都用了一个boot_loader4curses()的方法来接替执行,区别在于两者参数is_server的值,Ctrl + B接着看boot_loader4curses():

boot_loader4curses()源码

这里看到里面初始化了一个PairObject类为comm_module,在其start()方法中我们看到根据is_server的值进行了bindconnect之间的选择:

start()方法是establish()方法的异常捕捉版本

establish()方法

接着回到boot_loader4curses(),在初始化完成以后,我新建了一个chat_logs列表变量,用于curses的界面显示与comm_module之间的数据共享(无奈之举,想用管道但是不会),comm_module需要将接收到的消息存入chat_logs中让curses去显示在屏幕上,所以在这里用enable_recv_loop()方法启动了一个线程来接收消息;相反,发送的消息却不一定要经过comm_module,因为键盘输入一开始就是curses在接管,因此不必将发送循环也外联到chat_logs上,直接连接到curses中即可,从而非常正常+正确地落实了从curses向comm_module下达发送消息指令

image-20200214170536492.png

然后该提前准备的都准备好了,就可以启动curses界面了,即239行的wrapper函数,这是curses的内置函数,通过from curses import wrapper引入,用途是简化一些初始化操作,什么cbreak()、noecho()、keypad()这些原先都是需要预先设置,并且在完毕以后按着镜像顺序归零。只要程序有error,结束以后命令行界面就是一顿乱飞如:

不用wrapper然后乱飞的样子

而使用了wrapper以后则会优雅地返回出错详细,虽然真正到curses时它报的错误贯彻了C的风格言简意赅没什么用但至少终端还健在:


这就比较友好了,相对来说

上述的测试代码如下,注释到最后两行中的任意一行以切换测试内容:

# _*_coding:utf-8 _*_
# @Time    : 2020/2/14 17:19
# @Author  : Shek 
# @FileName: fuzz_test.py
# @Software: PyCharm
import curses
from curses import wrapper


def main(stdscr):
    # Clear screen
    stdscr.clear()

    # This raises ZeroDivisionError when i == 10.
    for i in range(0, 11):
        v = i - 10
        stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10 / v))

    stdscr.refresh()
    stdscr.getkey()


def run_with_wrapper():
    wrapper(main)


def run_without_wrapper():
    std_scr = curses.initscr()
    curses.noecho()
    curses.cbreak()
    std_scr.keypad(1)

    main(stdscr=std_scr)

    std_scr.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()


run_with_wrapper()
# run_without_wrapper()

好了wrapper()里的函数放在后面专门讲,先接着往下。我们看到:

comm_module.enable_recv_loop()
comm_module.start_recv_loop(chat_var=chat_logs)
wrapper(main4curses_wrapper, comm_module, chat_logs)
comm_module.stop()  # always remember to call this after wrapper

在wrapper出来以后,一定要记得前面说的stop掉comm_module,不然地址资源不释放,你发现你的chrome都上不了网只能玩小恐龙,而微信、QQ却都还能用这里之所以不在wrapper里面call这个stop()方法是因为在wrapper里面的所有print在你出来以后都是看不见的,即使在还在wrapper里面享用着curses界面时也看不见因为那时候curses已经在画画了而你的print早就被cover了。而能否stop掉comm_module又比较重要,所以我把这个放在外面,可以明确知道是否正确关闭了nanomsg。

以上是我认为的第一层结构,其特点可以梳理如下:

特点 说明
入口比较简单 两个子命令绑定在boot_loader4curses()上
相互调用比较少 单方向调用,基本没有涉及对象间交互调用
后续关联单一、清晰 boot_loader4curses()内的关键点非常清晰:就是239行的wrapper()
关联对象、函数尚未变复杂 只剩下main4curses_wrapper()和comm_module

接下的code level2可以大胆地从wrapper()入手,之前固化的代码片段不会因为wrapper()中下一步发展而有大改动。

Code level2

(有人问我的截图里怎么注释都是英文……我回答一下:只是为了方便、简洁、不用调输入法嗖地一下就好了哈哈)

未完待续...

Structure Summary

下面用表格给大家梳理了一下:

名称 位置 类型 说明
chat.py . file 入口代码:提供命令行子命令选项与用户交互,而后进入curses交互
conf . directory 配置目录:存放配置文件(默认为config.py)
modules . directory 模块目录:存放通信、界面显示和日志记录等模块代码,用于调用
tools . directory 工具目录:存放用于测试的一键运行脚本,比如获取按键ASCII及其curses按键名等
logs . directory 日志保存目录:用于早期版本,近期版本因为引入了curses暂时未集成logger模块,故logs目录为空,暂无实际使用意义。
config.py ./conf file 默认配置文件,各常量均以大写命名,具有可读性,如:C_SHOW_POSITION_SIZE = True,C表示给curses相关函数使用,SHOW_POSITION_SIZE代表是否在窗口栏显示定位坐标和窗口大小。
common.py ./modules file 通用模块。
函数:
cn_count()
current_datetime()
logger.py ./modules file 日志记录模块,用于同时记录日志到本地文件和终端。
类:Logger
tools.py ./modules file 工具函数模块,放置了tools下脚本需要用到的函数。
函数:
print_key_name()
curses_ui_test_server()
curses_ui_test_client()
ui ./modules directory 界面显示子模块目录。
communication ./modules directory 通信子模块目录。
cmd.py ./modules/ui file 子命令模块,包含两个子命令的调用函数。
函数:
sub_cmd_bind()
sub_cmd_connect()
windows.py ./moudles/ui file 窗口模块,用于创建curses窗口布局,移除了原生curses坐标系统先y后x的反人类设定,更加易于使用。
类:
CreateWindow
StatusWindow
ChatWindow
SendWindow
DebugWindow
HelpWindow
curses.py ./modules/ui file curses启动模块,用于辅助启动curses。
函数:
process_chinese_characters()
color_pair_configure()
pre_configure()
key_pressed_solution()
main4curses_wrapper()
boot_loader4curses()
nanomsg_pair.py ./module/
communication
file nanomsg(pair)通信模块,用于实际执行网络数据通信,优化了原生C-nanomsg的蜜汁报错,提高执行效率。
类:
PairObject

改进

完善模块通用性、易用性,移植到Qt5和PyQt5上。

本系列其他文章:

内容 文章地址 说明
准备 用Python操作nanomsg(一)——准备 2020.2.7更新
PipeLine 用Python操作nanomsg(二)——PipeLine 2020.2.7更新
PushPub 用Python操作nanomsg(三)——PubSub 2020.2.8更新
ReqRep 用Python操作nanomsg(五)——ReqRep 未开始
Survey 用Python操作nanomsg(六)——Survey 未开始
Bus 用Python操作nanomsg(七)——Bus 未开始
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341