先上仓库地址 https://github.com/r00t1900/nano-chat.git
序
按照之前的节奏,第四章应该就是介绍PAIR模式的用法并且顺利更新之前的LAN-Chat Program后就可以完事儿了。今天这章到今天才完成,实属是自己一根筋,想着把curses利用进来,让命令行程序的可操作性更好一些。
最初的“梦想”实现了,就是花的时间太长了,看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_TIMEOUT
和C_RECV_TIMEOUT
我都设置为200即200ms,需要根据不同情况进行设置。
最大的收获
说实话这一次写(写代码的写,不是写本文的写),私以为劳动量还是挺大的。
以前写一些分析、数据爬取或者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
按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():
这里看到里面初始化了一个PairObject类为comm_module,在其start()方法中我们看到根据is_server
的值进行了bind
和connect
之间的选择:
接着回到boot_loader4curses(),在初始化完成以后,我新建了一个chat_logs列表变量,用于curses的界面显示与comm_module之间的数据共享(无奈之举,想用管道但是不会),comm_module需要将接收到的消息存入chat_logs中让curses去显示在屏幕上,所以在这里用enable_recv_loop()方法启动了一个线程来接收消息;相反,发送的消息却不一定要经过comm_module,因为键盘输入一开始就是curses在接管,因此不必将发送循环也外联到chat_logs上,直接连接到curses中即可,从而非常正常+正确地落实了从curses向comm_module下达发送消息指令。
然后该提前准备的都准备好了,就可以启动curses界面了,即239行的wrapper函数,这是curses的内置函数,通过from curses import wrapper
引入,用途是简化一些初始化操作,什么cbreak()、noecho()、keypad()这些原先都是需要预先设置,并且在完毕以后按着镜像顺序归零。只要程序有error,结束以后命令行界面就是一顿乱飞如:
而使用了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 | 未开始 |