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


最初的“夢想”實現(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_TIMEOUT和C_RECV_TIMEOUT我都設置為200即200ms,需要根據(jù)不同情況進行設置。
最大的收獲
說實話這一次寫(寫代碼的寫,不是寫本文的寫),私以為勞動量還是挺大的。

以前寫一些分析、數(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

按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)在是神馬樣子:

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

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


接著回到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ā)送消息指令。

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

而使用了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 | 未開始 |