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

先上倉庫地址 https://github.com/r00t1900/nano-chat.git

按照之前的節(jié)奏,第四章應該就是介紹PAIR模式的用法并且順利更新之前的LAN-Chat Program后就可以完事兒了。今天這章到今天才完成,實屬是自己一根筋,想著把curses利用進來,讓命令行程序的可操作性更好一些。


tmux左右分屏展示程序運行的樣子
nano-chat

最初的“夢想”實現(xiàn)了,就是花的時間太長了,看git的提交記錄才發(fā)現(xiàn)在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,需要根據(jù)不同情況進行設置。

最大的收獲

說實話這一次寫(寫代碼的寫,不是寫本文的寫),私以為勞動量還是挺大的。


看commit時間才知道被curses耽誤了許久

以前寫一些分析、數(shù)據(jù)爬取或者CV類的系統(tǒng)動輒就是千行以上,而這次的整個代碼規(guī)模估計沒有超過500行但是依然覺得勞動量比之前寫過的都大,無外乎是因為以前寫的項目其實是冗余太多、dulicated code fragment太多、結構設計不好的項目罷了,那時的perfect這時看來只能是normal。而現(xiàn)在,懂得了控制規(guī)模、優(yōu)化代碼,更重要的一點是:代碼項目一定要有結構,并且盡量把這個結構做到最好。在后期規(guī)模擴大的時候感覺非常明顯,結構設計不好,就只能空有一絲重構的沖動了:

對,只能是空有——沖動是因為覺得現(xiàn)在版本寫的太爛;空有是重構后未必能寫的沒這么爛所以又不敢動,你能保證這一版本用來繞過大部分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產(chǎn)生的交互界面了:

$ python3 chat.py bind tcp 127.0.0.1:2020
進入curses產(chǎn)生的交互界面

按Ctrl + D退出時只顯示:

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

則表示成功關閉了nanomsg,這點非常重要,因為如果沒有成功關閉將會造成資源占用。

(勘誤:圖中的幫助Ctrl- ^D后來發(fā)現(xiàn)有誤,^D就已經(jīng)代表了Ctrl + D了)

Code Level1

因為具體變動是在子命令的綁定函數(shù),我們來看看子命令的綁定函數(shù)現(xiàn)在是神馬樣子:

子命令的綁定函數(shù)

可以見到這里我都用了一個boot_loader4curses()的方法來接替執(zhí)行,區(qū)別在于兩者參數(shù)is_server的值,Ctrl + B接著看boot_loader4curses():

boot_loader4curses()源碼

這里看到里面初始化了一個PairObject類為comm_module,在其start()方法中我們看到根據(jù)is_server的值進行了bindconnect之間的選擇:

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

establish()方法

接著回到boot_loader4curses(),在初始化完成以后,我新建了一個chat_logs列表變量,用于curses的界面顯示與comm_module之間的數(shù)據(jù)共享(無奈之舉,想用管道但是不會),comm_module需要將接收到的消息存入chat_logs中讓curses去顯示在屏幕上,所以在這里用enable_recv_loop()方法啟動了一個線程來接收消息;相反,發(fā)送的消息卻不一定要經(jīng)過comm_module,因為鍵盤輸入一開始就是curses在接管,因此不必將發(fā)送循環(huán)也外聯(lián)到chat_logs上,直接連接到curses中即可,從而非常正常+正確地落實了從curses向comm_module下達發(fā)送消息指令。

image-20200214170536492.png

然后該提前準備的都準備好了,就可以啟動curses界面了,即239行的wrapper函數(shù),這是curses的內置函數(shù),通過from curses import wrapper引入,用途是簡化一些初始化操作,什么cbreak()、noecho()、keypad()這些原先都是需要預先設置,并且在完畢以后按著鏡像順序歸零。只要程序有error,結束以后命令行界面就是一頓亂飛如:

不用wrapper然后亂飛的樣子

而使用了wrapper以后則會優(yōu)雅地返回出錯詳細,雖然真正到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()里的函數(shù)放在后面專門講,先接著往下。我們看到:

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,不然地址資源不釋放,你發(fā)現(xiàn)你的chrome都上不了網(wǎng)只能玩小恐龍,而微信、QQ卻都還能用這里之所以不在wrapper里面call這個stop()方法是因為在wrapper里面的所有print在你出來以后都是看不見的,即使在還在wrapper里面享用著curses界面時也看不見因為那時候curses已經(jīng)在畫畫了而你的print早就被cover了。而能否stop掉comm_module又比較重要,所以我把這個放在外面,可以明確知道是否正確關閉了nanomsg。

以上是我認為的第一層結構,其特點可以梳理如下:

特點 說明
入口比較簡單 兩個子命令綁定在boot_loader4curses()上
相互調用比較少 單方向調用,基本沒有涉及對象間交互調用
后續(xù)關聯(lián)單一、清晰 boot_loader4curses()內的關鍵點非常清晰:就是239行的wrapper()
關聯(lián)對象、函數(shù)尚未變復雜 只剩下main4curses_wrapper()和comm_module

接下的code level2可以大膽地從wrapper()入手,之前固化的代碼片段不會因為wrapper()中下一步發(fā)展而有大改動。

Code level2

(有人問我的截圖里怎么注釋都是英文……我回答一下:只是為了方便、簡潔、不用調輸入法嗖地一下就好了哈哈)

未完待續(xù)...

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相關函數(shù)使用,SHOW_POSITION_SIZE代表是否在窗口欄顯示定位坐標和窗口大小。
common.py ./modules file 通用模塊。
函數(shù):
cn_count()
current_datetime()
logger.py ./modules file 日志記錄模塊,用于同時記錄日志到本地文件和終端。
類:Logger
tools.py ./modules file 工具函數(shù)模塊,放置了tools下腳本需要用到的函數(shù)。
函數(shù):
print_key_name()
curses_ui_test_server()
curses_ui_test_client()
ui ./modules directory 界面顯示子模塊目錄。
communication ./modules directory 通信子模塊目錄。
cmd.py ./modules/ui file 子命令模塊,包含兩個子命令的調用函數(shù)。
函數(shù):
sub_cmd_bind()
sub_cmd_connect()
windows.py ./moudles/ui file 窗口模塊,用于創(chuàng)建curses窗口布局,移除了原生curses坐標系統(tǒng)先y后x的反人類設定,更加易于使用。
類:
CreateWindow
StatusWindow
ChatWindow
SendWindow
DebugWindow
HelpWindow
curses.py ./modules/ui file curses啟動模塊,用于輔助啟動curses。
函數(shù):
process_chinese_characters()
color_pair_configure()
pre_configure()
key_pressed_solution()
main4curses_wrapper()
boot_loader4curses()
nanomsg_pair.py ./module/
communication
file nanomsg(pair)通信模塊,用于實際執(zhí)行網(wǎng)絡數(shù)據(jù)通信,優(yōu)化了原生C-nanomsg的蜜汁報錯,提高執(zhí)行效率。
類:
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 未開始
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容