美文网首页
用Python操作nanomsg(四)——Pair&curses

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

作者: 钢琴师2005 | 来源:发表于2020-02-14 20:34 被阅读0次

    先上仓库地址 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 未开始

    相关文章

      网友评论

          本文标题:用Python操作nanomsg(四)——Pair&curses

          本文链接:https://www.haomeiwen.com/subject/pqvjfhtx.html