2021/AUG/11
TCPソケットを使うときに、1対1の接続だけならncコマンドで十分な感じなのですが、、、
サーバで複数のクライアントの相手をさせたい時。
Pythonのsocketモジュールを使えば、楽チンにTCPソケットのサーバが作れますね。
どんどんサーバを作ってみて、いつも気になるのはポート番号の割り当て。
たわいもないサーバを作って、限りあるポート番号資源をホイホイと消費して、良いものだろうか?
当然ですが、クライアント側からは、サーバのポート番号にアクセスしにいくので、 クライアント側で、サーバのポート番号を知ってる必要があります。
ポート番号12345番とか適当に使ってたら、きっと他の人とぶつかったりするよな〜。
ポート番号を1つだけ使って、ベースのサーバを起動しておいて、 そのベースサーバに、文字列のIDで、個別にサーバを登録できないものだろうか?
クライアントはポート番号と文字列のIDの組で、サーバに接続しにいくような感じで。
ポート番号1つだけ使って、ベースサーバを起動しておきます。
サーバとして登録したい人が、ベースサーバにクライアントとして接続します。
接続すると、登録したい人は、サーバ登録したい旨のコマンドと、IDの文字列を送信します。
ベースサーバの内部ではソケットとID文字列を登録。
サーバに接続したいクライアントは、ベースサーバにクライアントとして接続して、 登録されているID文字列を送信します。
ベースサーバは、登録されたID文字列のサーバに、クライアントとの接続用のパスワードを生成して、送信します。
ベースサーバからのパスワードを受け取ったサーバは、 さらに追加でもう一度ベースサーバに接続し、そちらのソケットにパスワードを送り返します。
これが、従来のアクセプトに相当します。
パスワードを受け取ったベースサーバでは、 サーバからの接続してきてるソケット、 クライアントから接続してきてるソケットを持ってます。
この2つをパイプで接続したかのように、 読みだしては書き出す処理を、双方向で行なって面倒を見てあげれば良いのではないかと。
sock_ut.py
#!/usr/bin/env python
import sys
import socket
import empty
import thr
import fio_ut
import dbg
add_tail = lambda s, add: s + add
cut_tail = lambda s, cut: s[ : -len( cut ) ] if s.endswith( cut ) else s
close = fio_ut.close
def port_conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
cs.connect( ( host, port ) )
except:
return None
fio_ut.set_fs_sock( cs )
return cs
def send_str( sc, s ):
s = add_tail( s, '\n' )
return fio_ut.p_s( sc, s )
def recv_str_line( sc ):
s = fio_ut.g_s_line( sc )
return cut_tail( s, '\n' )
def recv_str_all( sc ):
s = fio_ut.g_s_all( sc )
return cut_tail( s, '\n' )
def pipe_loop( sc_from, sc_to ):
while True:
b = fio_ut.g_b( sc_from )
if not b:
break
if not fio_ut.p_b( sc_to, b ):
break
# srv=name,port=xxx,host=xxx
# srv=name,id=xxx,port=xxx,host=xxx
# srv=name,id=xxx
# get_srvs
# kill=name
# kill_base_srv
# name
def get_dic( s ):
if '=' not in s:
return {}
return dict( map( lambda t: t.split( '=' ), s.split( ',' ) ) )
is_keys_dic = lambda d, ks: len( d ) == len( ks ) and all( map( lambda k: k in d, ks ) )
is_key_dic = lambda d, k: len( d ) == 1 and k in d
is_srv_dic = lambda d: is_keys_dic( d, [ 'srv', 'port', 'host' ] )
is_kill_dic = lambda d: is_key_dic( d, 'kill' )
is_accept_dic = lambda d: is_keys_dic( d, [ 'srv', 'id', 'port', 'host' ] )
def get_port_host_from_dic( d ):
port = int( d.get( 'port' ) )
host = d.get( 'host' )
return ( port, host )
srvs = {}
reqs = {}
def base_th_srv( sc, d ):
name = d.get( 'srv' )
if name in srvs:
return
( port, host ) = get_port_host_from_dic( d )
q = thr.que_new()
srvs[ name ] = q
dbg.out( 'srv {} start'.format( name ) )
while True:
s = q.get()
if s == 'kill':
break
s += ',port={},host={}'.format( port, host )
if not send_str( sc, s ):
break
srvs.pop( name )
dbg.out( 'srv {} quit'.format( name ) )
def base_th_accept( sc, s ):
s = ','.join( s.split( ',' )[ : -2 ] )
if s not in reqs:
return
( sc2, ev ) = reqs.get( s )
ev2 = thr.event_new()
reqs[ s ] = [ sc, ev2 ]
ev.set()
ev2.wait()
if not send_str( sc, 'ok' ):
return
pipe_loop( sc, sc2 )
def base_th_connect( sc, s ):
name = s
q = srvs.get( name )
id = sc.fileno()
s = 'srv={},id={}'.format( name, id )
ev = thr.event_new()
reqs[ s ] = [ sc, ev ]
q.put( s )
ev.wait()
[ sc2, ev2 ] = reqs.pop( s )
ev2.set()
if not send_str( sc, 'ok' ):
return
pipe_loop( sc, sc2 )
def base_th( port, sc, ev_quit ):
fio_ut.set_fs_sock( sc )
s = recv_str_line( sc )
if not s:
close( sc )
return
d = get_dic( s )
if is_srv_dic( d ):
base_th_srv( sc, d )
elif is_accept_dic( d ):
base_th_accept( sc, s )
elif is_kill_dic( d ):
name = d.get( 'kill' )
if name in srvs:
q = srvs.get( name )
q.put( 'kill' )
elif s == 'get_srvs':
r = '\n'.join( srvs.keys() )
send_str( sc, r )
elif s == 'kill_base_srv':
ev_quit.set()
send_str( sc, 'next' )
elif s in srvs:
base_th_connect( sc, s )
close( sc )
def ping_th():
id = 'ping'
for ( name, q ) in srvs.items():
s = 'srv={},id={}'.format( name, id )
q.put( s )
def base_srv( port ):
th_p = thr.loop_new( ping_th, wait_tmout=1.0 )
thr.ths.start( th_p )
ss = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
ss.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
ss.bind( ( '', port ) )
ss.listen( 5 )
ev_quit = thr.event_new()
while not ev_quit.wait( 0 ):
( cs, adr ) = ss.accept()
th = thr.th_new( base_th, ( port, cs, ev_quit ) )
thr.ths.start( th )
th_p.quit_ev.set()
dbg.out( 'base_srv quit' )
def server( port, name, host='localhost' ):
ss = port_conn( port, host )
if not ss:
return None
s = 'srv={},port={},host={}'.format( name, port, host )
if not send_str( ss, s ):
close( ss )
return None
return ss
def accept_recv_ping( ss ):
s = recv_str_line( ss )
if not s:
return None
d = get_dic( s )
if not is_accept_dic( d ):
return None
if d.get( 'id' ) == 'ping':
return 'ping'
return ( s, d )
def accept_recv( ss ):
r = None
while True:
r = accept_recv_ping( ss )
if r != 'ping':
break
return r # None or ( s, d )
def accept_conn( r ):
if not r:
return None
( s, d ) = r
( port, host ) = get_port_host_from_dic( d )
cs = port_conn( port, host )
if not send_str( cs, s ):
close( cs )
return None
s = recv_str_line( cs )
if s != 'ok':
close( cs )
return None
return cs
def accept( ss ):
r = accept_recv( ss )
return accept_conn( r )
def srv_loop( port, name, th_func, host='localhost' ):
ss = server( port, name, host )
if not ss:
return
while True:
cs = accept( ss )
if not cs:
break
th = thr.th_new( th_func, ( cs, ) )
thr.ths.start( th )
close( ss )
def srv_func_new( cb, use_b=False, use_all=False ):
send = fio_ut.p_s
recv = fio_ut.g_s
if use_all:
recv = fio_ut.g_s_all
if use_b:
send = fio_ut.p_b
recv = fio_ut.g_b
if use_all:
recv = fio_ut.g_b_all
send_ = send
recv_ = recv
if not use_b:
send = lambda f, s: send_( f, add_tail( s, '\n' ) )
recv = lambda f: cut_tail( recv_( f ), '\n' )
def func( sc ):
while True:
s = recv( sc )
if not s:
break
r = cb( s )
if not r:
break
if not send( sc, r ):
break
close( sc )
return empty.new( locals() )
def srv_loop_cmd( host, port, name, cmd, mode ):
def th_func( sc ):
proc = fio_ut.proc_new( cmd )
def cb( b ):
fio_ut.p_b( proc.fo, b )
if mode == 'all':
fio_ut.close( proc.fo )
return fio_ut.g_b_all( proc.fi )
srv_func = srv_func_new( cb, use_b=True, use_all=True )
srv_func.func( sc )
proc.stop()
srv_loop( port, name, th_func, host )
def connect( port, name, host='localhost' ):
cs = port_conn( port, host )
if not cs:
return None
if not send_str( cs, name ):
close( cs )
return None
s = recv_str_line( cs )
if s != 'ok':
close( cs )
return None
return cs
def get_srvs( host, port ):
cs = port_conn( port, host )
if not cs:
return
send_str( cs, 'get_srvs' )
r = recv_str_all( cs )
close( cs )
return r.split( '\n' )
def kill_srv( port, name, host='loaclhost' ):
cs = port_conn( port, host )
if not cs:
return
s = 'kill=' + name
#dbg.out( s )
send_str( cs, s )
close( cs )
def kill_base_srv( port, host='loaclhost' ):
cs = port_conn( port, host )
if not cs:
return
dbg.out( 'kill_base_srv' )
send_str( cs, 'kill_base_srv' )
r = recv_str_line( cs )
close( cs )
cs = port_conn( port, host ) # for accept loop
if cs:
close( cs )
def help():
lst = [
'\\',
'base [ port=13579 ]',
'srv [ host=localhost ][ port=13579 ] upper',
'srv [ host=localhost ][ port=13579 ] eval',
'srv [ host=localhost ][ port=13579 ] srv_name cmd arg ...',
'srv -a [ host=localhost ][ port=13579 ] srv_name cmd arg ... ( all mode )',
'cli [ host=localhost ][ port=13579 ] upper word word word ...',
'cli [ host=localhost ][ port=13579 ] eval word word word ...',
'cli [ host=localhost ][ port=13579 ] srv_name',
'cli -a [ host=localhost ][ port=13579 ] srv_name ( all mode )',
'srvs [ host=localhost ][ port=13579 ]',
'kill_base [ host=localhost ][ port=13579 ]',
'kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )'
]
dbg.help_exit( '\n'.join( lst ) )
def run_cli( sc, mode ):
( fi, fo ) = fio_ut.set_fs_sys_stdio()
def cnv_out( b ):
if not fio_ut.p_b( sc, b ):
return False
b = fio_ut.g_b_all( sc )
if not b:
return False
if not fio_ut.p_b( fo, b ):
return False
return True
if mode == 'all':
buf = b''
while True:
b = fio_ut.g_b_all( fi )
if not b:
break
buf += b
cnv_out( buf )
return
# mode == 'line'
while True:
b = fio_ut.g_b_line( fi )
if not b:
break
if not cnv_out( b ):
break
def run():
if len( sys.argv ) < 2:
help()
argv = sys.argv[ 1 : ]
def pop():
if len( argv ) <= 0:
help()
return argv.pop( 0 )
def popk( k, dv=None ):
for i in range( len( argv ) ):
if argv[ i ].startswith( k ):
return argv.pop( i )[ len( k ) : ]
return dv
def pop_mode():
if argv[ 0 ] == '-a':
pop()
return 'all'
return 'line'
host = popk( 'host=', 'localhost' )
port = int( popk( 'port=', '13579' ) )
names = {
'upper': lambda s: s.upper(),
'eval': lambda s: str( eval( s ) ),
}
cmd = pop()
if cmd == 'base':
base_srv( port )
elif cmd == 'srv':
mode = pop_mode()
name = pop()
if name in names:
cb = names.get( name )
srv_func = srv_func_new( cb )
srv_loop( port, name, srv_func.func, host )
else:
cmd = ' '.join( argv )
srv_loop_cmd( host, port, name, cmd, mode )
elif cmd == 'cli':
mode = pop_mode()
lst = get_srvs( host, port )
name = pop()
if name not in lst:
return
sc = connect( port, name, host )
if not sc:
return
if name in names:
while len( argv ) > 0:
w = pop()
if not send_str( sc, w ):
break
r = recv_str_line( sc )
if not r:
break
dbg.out( '{} --> {}'.format( w, r ) )
else:
run_cli( sc, mode )
close( sc )
elif cmd == 'srvs':
lst = get_srvs( host, port )
dbg.out( '\n'.join( lst ) )
elif cmd == 'kill':
name = pop()
kill_srv( port, name, host )
elif cmd == 'kill_base':
kill_base_srv( port, host )
if __name__ == "__main__":
run()
# EOF
pythonのユーティリティ・プログラム 2020冬 の使用を前提としてます。
kon_ut は kon_pageのpythonモジュールのインストール からインストールできます。
動作確認用に
を組み込んであります。
$ ./sock_ut.py Usage: ./sock_ut.py \ port base port srv upper port srv eval port cli upper word word word ... port cli eval word word word ... port kill_base
などと、そっけないヘルプ表示。
まずは、ポート番号を指定してベースサーバを起動。
$ ./sock_ut.py 13579 base &
ベースサーバに、2つのサーバを登録
$ ./sock_ut.py 13579 srv upper & $ ./sock_ut.py 13579 srv eval &
upperサーバにクライアントをつないで文字列を送信して結果を受信
$ ./sock_ut.py 13579 cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE
evalサーバにクライアントをつないで文字列を送信して結果を受信
$ ./sock_ut.py 13579 cli eval 1+1 2*3 100.0/3 1+1 --> 2 2*3 --> 6 100.0/3 --> 33.3333333333
ベースサーバを終了
$ ./sock_ut.py 13579 kill_base kill_base_srv base_srv quit [1] Done ./sock_ut.py 13579 base $ [2]- Done ./sock_ut.py 13579 srv upper [3]+ Done ./sock_ut.py 13579 srv eval $
ソースコードの上から順番に。
便利部品的な関数。
def port_conn( port, host='localhost' ): cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) try: cs.connect( ( host, port ) ) except: return None return cs
port番号にconnectで繋ぎにいって、ソケットを返します。
失敗するとNoneを返します。
def send_str( sc, s ): bt = s.encode( 'utf-8' ) try: sc.sendall( bt ) except: return False return True
ソケットscに文字列sを送信します。
成功、失敗をboolで返します。
def recv_str( sc ): bufmax = 16 * 1024 bt = sc.recv( bufmax ) s = bt.decode( 'utf-8' ) return s
ソケットscから最大で16Kバイトの受信した文字列を返します。
ソケットがクローズされてるなど、受信できないと空文字列 '' が返ります。
def pipe_loop( sc_from, sc_to ): while True: s = recv_str( sc_from ) if not s: break if not send_str( sc_to, s ): break
ソケットsc_fromから受信した文字列を、 ソケットsc_toに送信する動作を繰り返します。
ソケットがクローズされるなどすると、呼び出し元に返ります。
ベースサーバに接続した直後に送信する文字列のコマンドや、 ベースサーバから送られる文字列のコマンドの仕様です。
# srv=name # port=xxx,srv=name,id=xxx # kill_base_srv # name
サーバとして登録したい場合は、サーバの名前を name として'srv=name' の形式の文字列を送信します。
上記の文字列でベースサーバにサーバ登録した後、 クライアントから接続要求があると、 ベースサーバからサーバに 'port=xxx,srv=name,id=xxx' の形式の文字列を送ります。
仕組み概要 で、「パスワード」と表現してたものです。
サーバはベースサーバから文字列が送られてくると、 サーバにもう1つソケットを接続して、そちらに、文字列をそのまま送り返します。 ベースサーバ側でクライアントと接続する処理が完了すると、 新たに接続した方のソケットに、ベースサーバから'ok'の文字列が送られてきます。
これが、いわゆるacceptに相当する動作になります。
起動してるベースサーバを終了させたいときは、ベースサーバに接続して、 'kill_base_srv' の文字列を送り込みます。
ベースサーバ内部でacceptして、この文字列をスレッド処理すると、内部で終了のイベントフラグが立ちます。 このままでは、ベースサーバのメインスレッドは、残念ながら再度acceptで待つため、 フラグが立ってから、もう一度ベースサーバに接続しにいって、acceptから抜けさせないと、真の終了となりません。
nameで登録してるサーバにクライアントとして接続したい場合は、 ベースサーバに接続した後、'name' の文字列を送信します。 ベースサーバから'ok'の文字列が送られてくると、サーバ側からのacceptが完了し、サーバへの接続が完了します。
def get_dic( s ): if '=' not in s: return {} return dict( map( lambda t: t.split( '=' ), s.split( ',' ) ) )
ベースサーバが受信した文字列 s を辞書の形式にして返します。
サーバの登録依頼や、accept 時のパスワードの場合は、キー 'srv', 'port', 'id' などを含む辞書を返します。
それ以外の場合は、空の辞書 {} を返します。
def is_srv_dic( d ): return len( d ) == 1 and 'srv' in d
上記 get_dic( s ) で返った辞書 d を指定します。
サーバの登録依頼の形式の辞書の場合は True を、 それ以外の場合はFalse を返します。
def is_accept_dic( d ): ks = [ 'port', 'srv', 'id' ] return len( d ) == len( ks ) and all( map( lambda k: k in d, ks ) )
上記 get_dic( s ) で返った辞書 d を指定します。
accept時のパスワードの形式の辞書の場合は True を、 それ以外の場合はFalse を返します。
srvs = {} reqs = {}
辞書 srvs には、サーバ登録した名前をキーとして、値キューが登録されます。
キューに文字列をputすると、accept待ちしてるサーバ側のソケットに向けて、 その文字列が送信されます。
辞書 reqs には、accept処理で使用する 'port=xxx,srv=name,id=xxx' 形式の 文字列をキーとして、イベントとソケットの組みが値として登録されます。
クライアントからベースサーバに接続要求がきたときに、 まず、パスワード形式の文字列を生成し、 クライアント側のソケットと、生成したイベントの組みを登録しつつ、 サーバ側のキューにパスワード形式の文字列を書き込み、 クライアント側は、イベントを待ちます。
キューの文字列は送信され、サーバ側のaccept処理で、 ベースサーバに新たな接続があり、パスワードが送り返されてきます。
ベースサーバは、辞書 reqs から、クライアント側のソケットを見つけ、 組みになってるイベントをセットして、クライアントを起こします。
が、その前に、サーバ側の新たなソケットと新たなイベントを組みにして、 辞書 reqs のパスワードのキーに値を更新してから、 クライアント側を起こします。 サーバ接続側は、辞書 reqs に登録しなおした方のイベントを待ちます。
起こされたクライアント側は、辞書 reqs からサーバ側のソケットを見つけ、 組みのイベントをセットして、サーバ側を起こします。
これで、クライアント側、サーバ側、ともに違いのソケットを取得した状態になります。
少々順番がアレしますが、、、
def base_srv( port ): ss = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) ss.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) ss.bind( ( '', port ) ) ss.listen( 5 ) ev_quit = thr.event_new() while not ev_quit.wait( 0 ): ( cs, adr ) = ss.accept() th = thr.th_new( base_th, ( port, cs, ev_quit ) ) thr.ths.start( th ) dbg.out( 'base_srv quit' )
ベースサーバの本来のサーバ処理ループです。
port番号のサーバソケットを生成し、 bind, listen, accept の流れです。
accept したクライアントソケットcs を含めて、 base_th() 関数の引数として、スレッドを生成し実行します。
イベント ev_quit がセットされると、ループを抜けて、 呼び出し側に返ります。
ですが、先述の通り、base_th( port, cs, ev_quit ) がスレッド実行される時は、 メインスレッドは、すでにループを回ってss.accept()で待ってる状態のため、 ev_quit がセットされた後、もう一度クライアントがconnect()してきて、 ss.accept()から出てこなければ、ループを終了できません。
def base_th( port, sc, ev_quit ): s = recv_str( sc ) if not s: sc.close() return d = get_dic( s ) if is_srv_dic( d ): base_th_srv( sc, d ) elif is_accept_dic( d ): base_th_accept( sc, s ) elif s == 'kill_base_srv': ev_quit.set() send_str( sc, 'next' ) elif s in srvs: base_th_connect( port, sc, s ) sc.close()
base_srv() からスレッドを生成して実行する base_th() 関数です。
sc は ベースサーバで accept したソケット、ev_quit は終了ようのイベントです。
accept したソケットから受信したコマンド文字列sを取得。
get_dic() で辞書の形式にして、 サーバからのサーバ登録要求なら、base_th_srv() 関数を呼び出します。
サーバからのaccept要求なら、base_th_accept() 関数を呼び出します。
ベースサーバの終了コマンドなら、ev_quit イベントをセットし、 ソケットにイベントをセットした事を伝えるために、 適当な文字列を送信します。
文字列がそれ以外で、辞書 srvs のキーとして登録された文字列ならば、 クライアントからの接続要求として、 base_th_connect() 関数を呼び出します。
上記いづれかの処理関数で処理を終えて帰ってきた時や、 いづれにも該当しない文字列だった場合、 最後に引数のソケットscをクローズして関数を抜け、 スレッドの処理を終了します。
def base_th_srv( sc, d ): name = d.get( 'srv' ) if name in srvs: return q = thr.que_new() srvs[ name ] = q while True: s = q.get() if not send_str( sc, s ): break
base_th() からサーバの登録処理で呼び出されます。
サーバからベースサーバに送られてきた文字列sから、 get_dic( s )で取得した辞書 d が引数の d です。
d から 登録したいサーバの名前 name を取り出します。
サーバ側にパスワードを送信するためのキューを生成し、 グローバル変数の辞書 srvs に登録します。
以降は、ループで キューから文字列を取り出し、 ベースサーバからサーバ側へと送信します。
サーバ側でソケットがクローズされるなどして、 送信が失敗すると、ループを抜けて終了します。
def base_th_accept( sc, s ): if s not in reqs: return ( sc2, ev ) = reqs.get( s ) ev2 = thr.event_new() reqs[ s ] = [ sc, ev2 ] ev.set() ev2.wait() if not send_str( sc, 'ok' ): return pipe_loop( sc, sc2 )
base_th() からaccept要求処理で呼び出されます。
サーバがベースサーバへと送り返したパスワード形式の文字列が引数のsです。
グローバル変数の辞書 reqs から、クライアント側で登録した、 クライアント側のソケットを sc2 として、 クライアント側が待ってるイベントを ev として取得します。
今度はサーバ側の自分が待つイベントev2と、サーバ側のソケットscを、 辞書 reqs に登録しなおします。
クライアント側で待ってるイベントevをセットして、クライアント側を起こします。 クライアント側が起こしてくれるまで、ev2を待ちます。
クライアント側が起こしてくれたら、サーバ側のソケットに'ok'を送信して通知します。
以降は、サーバ側から送られてきた文字列を、クライアント側に転送しつづけます。
どちらかのソケットがクローズされるなどして、送信、受信が失敗すると、 処理を終えて呼び出し元に返ります。
def base_th_connect( port, sc, s ): name = s q = srvs.get( name ) id = sc.fileno() s = 'port={},srv={},id={}'.format( port, name, id ) ev = thr.event_new() reqs[ s ] = [ sc, ev ] q.put( s ) ev.wait() [ sc2, ev2 ] = reqs.pop( s ) ev2.set() if not send_str( sc, 'ok' ): return pipe_loop( sc, sc2 )
base_th() からクライアントの接続要求で呼び出されます。
文字列sは、クライアント側からベースサーバに送信された、サーバのnameです。
nameのサーバ側のキューを取得します。
accept処理のパスワード形式の文字列を生成します。
サーバ側の処理を待つためのイベントを生成します。
サーバ側に伝えるソケットscとイベントを、 辞書 reqs に登録し、サーバ側のキューに生成したパスワード文字列を書き込み、 送信されるのを待ちます。
サーバ側の処理が終わり起こしてもらうまで、イベントを待ちます。
サーバ側の処理が終わり起こしてもらうと、 辞書 reqs からサーバ側でセットしたソケットとイベントを取り出します。 (もう接続のやりとりは終わりなので、辞書からキーと値は削除します)
サーバ側で待っているイベントev2をセットして、 クライアント側に文字列'ok'を返します。
以降は、クライアント側から送られてきた文字列を、サーバ側に転送しつづけます。
どちらかのソケットがクローズされるなどして、送信、受信が失敗すると、 処理を終えて呼び出し元に返ります。
def server( port, name ): ss = port_conn( port ) if not ss: return None s = 'srv=' + name if not send_str( ss, s ): ss.close() return None return ss
サーバ登録用の関数です。
ベースサーバのport番号と、サーバ登録したい名前nameを指定します。
port_conn( port ) でベースサーバに接続し、 サーバ登録要求コマンドの文字列 ’srv=name' の形式の文字列を送信します。
ベースサーバに接続したソケット ss を返します。
def accept( ss ): s = recv_str( ss ) if not s: return None d = get_dic( s ) if not is_accept_dic( d ): return None port = int( d.get( 'port' ) ) cs = port_conn( port ) if not send_str( cs, s ): cs.close() return None s = recv_str( cs ) if s != 'ok': cs.close() return None return cs
先の server( port, name ) で返る、ベースサーバに接続したソケット ss を、引数で指定します。
ベースサーバにクライアントからnameへの接続要求があると、 ss にパスワード文字列が送られてきます。
recv_str( ss ) で受信した文字列を、パスワード形式の文字列か判定した後、 port_conn() でベースサーバに新たな接続を追加し、 追加した接続のソケットにパスワード文字列を、そのまま送信しかえします。
ベースサーバ内部でクライアントとの接続処理が終わると、 パスワードを送りかえした方のソケットに、ベースサーバから 'ok' が送られてきます。
クライアントと接続が完了したソケットを返します。
def srv_loop( port, name, th_func ): ss = server( port, name ) if not ss: return while True: cs = accept( ss ) if not cs: break th = thr.th_new( th_func, ( cs, ) ) thr.ths.start( th )
上記のserver()とaccept()を使用した、サーバ処理ループの雛形の関数です。
引数は、ベースサーバのport番号、登録したいサーバのname、accept後のソケットを引数としてスレッド実行する関数th_funcです。
server( port, name ) でベースサーバにサーバnameを登録します。
ループ処理で、accept()し、クライアントソケット cs を引数として th_func( cs ) をスレッド実行します。
サーバソケット ss がサーバ側でクローズされるなどして、accept() 結果が失敗すると、 ループを抜けて、呼び出し元に返ります。
def srv_func_new( cb ): def func( sc ): while True: s = recv_str( sc ) if not s: break r = cb( s ) if not send_str( sc, r ): break sc.close() return empty.new( locals() )
先の srv_loop() の引数で指定する th_func を生成するときに、 便利に使える関数です。
引数 cb には、コールバック関数を指定します。
コールバック関数は、文字列1つを引数で受け取り、 結果の文字列を返す関数です。
srv_func_new( cb ) が返すオブジェクトの属性 .func には、 内部関数 func( sc ) がセットされます。
内部関数 func() は引数ソケットを受け取り、 ループ処理でソケットsc から受信文字列を取得し、 コールバック関数 cb( s ) を呼び出し、その結果の文字列を、 ソケットに送り返します。
先の、srv_loop() 関数の引数 th_func の形式の関数になります。
def cb( s ): r = ... return r srv_func = srv_func_new( cb ) th_func = srv_func.func srv_loop( port, name, th_func )
のように使用します。
def connect( port, name ): cs = port_conn( port ) if not cs: return None if not send_str( cs, name ): cs.close() return None s = recv_str( cs ) if s != 'ok': cs.close() return None return cs
クライアント接続用の関数です。
ベースサーバのport番号と、接続したいサーバのnameを指定します。
port_conn( port ) でベースサーバに接続し、 接続したいサーバのnameを送信します。
ベースサーバとサーバ間のaccept処理の完了を待ち、 ベースサーバから'ok'が送られてきたら、 サーバと接続してるソケットを返します。
def kill_base_srv( port ): cs = port_conn( port ) if not cs: return dbg.out( 'kill_base_srv' ) send_str( cs, 'kill_base_srv' ) r = recv_str( cs ) cs.close() cs = port_conn( port ) # for accept loop if cs: cs.close()
ベースサーバを終了させるための関数です。
ベースサーバのport番号を指定します。
port_conn( port ) でベースサーバに接続し、 ベースサーバを終了させるコマンド文字列を送信します。
ベースサーバ内部で、終了イベントのセットが完了すると、 通知を表す何らかの文字列が送られてきます。
実装の都合で、もう一度 port_conn( port ) でベースサーバに接続し、 ベースサーバのループ処理を終了させます。
sock_ut.py をコマンドとして実行して、動作確認するときの処理内容です。
def help(): lst = [ '\\', 'port base', 'port srv upper', 'port srv eval', 'port cli upper word word word ...', 'port cli eval word word word ...', 'port kill_base', ] dbg.help_exit( '\n'.join( lst ) )
そっけないヘルプメッセージです。
引数の先頭は整数でベースサーバのport番号を指定します。
port番号 base
で実行すると、base_srv( port ) を呼び出し、ベースサーバになります。
port番号 srv upper
で実行すると、起動しているであろうベースサーバに対し、'upper' という名前のサーバを登録し、 受信した文字列の小文字部分を大文字にして送り返すだけの、サーバとして動作します。
port番号 srv eval
で実行すると、起動しているであろうベースサーバに対し、'eval' という名前のサーバを登録し、 受信した文字列を eval() 関数で評価し、結果を str() で文字列に変換して送り返すだけの、サーバとして動作します。
port番号 cli upper word word word ...
で実行すると、ベースサーバに登録してある 'upper' サーバに接続し、world word word ... の単語を順次 'upper' サーバに送信し、 受信した文字列を表示して終了します。
port番号 cli eval word word word ...
で実行すると、ベースサーバに登録してある 'eval' サーバに接続し、world word word ... の単語を順次 'eval' サーバに送信し、 受信した文字列を表示して終了します。
port番号 kill_base
で実行すると、ベースサーバを終了させます。
def run(): if len( sys.argv ) < 2: help() argv = sys.argv[ 1 : ] def pop(): if len( argv ) <= 0: help() return argv.pop( 0 ) port = int( pop() ) names = { 'upper': lambda s: s.upper(), 'eval': lambda s: str( eval( s ) ), } cmd = pop() if cmd == 'base': base_srv( port ) elif cmd == 'srv': name = pop() if name in names: cb = names.get( name ) srv_func = srv_func_new( cb ) srv_loop( port, name, srv_func.func ) elif cmd == 'cli': name = pop() if name in names: sc = connect( port, name ) if not sc: return while len( argv ) > 0: w = pop() if not send_str( sc, w ): break r = recv_str( sc ) if not r: break dbg.out( '{} --> {}'.format( w, r ) ) sc.close() elif cmd == 'kill_base': kill_base_srv( port )
データが文字列である前提が、きつ過ぎるかもです。
データ転送用のpipe_loop() 関数では、バイト列に変更してみました。
v2.patch
diff -ur v1/sock_ut.py v2/sock_ut.py
--- v1/sock_ut.py 2021-06-09 16:10:05.000000000 +0900
+++ v2/sock_ut.py 2021-06-10 21:09:48.000000000 +0900
@@ -17,26 +17,32 @@
return None
return cs
-def send_str( sc, s ):
- bt = s.encode( 'utf-8' )
+def send_bt( sc, bt ):
try:
sc.sendall( bt )
except:
return False
return True
-def recv_str( sc ):
+def recv_bt( sc ):
bufmax = 16 * 1024
bt = sc.recv( bufmax )
- s = bt.decode( 'utf-8' )
- return s
+ return bt
+
+def send_str( sc, s ):
+ bt = s.encode( 'utf-8' )
+ return send_bt( sc, bt )
+
+def recv_str( sc ):
+ bt = recv_bt( sc )
+ return bt.decode( 'utf-8' )
def pipe_loop( sc_from, sc_to ):
while True:
- s = recv_str( sc_from )
- if not s:
+ bt = recv_bt( sc_from )
+ if not bt:
break
- if not send_str( sc_to, s ):
+ if not send_bt( sc_to, bt ):
break
# srv=name
一度の受信のサイズ制限 bufmax = 16 * 1024 があるので、 とりあえず受信してる分はループで取り込む関数を追加してみました。
def recv_bt_all( sc ): buf = b'' while not buf or can_read( sc ): bt = recv_bt( sc ) if not bt: break buf += bt return buf
例えば
ベースサーバ起動
$ ./sock_ut.py 13579 base & [1] 55887
サーバ起動
$ ./sock_ut.py 13579 srv upper & [2] 55892
クライアント接続してやりとり
$ ./sock_ut.py 13579 cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE
サーバを^Cで終了
$ fg ./sock_ut.py 13579 srv upper C-c C-cTraceback (most recent call last): File "./sock_ut.py", line 290, in <module> run() File "./sock_ut.py", line 269, in run srv_loop( port, name, srv_func.func ) File "./sock_ut.py", line 184, in srv_loop cs = accept( ss ) File "./sock_ut.py", line 160, in accept s = recv_str( ss ) File "./sock_ut.py", line 37, in recv_str bt = recv_bt( sc ) File "./sock_ut.py", line 29, in recv_bt bt = sc.recv( bufmax ) KeyboardInterrupt
もう一度サーバを起動しようとすると
$ ./sock_ut.py 13579 srv upper & [2] 55894 $ [2]+ Done ./sock_ut.py 13579 srv upper $
フォアグランドで試してみても
$ ./sock_ut.py 13579 srv upper $
すぐに終了してしまってる...
おそらく2回目の起動では、
def server( port, name ): ss = port_conn( port ) if not ss: return None s = 'srv=' + name if not send_str( ss, s ): ss.close() return None return ss
接続して、srv=name ( 今回の場合 srv=upper ) を送信。
ベースサーバのスレッドでは、srv=upperを受信して、nameとしてupperを取り出し。
def base_th_srv( sc, d ): name = d.get( 'srv' ) if name in srvs: return q = thr.que_new() srvs[ name ] = q while True: s = q.get() if not send_str( sc, s ): break
ここで、辞書 srvs に同じname ( upper ) のキーが残っているので、 すぐに return してスレッド終了。
ソケット sc も呼び出し元の方で close されます。
server() が返すソケットssは、accept( ss ) に渡されたとして、 接続先のベースサーバ側で close されているので
def accept( ss ): s = recv_str( ss ) if not s: return None :
受信できずに return None
2度目のサーバの起動は、acceptで None が返り、すぐに終了します。
そもそもサーバが終了しても、 ベースサーバ側の辞書srvsにエントリーが残っている事が問題です。
エントリーを削除する処理が抜けてました。m(__)m
base_th_srv() のループから抜けたタイミングで、 srvs.pop( name ) して削除すれば良いでしょう。
ですがその前に...
def base_th_srv( sc, d ): name = d.get( 'srv' ) if name in srvs: return q = thr.que_new() srvs[ name ] = q while True: s = q.get() if not send_str( sc, s ): break srvs.pop( name ) # これを追加
q.get() でキューを待ってる時に、 ソケットscが接続元でcloseされた事をどうやって知れば良いの!?
キュー待ちでタイムアウトで定期的に抜けるようにして、 selectでソケットscのwritableチェックでは、どうでしょう?
やってみましたが、残念ながら close された後でも select の結果は送信可能のままでした。
https://docs.python.org/ja/3.6/howto/sockets.html
を一通り見てみましたが、 select の writable は、送信バッファに空きがあるかどうか程度の判定の様子。
close判定するにしても、socketが死んでもなかなか見捨ててくれないらしく。
となると、送信してみてエラーで判定が堅そうかな。
相手の生存確認のために、適当に送信してみるにしても、 現状ではaccept()待ちしてる側で受信して、 適当な文字列だとエラーになってしまいます。
そこを対策するならば、生存確認なら受信したデータは破棄するとして...
いや、そもそも、accept( ss ) の ss が、生存確認のために readabe になってしまうか〜
まぁそこは、server() で取得した ss は、常にすぐ accept( ss ) につっこんでブロックする運用を前提という事にしよう。
そうしよう。
ならば送信する文字列もacceptの形式 'port=xxx,srv=name,id=xxx' にしてしまえば
def accept( ss ): s = recv_str( ss ) if not s: return None d = get_dic( s ) if not is_accept_dic( d ): # このあたりのエラー判定は無駄にならずに済みます return None :
今idの値は
id = sc.fileno()
整数が前提になっているので、 ここに文字列'ping'が指定されてたら、生存確認の送信と解釈する事にします。
どうせなら、生存確認の送信は、キューに 'port=xxx,srv=name,id=ping' も文字列を書き込んでしまえば、
def base_th_srv( sc, d ): name = d.get( 'srv' ) if name in srvs: return q = thr.que_new() srvs[ name ] = q while True: s = q.get() if not send_str( sc, s ): break srvs.pop( name ) # これを追加
この処理は、そのままで良くて、scの先がcloseしてると、 send_str() がエラーで返ってループを抜けてくれます。
ならば、あとはキューに書き込むのは...
専用のスレッドを1つ追加して、辞書srvsに登録されてるサーバのキュー 全部に生存確認用の書き込みをして、面倒を見る事にしよう。
v3.patch
diff -ur v2/sock_ut.py v3/sock_ut.py
--- v2/sock_ut.py 2021-06-10 21:09:48.000000000 +0900
+++ v3/sock_ut.py 2021-06-24 21:26:04.000000000 +0900
@@ -9,6 +9,8 @@
import thr
import dbg
+can_read = lambda sc, tmout=0: select.select( [ sc ], [], [], tmout )[ 0 ] == [ sc ]
+
def port_conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
@@ -29,6 +31,15 @@
bt = sc.recv( bufmax )
return bt
+def recv_bt_all( sc ):
+ buf = b''
+ while not buf or can_read( sc ):
+ bt = recv_bt( sc )
+ if not bt:
+ break
+ buf += bt
+ return buf
+
def send_str( sc, s ):
bt = s.encode( 'utf-8' )
return send_bt( sc, bt )
@@ -73,11 +84,15 @@
q = thr.que_new()
srvs[ name ] = q
+ dbg.out( 'srv {} start'.format( name ) )
while True:
s = q.get()
if not send_str( sc, s ):
break
+ srvs.pop( name )
+ dbg.out( 'srv {} quit'.format( name ) )
+
def base_th_accept( sc, s ):
if s not in reqs:
return
@@ -131,7 +146,16 @@
sc.close()
+def ping_th( port ):
+ id = 'ping'
+ for ( name, q ) in srvs.items():
+ s = 'port={},srv={},id={}'.format( port, name, id )
+ q.put( s )
+
def base_srv( port ):
+ th_p = thr.loop_new( ping_th, ( port, ), wait_tmout=1.0 )
+ thr.ths.start( th_p )
+
ss = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
ss.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
ss.bind( ( '', port ) )
@@ -143,6 +167,7 @@
th = thr.th_new( base_th, ( port, cs, ev_quit ) )
thr.ths.start( th )
+ th_p.quit_ev.set()
dbg.out( 'base_srv quit' )
def server( port, name ):
@@ -156,7 +181,7 @@
return None
return ss
-def accept( ss ):
+def accept_inner( ss ):
s = recv_str( ss )
if not s:
return None
@@ -165,6 +190,9 @@
if not is_accept_dic( d ):
return None
+ if d.get( 'id' ) == 'ping':
+ return 'ping'
+
port = int( d.get( 'port' ) )
cs = port_conn( port )
if not send_str( cs, s ):
@@ -176,6 +204,15 @@
return None
return cs
+def accept( ss ):
+ cs = None
+ while True:
+ cs = accept_inner( ss )
+ if cs != 'ping':
+ break
+ #dbg.out( 'accept ping' )
+ return cs
+
def srv_loop( port, name, th_func ):
ss = server( port, name )
if not ss:
生存確認用のスレッドは、base_srv( port ) の冒頭で起動し、 1.0秒間隔で、生存確認を繰り返すようにしてみました。
@@ -131,7 +146,16 @@ sc.close() +def ping_th( port ): + id = 'ping' + for ( name, q ) in srvs.items(): + s = 'port={},srv={},id={}'.format( port, name, id ) + q.put( s ) + def base_srv( port ): + th_p = thr.loop_new( ping_th, ( port, ), wait_tmout=1.0 ) + thr.ths.start( th_p ) + ss = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) ss.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) ss.bind( ( '', port ) ) @@ -143,6 +167,7 @@ th = thr.th_new( base_th, ( port, cs, ev_quit ) ) thr.ths.start( th ) + th_p.quit_ev.set() dbg.out( 'base_srv quit' ) def server( port, name ):
ベースサーバがループから抜けて終了する直前に、 生存確認スレッドも終了のイベントをセットして、 スレッドを終了させます。
ベースサーバ起動
$ ./sock_ut.py 13579 base & [1] 57765
サーバ起動
$ ./sock_ut.py 13579 srv upper & [2] 57766 srv upper start
クライアント接続してやりとり
$ ./sock_ut.py 13579 cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE
サーバ終了
$ kill %2 $ [2]+ Terminated: 15 ./sock_ut.py 13579 srv upper $ srv upper quit
サーバ起動しなおし
$ ./sock_ut.py 13579 srv upper & [2] 57769 srv upper start
サーバをフォアグランドにして^C で終了
$ fg ./sock_ut.py 13579 srv upper C-c C-cTraceback (most recent call last): File "./sock_ut.py", line 327, in <module> run() File "./sock_ut.py", line 306, in run srv_loop( port, name, srv_func.func ) File "./sock_ut.py", line 221, in srv_loop cs = accept( ss ) File "./sock_ut.py", line 210, in accept cs = accept_inner( ss ) File "./sock_ut.py", line 185, in accept_inner s = recv_str( ss ) File "./sock_ut.py", line 48, in recv_str bt = recv_bt( sc ) File "./sock_ut.py", line 31, in recv_bt bt = sc.recv( bufmax ) KeyboardInterrupt $ srv upper quit
サーバ3回目の起動
$ ./sock_ut.py 13579 srv upper & [2] 57770 srv upper start
クライアント接続してやりとり
$ ./sock_ut.py 13579 cli upper abc abc --> ABC
サーバ終了
$ fg ./sock_ut.py 13579 srv upper C-c C-cTraceback (most recent call last): File "./sock_ut.py", line 327, in <module> run() File "./sock_ut.py", line 306, in run srv_loop( port, name, srv_func.func ) File "./sock_ut.py", line 221, in srv_loop cs = accept( ss ) File "./sock_ut.py", line 210, in accept cs = accept_inner( ss ) File "./sock_ut.py", line 185, in accept_inner s = recv_str( ss ) File "./sock_ut.py", line 48, in recv_str bt = recv_bt( sc ) File "./sock_ut.py", line 31, in recv_bt bt = sc.recv( bufmax ) KeyboardInterrupt srv upper quit $
大丈夫そうですね。
文字列の受信の方も追加しておきます。
関数を抜ける前に、 server()の呼び出しで取得したソケットss をクローズする処理が抜けていました。
ss.close()を追加しておきます。
動作に不具合は見つかってませんが、実装をちょっと変更。
accept()の内部処理を整理して関数に分けました。
処理の前半でソケットssから受信する部分を、accept_recv()に。
後半でベースサーバに接続しにいく部分を、accept_conn()に。
accept_recv()の中で生存確認の面倒を見るようにして、、 accept()本体自身は
def accept( ss ): r = accept_recv( ss ) return accept_conn( r )
このように。
v4.patch
diff -ur v3/sock_ut.py v4/sock_ut.py
--- v3/sock_ut.py 2021-06-24 21:26:04.000000000 +0900
+++ v4/sock_ut.py 2021-06-28 19:46:14.000000000 +0900
@@ -48,6 +48,10 @@
bt = recv_bt( sc )
return bt.decode( 'utf-8' )
+def recv_str_all( sc ):
+ bt = recv_bt_all( sc )
+ return bt.decode( 'utf-8' )
+
def pipe_loop( sc_from, sc_to ):
while True:
bt = recv_bt( sc_from )
@@ -181,7 +185,7 @@
return None
return ss
-def accept_inner( ss ):
+def accept_recv_ping( ss ):
s = recv_str( ss )
if not s:
return None
@@ -193,6 +197,20 @@
if d.get( 'id' ) == 'ping':
return 'ping'
+ return ( s, d )
+
+def accept_recv( ss ):
+ r = None
+ while True:
+ r = accept_recv_ping( ss )
+ if r != 'ping':
+ break
+ return r # None or ( s, d )
+
+def accept_conn( r ):
+ if not r:
+ return None
+ ( s, d ) = r
port = int( d.get( 'port' ) )
cs = port_conn( port )
if not send_str( cs, s ):
@@ -205,13 +223,8 @@
return cs
def accept( ss ):
- cs = None
- while True:
- cs = accept_inner( ss )
- if cs != 'ping':
- break
- #dbg.out( 'accept ping' )
- return cs
+ r = accept_recv( ss )
+ return accept_conn( r )
def srv_loop( port, name, th_func ):
ss = server( port, name )
@@ -223,6 +236,7 @@
break
th = thr.th_new( th_func, ( cs, ) )
thr.ths.start( th )
+ ss.close()
def srv_func_new( cb ):
ベースサーバの終了処理は実装してましたが、 接続してるサーバの終了処理が無かったので追加してみました。
I/Fの関数は
def kill_srv( port, name ): cs = port_conn( port ) if not cs: return s = 'kill=' + name #dbg.out( s ) send_str( cs, s ) cs.close()
ベースサーバに接続して、'kill=サーバ名' を送ります。
sock_ut.pyをコマンドとして実行するときは
ヘルプメッセージに追加の通り
'port kill srv_name ( srv_name is upper, eval or ... )'
サーバ名はそのポートのベースサーバに接続しているサーバ名であれば、オッケーです。
upper, eval 以外も受け付けます。
v5.patch
diff -ur v4/sock_ut.py v5/sock_ut.py
--- v4/sock_ut.py 2021-06-28 19:46:14.000000000 +0900
+++ v5/sock_ut.py 2021-06-30 04:15:08.000000000 +0900
@@ -62,6 +62,7 @@
# srv=name
# port=xxx,srv=name,id=xxx
+# kill=name
# kill_base_srv
# name
@@ -70,8 +71,11 @@
return {}
return dict( map( lambda t: t.split( '=' ), s.split( ',' ) ) )
-def is_srv_dic( d ):
- return len( d ) == 1 and 'srv' in d
+is_key_dic = lambda d, k: len( d ) == 1 and k in d
+
+is_srv_dic = lambda d: is_key_dic( d, 'srv' )
+
+is_kill_dic = lambda d: is_key_dic( d, 'kill' )
def is_accept_dic( d ):
ks = [ 'port', 'srv', 'id' ]
@@ -91,6 +95,8 @@
dbg.out( 'srv {} start'.format( name ) )
while True:
s = q.get()
+ if s == 'kill':
+ break
if not send_str( sc, s ):
break
@@ -141,6 +147,12 @@
elif is_accept_dic( d ):
base_th_accept( sc, s )
+ elif is_kill_dic( d ):
+ name = d.get( 'kill' )
+ if name in srvs:
+ q = srvs.get( name )
+ q.put( 'kill' )
+
elif s == 'kill_base_srv':
ev_quit.set()
send_str( sc, 'next' )
@@ -267,6 +279,15 @@
return None
return cs
+def kill_srv( port, name ):
+ cs = port_conn( port )
+ if not cs:
+ return
+ s = 'kill=' + name
+ #dbg.out( s )
+ send_str( cs, s )
+ cs.close()
+
def kill_base_srv( port ):
cs = port_conn( port )
if not cs:
@@ -289,6 +310,7 @@
'port cli upper word word word ...',
'port cli eval word word word ...',
'port kill_base',
+ 'port kill srv_name ( srv_name is upper, eval or ... )'
]
dbg.help_exit( '\n'.join( lst ) )
@@ -333,6 +355,10 @@
break
dbg.out( '{} --> {}'.format( w, r ) )
sc.close()
+ elif cmd == 'kill':
+ name = pop()
+ kill_srv( port, name )
+
elif cmd == 'kill_base':
kill_base_srv( port )
$ ./sock_ut.py 13579 base & [1] 68843 $ ./sock_ut.py 13579 srv eval & [2] 68844 srv eval start $ $ ./sock_ut.py 13579 cli eval 2+3 2+3 --> 5 $ ./sock_ut.py 13579 kill eval srv eval quit $ [2]+ Done ./sock_ut.py 13579 srv eval
色々と試していると、不具合が出ました。
例えば組み込んであるupperサーバで試した手順
$ ./sock_ut.py 13579 base & $ ./sock_ut.py 13579 srv upper & srv upper start $ ./sock_ut.py 13579 cli upper abc abc --> ABC $ ./sock_ut.py 13568 kill_base kill_base_srv base_srv quit [1]- Done ./sock_ut.py 13568 base [2]+ Done ./sock_ut.py 13568 srv upper
なのですが、、、
upperサーバのプロセスのaccept処理のここ
def accept_conn( r ): if not r: return None ( s, d ) = r port = int( d.get( 'port' ) ) cs = port_conn( port ) if not send_str( cs, s ): cs.close() return None s = recv_str( cs ) # !!! <-- ここです if s != 'ok': cs.close() return None return cs
このrecv_str( cs )で'ok'を受け取る手前でまごまごしてると、 マシン負荷などの状況によっては、 クライアント側の別のプロセスの処理に切り替わります。
クライアント側で、ソケットの接続が完了して、データを送り始めてしまうと、 ベースサーバのスレッドからの'ok'の送信直後に、 クライアントプロセスからのデータの送信がきてしまい、 連結して上記のrecv_str( cs )の箇所で、一気に読み出されてしまいます。
というか、そういう不具合が出ました。
ちょっと、再現しやすいようにコードに手を入れて、 故意にそういう状況を作ってみます。
--- v5/sock_ut.py 2021-06-30 04:15:08.000000000 +0900 +++ sock_ut.py 2021-07-02 21:56:35.000000000 +0900 @@ -228,7 +228,9 @@ if not send_str( cs, s ): cs.close() return None + time.sleep( 1 ) # ! s = recv_str( cs ) + dbg.out( 's={}'.format( s ) ) # ! if s != 'ok': cs.close() return None
time.sleep( 1 )で「まごまご」させつつ、、、
受信結果をわざとらしく表示。
$ ./sock_ut.py 13568 base & $ ./sock_ut.py 13568 srv upper & srv upper start $ ./sock_ut.py 13568 cli upper abc s=okabc srv upper quit C-c C-cTraceback (most recent call last): File "./sock_ut.py", line 369, in <module> run() File "./sock_ut.py", line 355, in run r = recv_str( sc ) File "./sock_ut.py", line 48, in recv_str bt = recv_bt( sc ) File "./sock_ut.py", line 31, in recv_bt bt = sc.recv( bufmax ) KeyboardInterrupt [2]+ Done ./sock_ut.py 13568 srv upper $
'ok' と 'abc' が連結されて受信。見事に再現されましたね。
なーんて、わざとらしい。
もっといい解決方法もありそうですが、 とりあえず思いつく簡単な対応を。
'ok'を期待して受信する箇所で、 最大2バイトの受信に制限します。
受信処理の最下位は、
def recv_bt( sc ): bufmax = 16 * 1024 bt = sc.recv( bufmax ) return bt def recv_str( sc ): bt = recv_bt( sc ) return bt.decode( 'utf-8' )
なので
def recv_bt( sc, bufmax=16*1024 ): bt = sc.recv( bufmax ) return bt def recv_str( sc, bufmax=16*1024 ): bt = recv_bt( sc, bufmax ) return bt.decode( 'utf-8' )
に変更して、呼び出し側で、bufmaxの指定も可能にします。
「まごまご」させて試した例の箇所は
def accept_conn( r ): if not r: return None ( s, d ) = r port = int( d.get( 'port' ) ) cs = port_conn( port ) if not send_str( cs, s ): cs.close() return None s = recv_str( cs, bufmax=2 ) if s != 'ok': cs.close() return None return cs
このように bufmax=2 に制限。
他、クライアント側の'ok'受信箇所でも同様に。
v6.patch
diff -ur v5/sock_ut.py v6/sock_ut.py
--- v5/sock_ut.py 2021-06-30 04:15:08.000000000 +0900
+++ v6/sock_ut.py 2021-07-02 21:40:11.000000000 +0900
@@ -26,8 +26,7 @@
return False
return True
-def recv_bt( sc ):
- bufmax = 16 * 1024
+def recv_bt( sc, bufmax=16*1024 ):
bt = sc.recv( bufmax )
return bt
@@ -44,8 +43,8 @@
bt = s.encode( 'utf-8' )
return send_bt( sc, bt )
-def recv_str( sc ):
- bt = recv_bt( sc )
+def recv_str( sc, bufmax=16*1024 ):
+ bt = recv_bt( sc, bufmax )
return bt.decode( 'utf-8' )
def recv_str_all( sc ):
@@ -228,7 +227,7 @@
if not send_str( cs, s ):
cs.close()
return None
- s = recv_str( cs )
+ s = recv_str( cs, bufmax=2 )
if s != 'ok':
cs.close()
return None
@@ -273,7 +272,7 @@
cs.close()
return None
- s = recv_str( cs )
+ s = recv_str( cs, bufmax=2 )
if s != 'ok':
cs.close()
return None
まず、普通に動作確認。
$ ./sock_ut.py 13579 base & [1] 85756 $ ./sock_ut.py 13579 srv upper & [2] 85757 srv upper start $ ./sock_ut.py 13579 cli upper abc abc --> ABC $ ./sock_ut.py 13579 kill_base kill_base_srv base_srv quit [1]- Done ./sock_ut.py 13579 base [2]+ Done ./sock_ut.py 13579 srv upper $
では例のソースの改ざんをば。
$ diff -u v6/sock_ut.py sock_ut.py --- v6/sock_ut.py 2021-07-02 21:40:11.000000000 +0900 +++ sock_ut.py 2021-07-02 22:25:36.000000000 +0900 @@ -227,7 +227,9 @@ if not send_str( cs, s ): cs.close() return None + time.sleep( 1 ) s = recv_str( cs, bufmax=2 ) + dbg.out( 's={}'.format( s ) ) if s != 'ok': cs.close() return None
実行してみます。
$ ./sock_ut.py 13579 base & [1] 85761 $ ./sock_ut.py 13579 srv upper & [2] 85762 srv upper start $ ./sock_ut.py 13579 cli upper abc s=ok abc --> ABC $ ./sock_ut.py 13579 cli upper foo bar hoge s=ok foo --> FOO bar --> BAR hoge --> HOGE $ ./sock_ut.py 13579 kill_base kill_base_srv base_srv quit [1]- Done ./sock_ut.py 13579 base [2]+ Done ./sock_ut.py 13579 srv upper
大丈夫のようですね。
ここまで暗黙の了解でマシン1台の世界に限定で、 ホスト名は'localhost'固定でした。
全ては、次の関数が物語ってます。
def port_conn( port, host='localhost' ): cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) try: cs.connect( ( host, port ) ) except: return None return cs
一応、引数でhostを指定できるようにしてましたが、 呼び出し箇所は全てport番号のみの指定。
ベースサーバのマシンと、サーバのマシン、クライアントのマシン、 全部バラバラでも動作するように改造してみる事にします。
port_conn() の呼び出し箇所から芋づる式にたぐっていけば、 まぁざっと引数の追加が必要な箇所はあぶり出されるでしょう。
def server( port, name ): ss = port_conn( port ) :
def accept_conn( r ): if not r: return None ( s, d ) = r port = int( d.get( 'port' ) ) cs = port_conn( port ) :
def connect( port, name ): cs = port_conn( port ) :
def kill_srv( port, name ): cs = port_conn( port ) :
def kill_base_srv( port ): cs = port_conn( port ) : cs = port_conn( port ) # for accept loop :
が1次呼元。
def srv_loop( port, name, th_func ): ss = server( port, name ) :
def accept( ss ): r = accept_recv( ss ) return accept_conn( r )
def run(): : sc = connect( port, name ) : kill_srv( port, name ) : kill_base_srv( port ) :
が2次呼元、てな具合にさかのぼっていくと、 コマンドとして起動したときの run() 関数へと。
コマンドとして実行したときのコマンドライン引数で ホスト名の追加が必要です。
従来のコマンドライン引数の形式は、
def help(): lst = [ '\\', 'port base', 'port srv upper', 'port srv eval', 'port cli upper word word word ...', 'port cli eval word word word ...', 'port kill_base', 'port kill srv_name ( srv_name is upper, eval or ... )' ] dbg.help_exit( '\n'.join( lst ) )
portの指定が先頭固定なので、、、うーむ。
毎回、ホスト名とポート番号の指定必須というのも面倒そう。
従来との互換性を保ちつつ、といのは諦めます。
指定がなければ、デフォルトがlocalhostの13579番ポートという事に してしまいましょう。
def help(): lst = [ '\\', 'base [ port=13579 ]', 'srv [ host=localhost ][ port=13579 ] upper', 'srv [ host=localhost ][ port=13579 ] eval', 'cli [ host=localhost ][ port=13579 ] upper word word word ...', 'cli [ host=localhost ][ port=13579 ] eval word word word ...', 'kill_base [ host=localhost ][ port=13579 ]', 'kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )' ] dbg.help_exit( '\n'.join( lst ) )
このように。
そして、呼元をさかのぼった関数の引数に host='localhost' を追加してまわります。
さて、accept。
従来のサーバ側の処理では、まずベースサーバにport番号でつなぎにいきます。
つないだソケットからport番号を含む文字列情報が流れてくるので、 そのポート番号につなぎにいって、いわゆるaccepに相当する処理をしてました。
accept(ソケット)の関数では、ソケットから受信する文字列に port番号の情報が含まれているので、port番号を引数で与える必要がありません。
さて今回は、ホスト名も必要です。
ならば、ソケットからホスト名の情報も受け取る事にしましょう。
従来のport番号の情報は、実はクライアント側のconnect()をうけた ベースサーバが、サーバ側にport番号の情報を与えていました。
ベースサーバのport番号は only 1 なので、 ベースサーバ、サーバ、クライアント、どこからでも同じ値で 丸く収まっておりました。
ホスト名はそうはいきません。 acceptの処理で接続しにいく時に必要なのは、 サーバからベースサーバに接続するときのホスト名とport番号です。
となると、server()の処理で、サーバからベースサーバに接続しにいきつつ、 ベース登録したいサーバ名だけを送るついでに、 接続するときに使ったホスト名とport番号も送っておけばよさそうです。
acceptではソケットから、ベースサーバに接続するために必要な、 ホスト名とport番号が送られてきます。 こうしておけばクライアント側の処理の手を借りずにすみます。
v7.patch
diff -ur v6/sock_ut.py v7/sock_ut.py
--- v6/sock_ut.py 2021-07-02 21:40:11.000000000 +0900
+++ v7/sock_ut.py 2021-07-14 22:40:49.000000000 +0900
@@ -59,8 +59,9 @@
if not send_bt( sc_to, bt ):
break
-# srv=name
-# port=xxx,srv=name,id=xxx
+# srv=name,port=xxx,host=xxx
+# srv=name,id=xxx,port=xxx,host=xxx
+# srv=name,id=xxx
# kill=name
# kill_base_srv
# name
@@ -70,15 +71,20 @@
return {}
return dict( map( lambda t: t.split( '=' ), s.split( ',' ) ) )
+is_keys_dic = lambda d, ks: len( d ) == len( ks ) and all( map( lambda k: k in d, ks ) )
+
is_key_dic = lambda d, k: len( d ) == 1 and k in d
-is_srv_dic = lambda d: is_key_dic( d, 'srv' )
+is_srv_dic = lambda d: is_keys_dic( d, [ 'srv', 'port', 'host' ] )
is_kill_dic = lambda d: is_key_dic( d, 'kill' )
-def is_accept_dic( d ):
- ks = [ 'port', 'srv', 'id' ]
- return len( d ) == len( ks ) and all( map( lambda k: k in d, ks ) )
+is_accept_dic = lambda d: is_keys_dic( d, [ 'srv', 'id', 'port', 'host' ] )
+
+def get_port_host_from_dic( d ):
+ port = int( d.get( 'port' ) )
+ host = d.get( 'host' )
+ return ( port, host )
srvs = {}
reqs = {}
@@ -88,6 +94,8 @@
if name in srvs:
return
+ ( port, host ) = get_port_host_from_dic( d )
+
q = thr.que_new()
srvs[ name ] = q
@@ -96,6 +104,7 @@
s = q.get()
if s == 'kill':
break
+ s += ',port={},host={}'.format( port, host )
if not send_str( sc, s ):
break
@@ -103,6 +112,7 @@
dbg.out( 'srv {} quit'.format( name ) )
def base_th_accept( sc, s ):
+ s = ','.join( s.split( ',' )[ : -2 ] )
if s not in reqs:
return
@@ -116,11 +126,11 @@
pipe_loop( sc, sc2 )
-def base_th_connect( port, sc, s ):
+def base_th_connect( sc, s ):
name = s
q = srvs.get( name )
id = sc.fileno()
- s = 'port={},srv={},id={}'.format( port, name, id )
+ s = 'srv={},id={}'.format( name, id )
ev = thr.event_new()
reqs[ s ] = [ sc, ev ]
q.put( s )
@@ -157,18 +167,18 @@
send_str( sc, 'next' )
elif s in srvs:
- base_th_connect( port, sc, s )
+ base_th_connect( sc, s )
sc.close()
-def ping_th( port ):
+def ping_th():
id = 'ping'
for ( name, q ) in srvs.items():
- s = 'port={},srv={},id={}'.format( port, name, id )
+ s = 'srv={},id={}'.format( name, id )
q.put( s )
def base_srv( port ):
- th_p = thr.loop_new( ping_th, ( port, ), wait_tmout=1.0 )
+ th_p = thr.loop_new( ping_th, wait_tmout=1.0 )
thr.ths.start( th_p )
ss = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
@@ -185,12 +195,12 @@
th_p.quit_ev.set()
dbg.out( 'base_srv quit' )
-def server( port, name ):
- ss = port_conn( port )
+def server( port, name, host='localhost' ):
+ ss = port_conn( port, host )
if not ss:
return None
- s = 'srv=' + name
+ s = 'srv={},port={},host={}'.format( name, port, host )
if not send_str( ss, s ):
ss.close()
return None
@@ -222,8 +232,8 @@
if not r:
return None
( s, d ) = r
- port = int( d.get( 'port' ) )
- cs = port_conn( port )
+ ( port, host ) = get_port_host_from_dic( d )
+ cs = port_conn( port, host )
if not send_str( cs, s ):
cs.close()
return None
@@ -237,8 +247,8 @@
r = accept_recv( ss )
return accept_conn( r )
-def srv_loop( port, name, th_func ):
- ss = server( port, name )
+def srv_loop( port, name, th_func, host='localhost' ):
+ ss = server( port, name, host )
if not ss:
return
while True:
@@ -263,8 +273,8 @@
return empty.new( locals() )
-def connect( port, name ):
- cs = port_conn( port )
+def connect( port, name, host='localhost' ):
+ cs = port_conn( port, host )
if not cs:
return None
@@ -278,8 +288,8 @@
return None
return cs
-def kill_srv( port, name ):
- cs = port_conn( port )
+def kill_srv( port, name, host='loaclhost' ):
+ cs = port_conn( port, host )
if not cs:
return
s = 'kill=' + name
@@ -287,8 +297,8 @@
send_str( cs, s )
cs.close()
-def kill_base_srv( port ):
- cs = port_conn( port )
+def kill_base_srv( port, host='loaclhost' ):
+ cs = port_conn( port, host )
if not cs:
return
dbg.out( 'kill_base_srv' )
@@ -296,20 +306,20 @@
r = recv_str( cs )
cs.close()
- cs = port_conn( port ) # for accept loop
+ cs = port_conn( port, host ) # for accept loop
if cs:
cs.close()
def help():
lst = [
'\\',
- 'port base',
- 'port srv upper',
- 'port srv eval',
- 'port cli upper word word word ...',
- 'port cli eval word word word ...',
- 'port kill_base',
- 'port kill srv_name ( srv_name is upper, eval or ... )'
+ 'base [ port=13579 ]',
+ 'srv [ host=localhost ][ port=13579 ] upper',
+ 'srv [ host=localhost ][ port=13579 ] eval',
+ 'cli [ host=localhost ][ port=13579 ] upper word word word ...',
+ 'cli [ host=localhost ][ port=13579 ] eval word word word ...',
+ 'kill_base [ host=localhost ][ port=13579 ]',
+ 'kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )'
]
dbg.help_exit( '\n'.join( lst ) )
@@ -324,7 +334,15 @@
help()
return argv.pop( 0 )
- port = int( pop() )
+ def popk( k, dv=None ):
+ for i in range( len( argv ) ):
+ if argv[ i ].startswith( k ):
+ return argv.pop( i )[ len( k ) : ]
+ return dv
+
+ host = popk( 'host=', 'localhost' )
+ port = int( popk( 'port=', '13579' ) )
+
names = {
'upper': lambda s: s.upper(),
'eval': lambda s: str( eval( s ) ),
@@ -338,11 +356,11 @@
if name in names:
cb = names.get( name )
srv_func = srv_func_new( cb )
- srv_loop( port, name, srv_func.func )
+ srv_loop( port, name, srv_func.func, host )
elif cmd == 'cli':
name = pop()
if name in names:
- sc = connect( port, name )
+ sc = connect( port, name, host )
if not sc:
return
while len( argv ) > 0:
@@ -356,10 +374,10 @@
sc.close()
elif cmd == 'kill':
name = pop()
- kill_srv( port, name )
+ kill_srv( port, name, host )
elif cmd == 'kill_base':
- kill_base_srv( port )
+ kill_base_srv( port, host )
if __name__ == "__main__":
まずは従来相当の動作の確認を。
$ ./sock_ut.py Usage: ./sock_ut.py \ base [ port=13579 ] srv [ host=localhost ][ port=13579 ] upper srv [ host=localhost ][ port=13579 ] eval cli [ host=localhost ][ port=13579 ] upper word word word ... cli [ host=localhost ][ port=13579 ] eval word word word ... kill_base [ host=localhost ][ port=13579 ] kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )
$ ./sock_ut.py base & $ ./sock_ut.py srv upper & srv upper start $ ./sock_ut.py cli upper foo bar foo --> FOO bar --> BAR $ ./sock_ut.py kill upper srv upper quit $ ./sock_ut.py kill_base kill_base_srv base_srv quit
さて別のマシン...
ひっさしぶりにeee-pcからのUbuntuの起動、 バッテリが完全に死んでて、 電源アダプタのケーブルが接触不良で手間取りましたが、 何とか起動成功!
kondoh@kondoh-901:~$ dmesg : [ 0.000000] Linux version 3.13.0-66-generic (buildd@lgw01-22) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) ) #108-Ubuntu SMP Wed Oct 7 15:21:40 UTC 2015 (Ubuntu 3.13.0-66.108-generic 3.13.11-ckt27) : [ 0.082405] smpboot: CPU0: Intel(R) Atom(TM) CPU N270 @ 1.60GHz (fam: 06, model: 1c, stepping: 02) : kondoh@kondoh-901:~$ cat /etc/issue Ubuntu 14.04.3 LTS \n \l kondoh@kondoh-901:~$ : wlan0 Link encap:イーサネット ハードウェアアドレス 00:15:af:e7:01:99 inetアドレス:192.168.11.9 ブロードキャスト:192.168.11.255 マスク:255.255.255.0 inet6アドレス: fe80::215:afff:fee7:199/64 範囲:リンク UP BROADCAST RUNNING MULTICAST MTU:1500 メトリック:1 RXパケット:301 エラー:0 損失:0 オーバラン:0 フレーム:0 TXパケット:205 エラー:0 損失:0 オーバラン:0 キャリア:0 衝突(Collisions):0 TXキュー長:1000 RXバイト:62098 (62.0 KB) TXバイト:108493 (108.4 KB) kondoh@kondoh-901:~$ python Python 2.7.6 (default, Jun 22 2015, 18:00:18) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> ^D kondoh@kondoh-901:~$ python3 Python 3.4.3 (default, Oct 14 2015, 20:33:09) [GCC 4.8.4] on linux Type "help", "copyright", "credits" or "license" for more information. >>> ^D
いけるかな?
kondoh@kondoh-901:~$ wget kondoh.html.xdomain/inst.sh kondoh@kondoh-901:~$ chmod +x inst.sh kondoh@kondoh-901:~$ ./inst.sh
最新版を母艦から
$ ping 192.168.11.7 PING 192.168.11.7 (192.168.11.7): 56 data bytes 64 bytes from 192.168.11.7: icmp_seq=0 ttl=64 time=0.161 ms : ^C kondoh@kondoh-901:~$ scp kondoh@192.168.11.7:/Users/kondoh/kon_page/sock_ut/sock_ut.py . Password: sock_ut.py 100% 7311 7.1KB/s 00:00
ここで、ベースサーバを起動しておきます。
kondoh@kondoh-901:~$ ./sock_ut.py base &
では母艦側から
$ ./sock_ut.py srv eval host=192.168.11.9 &
おお! eee-pc側に表示でた
srv eval start
母艦側から
$ ./sock_ut.py cli eval host=192.168.11.9 1+2 1+2 --> 3
eee-pc側からも
kondoh@kondoh-901:~$ ./sock_ut.py cli eval 2+3 2+3 --> 5
サーバevalがどこで動いてるかは気にせずに、 host 192.168.11.9 の port 13579 のベースサーバに、 サーバevalに接続を依頼するという事になりますな。
eee-pc側でサーバupperを起動してみます。
kondoh@kondoh-901:~$ ./sock_ut.py srv upper & srv upper start
母艦側からベースサーバに
$ ./sock_ut.py cli upper host=192.168.11.9 foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE
OK
母艦側から
$ ./sock_ut.py kill upper host=192.168.11.9
eee-pc側で起動してるサーバupperが終了
srv upper quit
そして、やはり母艦側から
$ ./sock_ut.py kill eval host=192.168.11.9
で母艦側で起動してるサーバevalをとめてみると、 eee-pc側からはベースサーバからの表示が
srv eval quit
そして、母艦側でバックグランド・ジョブが終了
[1]+ Done ./sock_ut.py srv eval host=192.168.11.9
さらに母艦からeee-pc側のベースサーバを止めてみます。
$ ./sock_ut.py kill_base host=192.168.11.9 kill_base_srv
eee-pc側で
base_srv quit [1]- 終了 ./sock_ut.py base [2]+ 終了 ./sock_ut.py srv upper kondoh@kondoh-901:~$
大丈夫そうですね。
例えばPythonが使えない環境からでも、 クライアントとしてサーバに文字列の送信ができれば、 使えるでしょう。
使えませんでした。
ソケット通信の文字列って行単位でフラッシュされて、 受信する文字列の末尾には、たいがい改行コードがついてます。
そうでした。
v6 で、bufmax=2 で'ok'を受信する対応で、 もっと良い方法がと感じていたのは、これだったのかも知れません。
バイト列の部分はそのままで、文字列としての送受信の関数で、 改行を付加したり削除したりして安易に対応してみます。
となると'ok'受信はどうなるか?
本来なら、行単位の受信処理を作って、'ok'+改行までを取り出して、 以降はソケットに残ったままにしたいところ...
でも、後続のデータがきてたら、改行が見えるまで1バイトづつ受信するか、 まとめて受信して、とりすぎた分をプッシュバックできるように バッファを追加するか...
結構、面倒ですな。
行単位の受信は保留して、bufmaxで小手先の対応としておきます。
def recv_str( sc, bufmax=16*1024 ): bt = recv_bt( sc, bufmax + 1 ) # '\n' s = bt.decode( 'utf-8' ) return cut_tail( s, '\n' )
ねらった通りに指定bufmax数の受信のあとに改行がなかったら、 最後の受信した1バイトも追加されて、返してしまいますが、 とりあえずこれで。
v8.patch
diff -ur v7/sock_ut.py v8/sock_ut.py
--- v7/sock_ut.py 2021-07-14 22:40:49.000000000 +0900
+++ v8/sock_ut.py 2021-07-16 10:25:21.000000000 +0900
@@ -11,6 +11,10 @@
can_read = lambda sc, tmout=0: select.select( [ sc ], [], [], tmout )[ 0 ] == [ sc ]
+add_tail = lambda s, add: s + add
+
+cut_tail = lambda s, cut: s[ : -len( cut ) ] if s.endswith( cut ) else s
+
def port_conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
@@ -40,16 +44,19 @@
return buf
def send_str( sc, s ):
+ s = add_tail( s, '\n' )
bt = s.encode( 'utf-8' )
return send_bt( sc, bt )
def recv_str( sc, bufmax=16*1024 ):
- bt = recv_bt( sc, bufmax )
- return bt.decode( 'utf-8' )
+ bt = recv_bt( sc, bufmax + 1 ) # '\n'
+ s = bt.decode( 'utf-8' )
+ return cut_tail( s, '\n' )
def recv_str_all( sc ):
bt = recv_bt_all( sc )
- return bt.decode( 'utf-8' )
+ s = bt.decode( 'utf-8' )
+ return cut_tail( s, '\n' )
def pipe_loop( sc_from, sc_to ):
while True:
まず従来の動作から。
$ ./sock_ut.py base & [1] 14765 $ ./sock_ut.py srv upper & [2] 14766 srv upper start $ ./sock_ut.py srv eval & [3] 14767 srv eval start $ ./sock_ut.py cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE $ ./sock_ut.py cli eval 1+1 2+3 1+1 --> 2 2+3 --> 5 $ ./sock_ut.py kill upper srv upper quit [2]- Done ./sock_ut.py srv upper $ ./sock_ut.py kill_base kill_base_srv base_srv quit [1]- Done ./sock_ut.py base [3]+ Done ./sock_ut.py srv eval $
ではクライアントをncコマンドで。
sock_ut.pyのコメントの仕様を確認
# srv=name,port=xxx,host=xxx # srv=name,id=xxx,port=xxx,host=xxx # srv=name,id=xxx # kill=name # kill_base_srv # name
$ ./sock_ut.py base & [1] 14772 $ ./sock_ut.py srv upper & [2] 14773 srv upper start $ ./sock_ut.py srv eval & [3] 14774 srv eval start
と、サーバを起動しておいて
ncコマンドで
$ nc localhost 13579 upper ok foo FOO bar BAR hoge HOGE C-c C-c $ nc localhost 13579 eval ok 1+1 2 2+3 5 C-c C-c $ nc localhost 13579 kill=eval srv eval quit [3]+ Done ./sock_ut.py srv eval $ nc localhost 13579 kill_base_srv next $ nc localhost 13579 base_srv quit [1]- Done ./sock_ut.py base [2]+ Done ./sock_ut.py srv upper $
ベースサーバの終了は、受信でブロックしてるループから出すために、 最後にもう一度つなぎにいけば大丈夫でした。
まずベースサーバ起動
$ ./sock_ut.py base & [1] 24540
別端末からncコマンドでつないでサーバのふりをしてみます
$ nc localhost 13579 srv=hoge,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost :
おお、1秒間隔で生存確認がきます。
これはベースサーバ側で送信が成功すればそれでOKなので、 返信は不要です。
ベースサーバ側には
srv hoge start
の表示がでました。
別端末で、クライアントとしてつなぎにいってみます。
$ nc localhost 13579 hoge
サーバ側は
: srv=hoge,id=ping,port=13579,host=loalhost srv=hoge,id=5,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost :
要求きました。
また別端末を開いて、acceptしてみます。
$ nc localhost 13579 srv=hoge,id=5,port=13579,host=loalhost ok
クライアント側の端末を見ると
$ nc localhost 13579 hoge ok
ok返ってます。接続完了。
クライアントから送ってみます。
$ nc localhost 13579 hoge ok hello
acceptした端末に
$ nc localhost 13579 srv=hoge,id=5,port=13579,host=loalhost ok hello
返事を返してみます
$ nc localhost 13579 srv=hoge,id=5,port=13579,host=loalhost ok hello world
クライアント側の端末に
$ nc localhost 13579 hoge ok hello world
返ってきます。
クライアント側^Dで終了。
$ nc localhost 13579 hoge ok hello world ^D $
acceptした端末でenterキーで
$ nc localhost 13579 srv=hoge,id=5,port=13579,host=loalhost ok hello world $
切断。
hogeサーバとしての端末は
: srv=hoge,id=ping,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost :
生き続けてます。
終了処理しておきます。
$ nc localhost 13579 kill_base_srv next $ nc localhost 13579 $
サーバの端末
: srv=hoge,id=ping,port=13579,host=loalhost srv=hoge,id=ping,port=13579,host=loalhost $
ベースサーバの端末
$ ./sock_ut.py base & [1] 24540 srv hoge start base_srv quit [1]+ Done ./sock_ut.py base $
ncコマンドで手動で、なんとかサーバのふりができました。
やはり、行単位の受信の対応を入れてみました。
めっちゃ書き換えてしまいました。
とりあえず、受信バッファへのプッシュバックはせずに、何とかする方針で。
内部処理の文字列の部分は
の基本方針のままに。
ただし、内部処理の文字列の受信では、改行が見えるまで1バイト単位のループで対応します。
サーバを生成して、クライアントが接続してくると、 acceptしてスレッド生成して、そのスレッドがクライアントの相手をします。
そのスレッドで指定のコマンドを起動して、 コマンドの標準入力、標準出力をクライアントとやりとりできるようにします。
これまでサーバ側は、ライブラリとしてAPIの関数を使って、 コールバック関数でサーバの動作をさせてました。
本追加でsock_ut.py コマンドから、対話型のコマンドを起動してサーバにする事ができます。
help()関数の
'srv [ host=localhost ][ port=13579 ] srv_name cmd arg ...',
この形式で起動します。
srv_name として、upperとevalだけは使えません。(^_^;
もちろんncコマンドを使ってもいいのですが、、、
一応、sock_ut.py としてもsock_ut.pyの標準入出力を、 接続先のサーバとつなげれるようにしておきます。
help()関数の
'cli [ host=localhost ][ port=13579 ] srv_name',
この形式で起動します。
ベースサーバから起動してるサーバ一覧を取得できるようにしました。
help()関数の
'srvs [ host=localhost ][ port=13579 ]',
こやつです。
対応するAPIの関数
def get_srvs( host, port ): cs = port_conn( port, host ) if not cs: return send_str( cs, 'get_srvs' ) r = recv_str_all( cs ) cs.close() return r.split( '\n' )
ベースサーバのホスト名とポート番号を指定します。
いままで ( port, host='localhot' ) の順でしたが、 コマンドライン引数のレベルで、デフォルトのホスト名は'localhost'なので、 よくかんがえると、APIの関数でデフォルト引数にしてる意味があまり無いかと。
ならばこの並びの方がしっくりきますね。
従来分はとりあえずそのままに、新規追加分からはこの並びで。
どこかのタイミングで、統一して書き換えてしまうかもです。
APIの関数としては、サーバ名のリストで返ります。
コマンドとしての出力文字列は、 サーバ名+改行で、縦に名前が並ぶ表示になります。
元々はソケットの受信、送信だけでした。
が、今やコマンドを実行するsubprocessの標準入出力のリードライトに、 sock_ut.py コマンド自身のsys.stdin sys.stdoutの標準入出力の リードライトも扱います。
個別に同じような処理を何度も書きかけましたが、 なんとか共通化できないものかと。
行受信のために改行を探しながら1バイト単位のリードや、 ライトしたあとにフラッシュが必要だったり、不要だったりと、 微妙に違ってきます。
ですがなんとか「くくって」みました。
fio_new()
def fio_new( get_b1_, put_b_, get_b_=None ):
to_s = lambda bt: bt.decode( 'utf-8' )
to_b = lambda s: s.encode( 'utf-8' )
def get_b1( f ):
b1 = get_b1_( f )
return b1 if b1 else b''
get_s1 = lambda f : to_s( get_b1( f ) )
def get_b_line( f ):
b = b''
while True:
b1 = get_b1( f )
if not b1:
break
b += b1
if b1 == b'\n':
break
return b
get_s_line = lambda f: to_s( get_b_line( f ) )
def get_b( f ):
if get_b_:
return get_b_( f )
b = b''
while not b or can_read( f ):
b1 = get_b1( f )
if not b1:
break
b += b1
return b
get_s = lambda f: to_s( get_b( f ) )
def get_b_all( f ):
b = b''
while not b or can_read( f ):
b_ = get_b( f )
if not b_:
break
b += b_
return b
get_s_all = lambda f: to_s( get_b_all( f ) )
def put_b( f, b ):
try:
put_b_( f, b )
except:
return False
if hasattr( f, 'flush' ):
f.flush()
return True
put_s = lambda f, s: put_b( f, to_b( s ) )
def get_func( use_b, use_all ):
if use_b:
return get_b_all if use_all else get_b
return get_s_all if use_all else get_s
def put_func( use_b ):
return put_b if use_b else put_s
return empty.new( locals() )
v9.patch
diff -ur v8/sock_ut.py v9/sock_ut.py
--- v8/sock_ut.py 2021-07-16 10:25:21.000000000 +0900
+++ v9/sock_ut.py 2021-07-17 01:34:19.000000000 +0900
@@ -7,6 +7,7 @@
import empty
import thr
+import cmd_ut
import dbg
can_read = lambda sc, tmout=0: select.select( [ sc ], [], [], tmout )[ 0 ] == [ sc ]
@@ -15,6 +16,106 @@
cut_tail = lambda s, cut: s[ : -len( cut ) ] if s.endswith( cut ) else s
+def fio_new( get_b1_, put_b_, get_b_=None ):
+
+ to_s = lambda bt: bt.decode( 'utf-8' )
+ to_b = lambda s: s.encode( 'utf-8' )
+
+ def get_b1( f ):
+ b1 = get_b1_( f )
+ return b1 if b1 else b''
+
+ get_s1 = lambda f : to_s( get_b1( f ) )
+
+ def get_b_line( f ):
+ b = b''
+ while True:
+ b1 = get_b1( f )
+ if not b1:
+ break
+ b += b1
+ if b1 == b'\n':
+ break
+ return b
+
+ get_s_line = lambda f: to_s( get_b_line( f ) )
+
+ def get_b( f ):
+ if get_b_:
+ return get_b_( f )
+
+ b = b''
+ while not b or can_read( f ):
+ b1 = get_b1( f )
+ if not b1:
+ break
+ b += b1
+ return b
+
+ get_s = lambda f: to_s( get_b( f ) )
+
+ def get_b_all( f ):
+ b = b''
+ while not b or can_read( f ):
+ b_ = get_b( f )
+ if not b_:
+ break
+ b += b_
+ return b
+
+ get_s_all = lambda f: to_s( get_b_all( f ) )
+
+
+ def put_b( f, b ):
+ try:
+ put_b_( f, b )
+ except:
+ return False
+ if hasattr( f, 'flush' ):
+ f.flush()
+ return True
+
+ put_s = lambda f, s: put_b( f, to_b( s ) )
+
+
+ def get_func( use_b, use_all ):
+ if use_b:
+ return get_b_all if use_all else get_b
+ return get_s_all if use_all else get_s
+
+ def put_func( use_b ):
+ return put_b if use_b else put_s
+
+
+ return empty.new( locals() )
+
+
+def get_std_b( f ):
+ return f.buffer if hasattr( f, 'buffer' ) else f
+
+stdin_b = get_std_b( sys.stdin )
+stdout_b = get_std_b( sys.stdout )
+std_fio = fio_new( lambda f: f.read( 1 ), lambda f, b: f.write( b ) )
+
+
+def proc_new( cmd ):
+ proc = cmd_ut.proc_new( cmd, stdin=cmd_ut.PIPE, stdout=cmd_ut.PIPE )
+
+ stdin = proc.get_stdin()
+ stdout = proc.get_stdout()
+
+ get_b1 = lambda f: f.read( 1 )
+ put_b = lambda f, b: f.write( b ) if f else 0
+
+ fio = fio_new( get_b1, put_b )
+
+ def stop():
+ proc.kill()
+ proc.wait()
+
+ return empty.new( locals() )
+
+
def port_conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
@@ -23,52 +124,33 @@
return None
return cs
-def send_bt( sc, bt ):
- try:
- sc.sendall( bt )
- except:
- return False
- return True
-
-def recv_bt( sc, bufmax=16*1024 ):
- bt = sc.recv( bufmax )
- return bt
-
-def recv_bt_all( sc ):
- buf = b''
- while not buf or can_read( sc ):
- bt = recv_bt( sc )
- if not bt:
- break
- buf += bt
- return buf
+bufmax = 16 * 1024
+sc_fio = fio_new( lambda f: f.recv( 1 ), lambda f, b: f.sendall( b ), lambda f: f.recv( bufmax ) )
def send_str( sc, s ):
s = add_tail( s, '\n' )
- bt = s.encode( 'utf-8' )
- return send_bt( sc, bt )
+ return sc_fio.put_s( sc, s )
-def recv_str( sc, bufmax=16*1024 ):
- bt = recv_bt( sc, bufmax + 1 ) # '\n'
- s = bt.decode( 'utf-8' )
+def recv_str_line( sc ):
+ s = sc_fio.get_s_line( sc )
return cut_tail( s, '\n' )
def recv_str_all( sc ):
- bt = recv_bt_all( sc )
- s = bt.decode( 'utf-8' )
+ s = sc_fio.get_s_all( sc )
return cut_tail( s, '\n' )
def pipe_loop( sc_from, sc_to ):
while True:
- bt = recv_bt( sc_from )
- if not bt:
+ b = sc_fio.get_b( sc_from )
+ if not b:
break
- if not send_bt( sc_to, bt ):
+ if not sc_fio.put_b( sc_to, b ):
break
# srv=name,port=xxx,host=xxx
# srv=name,id=xxx,port=xxx,host=xxx
# srv=name,id=xxx
+# get_srvs
# kill=name
# kill_base_srv
# name
@@ -150,7 +232,7 @@
pipe_loop( sc, sc2 )
def base_th( port, sc, ev_quit ):
- s = recv_str( sc )
+ s = recv_str_line( sc )
if not s:
sc.close()
return
@@ -169,6 +251,10 @@
q = srvs.get( name )
q.put( 'kill' )
+ elif s == 'get_srvs':
+ r = '\n'.join( srvs.keys() )
+ send_str( sc, r )
+
elif s == 'kill_base_srv':
ev_quit.set()
send_str( sc, 'next' )
@@ -214,7 +300,7 @@
return ss
def accept_recv_ping( ss ):
- s = recv_str( ss )
+ s = recv_str_line( ss )
if not s:
return None
@@ -244,7 +330,7 @@
if not send_str( cs, s ):
cs.close()
return None
- s = recv_str( cs, bufmax=2 )
+ s = recv_str_line( cs )
if s != 'ok':
cs.close()
return None
@@ -266,20 +352,42 @@
thr.ths.start( th )
ss.close()
-def srv_func_new( cb ):
+def srv_func_new( cb, use_b=False, use_all=False ):
+ send = send_ = sc_fio.put_func( use_b )
+ recv = recv_ = sc_fio.get_func( use_b, use_all )
+ if not use_b:
+ send = lambda f, s: send_( f, add_tail( s, '\n' ) )
+ recv = lambda f: cut_tail( recv_( f ), '\n' )
def func( sc ):
while True:
- s = recv_str( sc )
+ s = recv( sc )
if not s:
break
r = cb( s )
- if not send_str( sc, r ):
+ if not r:
+ break
+ if not send( sc, r ):
break
sc.close()
return empty.new( locals() )
+def srv_loop_cmd( host, port, name, cmd ):
+
+ def th_func( sc ):
+ proc = proc_new( cmd )
+
+ def cb( b ):
+ proc.fio.put_b( proc.stdin, b )
+ return proc.fio.get_b_line( proc.stdout )
+
+ srv_func = srv_func_new( cb, use_b=True, use_all=True )
+ srv_func.func( sc )
+ proc.stop()
+
+ srv_loop( port, name, th_func, host )
+
def connect( port, name, host='localhost' ):
cs = port_conn( port, host )
if not cs:
@@ -289,12 +397,21 @@
cs.close()
return None
- s = recv_str( cs, bufmax=2 )
+ s = recv_str_line( cs )
if s != 'ok':
cs.close()
return None
return cs
+def get_srvs( host, port ):
+ cs = port_conn( port, host )
+ if not cs:
+ return
+ send_str( cs, 'get_srvs' )
+ r = recv_str_all( cs )
+ cs.close()
+ return r.split( '\n' )
+
def kill_srv( port, name, host='loaclhost' ):
cs = port_conn( port, host )
if not cs:
@@ -310,7 +427,7 @@
return
dbg.out( 'kill_base_srv' )
send_str( cs, 'kill_base_srv' )
- r = recv_str( cs )
+ r = recv_str_line( cs )
cs.close()
cs = port_conn( port, host ) # for accept loop
@@ -323,8 +440,11 @@
'base [ port=13579 ]',
'srv [ host=localhost ][ port=13579 ] upper',
'srv [ host=localhost ][ port=13579 ] eval',
+ 'srv [ host=localhost ][ port=13579 ] srv_name cmd arg ...',
'cli [ host=localhost ][ port=13579 ] upper word word word ...',
'cli [ host=localhost ][ port=13579 ] eval word word word ...',
+ 'cli [ host=localhost ][ port=13579 ] srv_name',
+ 'srvs [ host=localhost ][ port=13579 ]',
'kill_base [ host=localhost ][ port=13579 ]',
'kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )'
]
@@ -345,7 +465,7 @@
for i in range( len( argv ) ):
if argv[ i ].startswith( k ):
return argv.pop( i )[ len( k ) : ]
- return dv
+ return dv
host = popk( 'host=', 'localhost' )
port = int( popk( 'port=', '13579' ) )
@@ -358,27 +478,55 @@
cmd = pop()
if cmd == 'base':
base_srv( port )
- elif cmd == 'srv':
+ elif cmd == 'srv':
name = pop()
if name in names:
cb = names.get( name )
srv_func = srv_func_new( cb )
srv_loop( port, name, srv_func.func, host )
- elif cmd == 'cli':
+ else:
+ cmd = ' '.join( argv )
+ srv_loop_cmd( host, port, name, cmd )
+ elif cmd == 'cli':
+ lst = get_srvs( host, port )
name = pop()
+ if name not in lst:
+ return
+
+ sc = connect( port, name, host )
+ if not sc:
+ return
+
if name in names:
- sc = connect( port, name, host )
- if not sc:
- return
while len( argv ) > 0:
w = pop()
if not send_str( sc, w ):
break
- r = recv_str( sc )
+ r = recv_str_line( sc )
if not r:
break
dbg.out( '{} --> {}'.format( w, r ) )
- sc.close()
+ else:
+ while True:
+ b = std_fio.get_b_line( stdin_b )
+ if not b:
+ break
+
+ if not sc_fio.put_b( sc, b ):
+ break
+
+ b = sc_fio.get_b_all( sc )
+ if not b:
+ break
+
+ if not std_fio.put_b( stdout_b, b ):
+ break
+ sc.close()
+
+ elif cmd == 'srvs':
+ lst = get_srvs( host, port )
+ dbg.out( '\n'.join( lst ) )
+
elif cmd == 'kill':
name = pop()
kill_srv( port, name, host )
まずはお約束のパターンから
$ ./sock_ut.py Usage: ./sock_ut.py \ base [ port=13579 ] srv [ host=localhost ][ port=13579 ] upper srv [ host=localhost ][ port=13579 ] eval srv [ host=localhost ][ port=13579 ] srv_name cmd arg ... cli [ host=localhost ][ port=13579 ] upper word word word ... cli [ host=localhost ][ port=13579 ] eval word word word ... cli [ host=localhost ][ port=13579 ] srv_name srvs [ host=localhost ][ port=13579 ] kill_base [ host=localhost ][ port=13579 ] kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... ) $ ./sock_ut.py base & [1] 30441 $ ./sock_ut.py srv upper & [2] 30442 srv upper start $ ./sock_ut.py srv eval & [3] 30443 srv eval start $ ./sock_ut.py cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE $ ./sock_ut.py cli eval 1+1 2+3 1+1 --> 2 2+3 --> 5
ここでサーバ一覧を表示してみると
$ ./sock_ut.py srvs upper eval
では、コマンドのサーバをば
bc -lコマンドで試してみます。
$ ./sock_ut.py srv bc bc -l & [4] 30504 srv bc start $ ./sock_ut.py cli bc 1+1 2 2+3.14 5.14 10/3 3.33333333333333333333 ^D $ ./sock_ut.py srvs upper eval bc $ ./sock_ut.py kill bc srv bc quit [4]+ Done ./sock_ut.py srv bc bc -l $ ./sock_ut.py kill_base kill_base_srv base_srv quit [1] Done ./sock_ut.py base [2]- Done ./sock_ut.py srv upper [3]+ Done ./sock_ut.py srv eval
fio_new()の部分をまとめなおしてfio_ut.pyとして切り出して、 のれん分けしました。
fio_ut.pyを含まない部分の差分で。
v10.patch
diff -ur v9/sock_ut.py v10/sock_ut.py
--- v9/sock_ut.py 2021-07-17 01:34:19.000000000 +0900
+++ v10/sock_ut.py 2021-07-19 00:10:50.000000000 +0900
@@ -2,119 +2,16 @@
import sys
import socket
-import select
-import time
import empty
import thr
-import cmd_ut
+import fio_ut
import dbg
-can_read = lambda sc, tmout=0: select.select( [ sc ], [], [], tmout )[ 0 ] == [ sc ]
-
add_tail = lambda s, add: s + add
-
cut_tail = lambda s, cut: s[ : -len( cut ) ] if s.endswith( cut ) else s
-def fio_new( get_b1_, put_b_, get_b_=None ):
-
- to_s = lambda bt: bt.decode( 'utf-8' )
- to_b = lambda s: s.encode( 'utf-8' )
-
- def get_b1( f ):
- b1 = get_b1_( f )
- return b1 if b1 else b''
-
- get_s1 = lambda f : to_s( get_b1( f ) )
-
- def get_b_line( f ):
- b = b''
- while True:
- b1 = get_b1( f )
- if not b1:
- break
- b += b1
- if b1 == b'\n':
- break
- return b
-
- get_s_line = lambda f: to_s( get_b_line( f ) )
-
- def get_b( f ):
- if get_b_:
- return get_b_( f )
-
- b = b''
- while not b or can_read( f ):
- b1 = get_b1( f )
- if not b1:
- break
- b += b1
- return b
-
- get_s = lambda f: to_s( get_b( f ) )
-
- def get_b_all( f ):
- b = b''
- while not b or can_read( f ):
- b_ = get_b( f )
- if not b_:
- break
- b += b_
- return b
-
- get_s_all = lambda f: to_s( get_b_all( f ) )
-
-
- def put_b( f, b ):
- try:
- put_b_( f, b )
- except:
- return False
- if hasattr( f, 'flush' ):
- f.flush()
- return True
-
- put_s = lambda f, s: put_b( f, to_b( s ) )
-
-
- def get_func( use_b, use_all ):
- if use_b:
- return get_b_all if use_all else get_b
- return get_s_all if use_all else get_s
-
- def put_func( use_b ):
- return put_b if use_b else put_s
-
-
- return empty.new( locals() )
-
-
-def get_std_b( f ):
- return f.buffer if hasattr( f, 'buffer' ) else f
-
-stdin_b = get_std_b( sys.stdin )
-stdout_b = get_std_b( sys.stdout )
-std_fio = fio_new( lambda f: f.read( 1 ), lambda f, b: f.write( b ) )
-
-
-def proc_new( cmd ):
- proc = cmd_ut.proc_new( cmd, stdin=cmd_ut.PIPE, stdout=cmd_ut.PIPE )
-
- stdin = proc.get_stdin()
- stdout = proc.get_stdout()
-
- get_b1 = lambda f: f.read( 1 )
- put_b = lambda f, b: f.write( b ) if f else 0
-
- fio = fio_new( get_b1, put_b )
-
- def stop():
- proc.kill()
- proc.wait()
-
- return empty.new( locals() )
-
+close = fio_ut.close
def port_conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
@@ -122,29 +19,27 @@
cs.connect( ( host, port ) )
except:
return None
+ fio_ut.set_fs_sock( cs )
return cs
-bufmax = 16 * 1024
-sc_fio = fio_new( lambda f: f.recv( 1 ), lambda f, b: f.sendall( b ), lambda f: f.recv( bufmax ) )
-
def send_str( sc, s ):
s = add_tail( s, '\n' )
- return sc_fio.put_s( sc, s )
+ return fio_ut.p_s( sc, s )
def recv_str_line( sc ):
- s = sc_fio.get_s_line( sc )
+ s = fio_ut.g_s_line( sc )
return cut_tail( s, '\n' )
def recv_str_all( sc ):
- s = sc_fio.get_s_all( sc )
+ s = fio_ut.g_s_all( sc )
return cut_tail( s, '\n' )
def pipe_loop( sc_from, sc_to ):
while True:
- b = sc_fio.get_b( sc_from )
+ b = fio_ut.g_b( sc_from )
if not b:
break
- if not sc_fio.put_b( sc_to, b ):
+ if not fio_ut.p_b( sc_to, b ):
break
# srv=name,port=xxx,host=xxx
@@ -232,9 +127,11 @@
pipe_loop( sc, sc2 )
def base_th( port, sc, ev_quit ):
+ fio_ut.set_fs_sock( sc )
+
s = recv_str_line( sc )
if not s:
- sc.close()
+ close( sc )
return
d = get_dic( s )
@@ -262,7 +159,7 @@
elif s in srvs:
base_th_connect( sc, s )
- sc.close()
+ close( sc )
def ping_th():
id = 'ping'
@@ -295,7 +192,7 @@
s = 'srv={},port={},host={}'.format( name, port, host )
if not send_str( ss, s ):
- ss.close()
+ close( ss )
return None
return ss
@@ -328,11 +225,11 @@
( port, host ) = get_port_host_from_dic( d )
cs = port_conn( port, host )
if not send_str( cs, s ):
- cs.close()
+ close( cs )
return None
s = recv_str_line( cs )
if s != 'ok':
- cs.close()
+ close( cs )
return None
return cs
@@ -350,11 +247,21 @@
break
th = thr.th_new( th_func, ( cs, ) )
thr.ths.start( th )
- ss.close()
+ close( ss )
def srv_func_new( cb, use_b=False, use_all=False ):
- send = send_ = sc_fio.put_func( use_b )
- recv = recv_ = sc_fio.get_func( use_b, use_all )
+ send = fio_ut.p_s
+ recv = fio_ut.g_s
+ if use_all:
+ recv = fio_ut.g_s_all
+ if use_b:
+ send = fio_ut.p_b
+ recv = fio_ut.g_b
+ if use_all:
+ recv = fio_ut.g_b_all
+
+ send_ = send
+ recv_ = recv
if not use_b:
send = lambda f, s: send_( f, add_tail( s, '\n' ) )
recv = lambda f: cut_tail( recv_( f ), '\n' )
@@ -369,18 +276,18 @@
break
if not send( sc, r ):
break
- sc.close()
+ close( sc )
return empty.new( locals() )
def srv_loop_cmd( host, port, name, cmd ):
def th_func( sc ):
- proc = proc_new( cmd )
+ proc = fio_ut.proc_new( cmd )
def cb( b ):
- proc.fio.put_b( proc.stdin, b )
- return proc.fio.get_b_line( proc.stdout )
+ fio_ut.p_b( proc.fo, b )
+ return fio_ut.g_b_all( proc.fi )
srv_func = srv_func_new( cb, use_b=True, use_all=True )
srv_func.func( sc )
@@ -394,12 +301,12 @@
return None
if not send_str( cs, name ):
- cs.close()
+ close( cs )
return None
s = recv_str_line( cs )
if s != 'ok':
- cs.close()
+ close( cs )
return None
return cs
@@ -409,7 +316,7 @@
return
send_str( cs, 'get_srvs' )
r = recv_str_all( cs )
- cs.close()
+ close( cs )
return r.split( '\n' )
def kill_srv( port, name, host='loaclhost' ):
@@ -419,7 +326,7 @@
s = 'kill=' + name
#dbg.out( s )
send_str( cs, s )
- cs.close()
+ close( cs )
def kill_base_srv( port, host='loaclhost' ):
cs = port_conn( port, host )
@@ -428,11 +335,11 @@
dbg.out( 'kill_base_srv' )
send_str( cs, 'kill_base_srv' )
r = recv_str_line( cs )
- cs.close()
+ close( cs )
cs = port_conn( port, host ) # for accept loop
if cs:
- cs.close()
+ close( cs )
def help():
lst = [
@@ -507,21 +414,22 @@
break
dbg.out( '{} --> {}'.format( w, r ) )
else:
+ ( fi, fo ) = fio_ut.set_fs_sys_stdio()
while True:
- b = std_fio.get_b_line( stdin_b )
+ b = fio_ut.g_b_line( fi )
if not b:
break
- if not sc_fio.put_b( sc, b ):
+ if not fio_ut.p_b( sc, b ):
break
- b = sc_fio.get_b_all( sc )
+ b = fio_ut.g_b_all( sc )
if not b:
break
- if not std_fio.put_b( stdout_b, b ):
+ if not fio_ut.p_b( fo, b ):
break
- sc.close()
+ close( sc )
elif cmd == 'srvs':
lst = get_srvs( host, port )
v9 での確認をざっと一通り
OKログ
$ ./sock_ut.py
Usage: ./sock_ut.py \
base [ port=13579 ]
srv [ host=localhost ][ port=13579 ] upper
srv [ host=localhost ][ port=13579 ] eval
srv [ host=localhost ][ port=13579 ] srv_name cmd arg ...
cli [ host=localhost ][ port=13579 ] upper word word word ...
cli [ host=localhost ][ port=13579 ] eval word word word ...
cli [ host=localhost ][ port=13579 ] srv_name
srvs [ host=localhost ][ port=13579 ]
kill_base [ host=localhost ][ port=13579 ]
kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )
$ ./sock_ut.py base &
[1] 36633
$ ./sock_ut.py srv upper &
[2] 36634
srv upper start
$ ./sock_ut.py srv eval &
[3] 36635
srv eval start
$ ./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
$ ./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
$ ./sock_ut.py srvs
upper
eval
$ ./sock_ut.py srv bc bc -l &
[4] 36639
srv bc start
$ ./sock_ut.py cli bc
1+1
2
2+3.14
5.14
10/3
3.33333333333333333333
^D
$
$ ./sock_ut.py srvs
upper
eval
bc
$ ./sock_ut.py kill bc
srv bc quit
[4]+ Done ./sock_ut.py srv bc bc -l
$ ./sock_ut.py kill_base
kill_base_srv
base_srv quit
[1] Done ./sock_ut.py base
[2]- Done ./sock_ut.py srv upper
[3]+ Done ./sock_ut.py srv eval
$
な場合、うまく動きませんでした。
sock_ut.pyをコマンドとして実行する場合で、 サーバとして実行するコマンドの動作について。
標準入力がクローズしてから、まとめて全ての結果を返すコマンドの場合、 固まってしまいます。
むしろ、行単位で反応するコマンドの方が少数なのでしょう。
fio_ut でがんばってみましたが、サーバとして実行させてるコマンドの方が、 そういう仕様の動作ならば、致し方ありません。
コマンドライン引数で、モードの指定を追加して対応してみます。
sock_ut.pyをコマンドとして実行する場合について
コマンドライン引数srvやcliの直後に '-a' のオプション指定があれば、 モード'all'で扱います。
srvで指定してsrv_nameとしてモードを登録しておけば、 cliのときにsrv_nameのの設定を見て自動的に対応できそうな気もしますが、、、
srvとcliは別のプロセスで、さらに別マシンで動作してる可能性すらあります。
となると、sock_ut.pyをコマンドとして実行したときだけのローカルなモードとう情報を、 ベースサーバ経由でsrvとcliの間でやりとりさせるように拡張するのは、 ちょっと違うかなと。
なので、ここは使い手がsrv_nameのmodeを覚えておいて、cliで正しく指定する事、という運用でカバーします。
v11.patch
diff -ur v10/sock_ut.py v11/sock_ut.py
--- v10/sock_ut.py 2021-07-19 00:10:50.000000000 +0900
+++ v11/sock_ut.py 2021-07-25 11:38:44.000000000 +0900
@@ -280,13 +280,15 @@
return empty.new( locals() )
-def srv_loop_cmd( host, port, name, cmd ):
+def srv_loop_cmd( host, port, name, cmd, mode ):
def th_func( sc ):
proc = fio_ut.proc_new( cmd )
def cb( b ):
fio_ut.p_b( proc.fo, b )
+ if mode == 'all':
+ fio_ut.close( proc.fo )
return fio_ut.g_b_all( proc.fi )
srv_func = srv_func_new( cb, use_b=True, use_all=True )
@@ -348,15 +350,51 @@
'srv [ host=localhost ][ port=13579 ] upper',
'srv [ host=localhost ][ port=13579 ] eval',
'srv [ host=localhost ][ port=13579 ] srv_name cmd arg ...',
+ 'srv -a [ host=localhost ][ port=13579 ] srv_name cmd arg ... ( all mode )',
'cli [ host=localhost ][ port=13579 ] upper word word word ...',
'cli [ host=localhost ][ port=13579 ] eval word word word ...',
'cli [ host=localhost ][ port=13579 ] srv_name',
+ 'cli -a [ host=localhost ][ port=13579 ] srv_name ( all mode )',
'srvs [ host=localhost ][ port=13579 ]',
'kill_base [ host=localhost ][ port=13579 ]',
'kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )'
]
dbg.help_exit( '\n'.join( lst ) )
+def run_cli( sc, mode ):
+ ( fi, fo ) = fio_ut.set_fs_sys_stdio()
+
+ def cnv_out( b ):
+ if not fio_ut.p_b( sc, b ):
+ return False
+
+ b = fio_ut.g_b_all( sc )
+ if not b:
+ return False
+
+ if not fio_ut.p_b( fo, b ):
+ return False
+
+ return True
+
+ if mode == 'all':
+ buf = b''
+ while True:
+ b = fio_ut.g_b_all( fi )
+ if not b:
+ break
+ buf += b
+ cnv_out( buf )
+ return
+
+ # mode == 'line'
+ while True:
+ b = fio_ut.g_b_line( fi )
+ if not b:
+ break
+ if not cnv_out( b ):
+ break
+
def run():
if len( sys.argv ) < 2:
help()
@@ -374,6 +412,12 @@
return argv.pop( i )[ len( k ) : ]
return dv
+ def pop_mode():
+ if argv[ 0 ] == '-a':
+ pop()
+ return 'all'
+ return 'line'
+
host = popk( 'host=', 'localhost' )
port = int( popk( 'port=', '13579' ) )
@@ -386,6 +430,7 @@
if cmd == 'base':
base_srv( port )
elif cmd == 'srv':
+ mode = pop_mode()
name = pop()
if name in names:
cb = names.get( name )
@@ -393,8 +438,9 @@
srv_loop( port, name, srv_func.func, host )
else:
cmd = ' '.join( argv )
- srv_loop_cmd( host, port, name, cmd )
+ srv_loop_cmd( host, port, name, cmd, mode )
elif cmd == 'cli':
+ mode = pop_mode()
lst = get_srvs( host, port )
name = pop()
if name not in lst:
@@ -414,21 +460,7 @@
break
dbg.out( '{} --> {}'.format( w, r ) )
else:
- ( fi, fo ) = fio_ut.set_fs_sys_stdio()
- while True:
- b = fio_ut.g_b_line( fi )
- if not b:
- break
-
- if not fio_ut.p_b( sc, b ):
- break
-
- b = fio_ut.g_b_all( sc )
- if not b:
- break
-
- if not fio_ut.p_b( fo, b ):
- break
+ run_cli( sc, mode )
close( sc )
elif cmd == 'srvs':
まずは、従来とおりの確認を
$ ./sock_ut.py Usage: ./sock_ut.py \ base [ port=13579 ] srv [ host=localhost ][ port=13579 ] upper srv [ host=localhost ][ port=13579 ] eval srv [ host=localhost ][ port=13579 ] srv_name cmd arg ... srv -a [ host=localhost ][ port=13579 ] srv_name cmd arg ... ( all mode ) cli [ host=localhost ][ port=13579 ] upper word word word ... cli [ host=localhost ][ port=13579 ] eval word word word ... cli [ host=localhost ][ port=13579 ] srv_name cli -a [ host=localhost ][ port=13579 ] srv_name ( all mode ) srvs [ host=localhost ][ port=13579 ] kill_base [ host=localhost ][ port=13579 ] kill [ host=localhost ][ port=13579 ] srv_name ( srv_name is upper, eval or ... )
OKログ
$ ./sock_ut.py base &
[1] 62391
$ ./sock_ut.py srv upper &
[2] 62392
srv upper start
$ ./sock_ut.py srv eval &
[3] 62393
srv eval start
$ ./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
$ ./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
$ ./sock_ut.py srv bc bc -l &
[4] 62396
srv bc start
$ ./sock_ut.py srvs
upper
eval
bc
$ ./sock_ut.py cli bc
1+1
2
10/3
3.33333333333333333333
^D
$
$ ./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
$ ./sock_ut.py kill_base
kill_base_srv
base_srv quit
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
$
そして、行単位じゃなくて、まとめて処理するタイプを。
例えば hexdump コマンド
$ hexdump hellow world ^D 0000000 68 65 6c 6c 6f 77 0a 77 6f 72 6c 64 0a 000000d $
これで試してみます。
$ ./sock_ut.py base & [1] 62448 $ ./sock_ut.py srv -a hd hexdump & [2] 62451 srv hd start $ ./sock_ut.py cli -a hd hellow world ^D 0000000 68 65 6c 6c 6f 77 0a 77 6f 72 6c 64 0a 000000d $ $ ./sock_ut.py srvs hd $ echo hello world | ./sock_ut.py cli -a hd 0000000 68 65 6c 6c 6f 20 77 6f 72 6c 64 0a 000000c $ $ ./sock_ut.py kill hd srv hd quit [2]+ Done ./sock_ut.py srv -a hd hexdump $ ./sock_ut.py kill_base kill_base_srv base_srv quit [1]+ Done ./sock_ut.py base $
OKです。
毎度の「まずは、従来とおりの確認を」のくだりが、 かなり面倒になってきました。
手っ取り早く自動化したいところです。
bashをあげて、古いプログラムの出力結果を記録しておいて、 更新後のプログラムでも同じ結果になるか比較するとかでどうでしょうか?
テスト手順を実行して記録するために、bashを起動しなおして試してみます。
標準出力をパイプでteeコマンドにつないで、ファイル/tmp/LOG1に記録してみます。
$ bash | tee /tmp/LOG1 $ PS1='> ' > ./sock_ut.py base & [1] 81070 > ./sock_ut.py srv upper & [2] 81072 > srv upper start ./sock_ut.py srv eval & [3] 81073 > srv eval start ./sock_ut.py cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE > ./sock_ut.py cli eval 1+1 2+3 1+1 --> 2 2+3 --> 5 > ./sock_ut.py srv bc bc -l & [4] 81076 > srv bc start ./sock_ut.py srvs upper eval bc > ./sock_ut.py cli bc 1+1 2 10/3 3.33333333333333333333 > ./sock_ut.py kill upper srv upper quit > ./sock_ut.py kill_base kill_base_srv base_srv quit [2] Done ./sock_ut.py srv upper > [1] Done ./sock_ut.py base [3]- Done ./sock_ut.py srv eval [4]+ Done ./sock_ut.py srv bc bc -l > exit exit $
テスト用に起動したbashに入っている事を分かりやすくするために、 bashを起動してすぐに、環境変数PS1を変更してプロンプト表示を変更しています。
記録した/tmp/LOG1 は
srv upper start srv eval start foo --> FOO bar --> BAR hoge --> HOGE 1+1 --> 2 2+3 --> 5 srv bc start upper eval bc 2 3.33333333333333333333 srv upper quit kill_base_srv base_srv quit
うーむ。bashの純粋な出力だけで、プロンプト表示や入力のエコーバックが含まれていません。
以前に pythonのユーティリティ・プログラム 2020冬
pty_spawn.py
#!/usr/bin/env python
import os
import sys
import pty
import dbg
if __name__ == "__main__":
cmd = sys.argv[1:]
if cmd:
pty.spawn( cmd )
else:
dbg.help_exit( 'cmd ...' )
# EOF
を作っていました。
これでbashを起動すれば、端末で試すやりとりを、何もかも記録出来そうです。
で配置されているものとして、まずはお試し。
$ python -m pty_spawn bash | tee /tmp/LOG2 $ PS1='> ' PS1='> ' > ./sock_ut.py base & ./sock_ut.py base & [1] 81095 > ./sock_ut.py srv upper & ./sock_ut.py srv upper & [2] 81096 > srv upper start ./sock_ut.py srv eval & ./sock_ut.py srv eval & [3] 81097 > srv eval start ./sock_ut.py cli upper foo bar hoge ./sock_ut.py cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE > ./sock_ut.py cli eval 1+1 2+3 ./sock_ut.py cli eval 1+1 2+3 1+1 --> 2 2+3 --> 5 > ./sock_ut.py srv bc bc -l & ./sock_ut.py srv bc bc -l & [4] 81113 > srv bc start ./sock_ut.py srvs ./sock_ut.py srvs upper eval bc > ./sock_ut.py cli bc ./sock_ut.py cli bc 1+1 1+1 2 10/3 10/3 3.33333333333333333333 > ./sock_ut.py kill upper ./sock_ut.py kill upper srv upper quit > ./sock_ut.py kill_base ./sock_ut.py kill_base kill_base_srv base_srv quit [2] Done ./sock_ut.py srv upper > [1] Done ./sock_ut.py base [3]- Done ./sock_ut.py srv eval [4]+ Done ./sock_ut.py srv bc bc -l > exit exit exit $
入力後のエコーバックの表示が重複していますが、 記録の結果の /tmp/LOG2 では
$ PS1='> ' > ./sock_ut.py base & [1] 81095 > ./sock_ut.py srv upper & [2] 81096 > srv upper start ./sock_ut.py srv eval & [3] 81097 > srv eval start ./sock_ut.py cli upper foo bar hoge foo --> FOO bar --> BAR hoge --> HOGE > ./sock_ut.py cli eval 1+1 2+3 1+1 --> 2 2+3 --> 5 > ./sock_ut.py srv bc bc -l & [4] 81113 > srv bc start ./sock_ut.py srvs upper eval bc > ./sock_ut.py cli bc 1+1 2 10/3 3.33333333333333333333 ^D^H^H> ./sock_ut.py kill upper srv upper quit > ./sock_ut.py kill_base kill_base_srv base_srv quit [2] Done ./sock_ut.py srv upper > [1] Done ./sock_ut.py base [3]- Done ./sock_ut.py srv eval [4]+ Done ./sock_ut.py srv bc bc -l > exit exit
プロンプトと入力のエコーバックが記録されています。
バックグランドプロセスからの出力が、 プロンプト表示の後にきてて、 微妙にわかりにくい箇所があります。
^D^H^H
の箇所があります。
^Dは、コントロールキー+DキーでEOTを入力した箇所ですが、 "^"と”D"の2バイトで記録されています。
直後の^Hは、 バックスペースキーを2回打ってタイプミスを修正した箇所だったでしょうか。 こちらは、
$ hd /tmp/LOG2 : 00001b0 33 33 33 33 33 33 33 33 33 33 33 33 0d 0a 5e 44 00001c0 08 08 3e 20 2e 2f 73 6f 63 6b 5f 75 74 2e 70 79 :
コード08 が2つ記録されていて、"^" + "H"ではありません。
hdコマンドの結果をよく見ると
: 0000160 6f 63 6b 5f 75 74 2e 70 79 20 73 72 76 73 0d 0a 0000170 75 70 70 65 72 0d 0a 65 76 61 6c 0d 0a 62 63 0d 0000180 0a 3e 20 2e 2f 73 6f 63 6b 5f 75 74 2e 70 79 20 :
0d 0a のDOS形式の改行コードです。
端末の制御コードとしては「復帰」「改行」が出力されているのが 記録されています。
/tmp/LOG2を眺めてみましたが、入力を促すプロンプト表示のあとに、 バックグランドプロセスからの出力が出てたりして、しっくりきません。
記録結果を編集して、ここからキー入力部分を表す目印を追加してはどうか?
キー入力は基本的に改行で終わってるし、 途中で出力が混じる事もまずないでしょう。
しかし^D。
ここには改行が入りませんが...
^Dの場合は特別扱いしましょうかね。
で、特別な印はとりあえず'CMD>>>'としておきます。
$ cp /tmp/LOG2 ./
LOG2編集して 'CMD>>>'を追加。
^H は削除しておきます。
ファイルLOG2
$ CMD>>>PS1='> '
> CMD>>>./sock_ut.py base &
[1] 81095
> CMD>>>./sock_ut.py srv upper &
[2] 81096
> srv upper start
CMD>>>./sock_ut.py srv eval &
[3] 81097
> srv eval start
CMD>>>./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> CMD>>>./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> CMD>>>./sock_ut.py srv bc bc -l &
[4] 81113
> srv bc start
CMD>>>./sock_ut.py srvs
upper
eval
bc
> CMD>>>./sock_ut.py cli bc
CMD>>>1+1
2
CMD>>>10/3
3.33333333333333333333
CMD>>>^D> CMD>>>./sock_ut.py kill upper
srv upper quit
> CMD>>>./sock_ut.py kill_base
kill_base_srv
base_srv quit
[2] Done ./sock_ut.py srv upper
> CMD>>>
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> CMD>>>exit
exit
プログラムから、 記録して'CMD>>>'を追加したファイルLOG2を読み込ませてみます。
改行コードの0x0d は削除して0x0aだけにしても大丈夫でしょう。
'CMD>>>'で分割してリストにしてみます。
test.py
#!/usr/bin/env python
import sys
import dbg
def run():
s = sys.stdin.read()
s = s.replace( '\r', '' )
lst = s.split( 'CMD>>>' )
for i in range( len( lst ) ):
dbg.out( 'lst[{}]="{}"'.format( i, lst[ i ] ) )
if __name__ == "__main__":
run()
# EOF
実行結果
$ ./test.py < LOG2
lst[0]="$ "
lst[1]="PS1='> '
> "
lst[2]="./sock_ut.py base &
[1] 81095
> "
lst[3]="./sock_ut.py srv upper &
[2] 81096
> srv upper start
"
lst[4]="./sock_ut.py srv eval &
[3] 81097
> srv eval start
"
lst[5]="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
lst[6]="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
lst[7]="./sock_ut.py srv bc bc -l &
[4] 81113
> srv bc start
"
lst[8]="./sock_ut.py srvs
upper
eval
bc
> "
lst[9]="./sock_ut.py cli bc
"
lst[10]="1+1
2
"
lst[11]="10/3
3.33333333333333333333
"
lst[12]="^D> "
lst[13]="./sock_ut.py kill upper
srv upper quit
> "
lst[14]="./sock_ut.py kill_base
kill_base_srv
base_srv quit
[2] Done ./sock_ut.py srv upper
> "
lst[15]="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
lst[16]="exit
exit
"
$
最初のlst[0]は起動してすぐのプロンプトだけなので気にせずスキップで良いとして...
lst[1]以降の文字列は、文字列の先頭から改行までがキー入力した内容。
改行から文字列の末尾までが、入力に反応して返ってきた内容。
ただし、改行コードは0x0aだけに変わってるので注意です。
そして、入力が '^D' の2文字のときは特別で、入力末尾の改行はありません。
リストのそれぞれの文字列を、入力コマンドと結果の文字列に分離してみます。
test.py
#!/usr/bin/env python
import sys
import dbg
def run():
s = sys.stdin.read()
s = s.replace( '\r', '' )
lst = s.split( 'CMD>>>' )
def func( s ):
n = 0
if s.startswith( '^D' ):
n = 2
elif '\n' in s:
n = s.index( '\n' ) + 1
return ( s[ : n ], s[ n : ] )
lst = list( map( func, lst ) )
for i in range( len( lst ) ):
dbg.out( 'lst[{}]={}'.format( i, lst[ i ] ) )
if __name__ == "__main__":
run()
# EOF
実行結果
$ ./test.py < LOG2
lst[0]=('', '$ ')
lst[1]=("PS1='> '\n", '> ')
lst[2]=('./sock_ut.py base &\n', '[1] 81095\n> ')
lst[3]=('./sock_ut.py srv upper &\n', '[2] 81096\n> srv upper start\n')
lst[4]=('./sock_ut.py srv eval &\n', '[3] 81097\n> srv eval start\n')
lst[5]=('./sock_ut.py cli upper foo bar hoge\n', 'foo --> FOO\nbar --> BAR\nhoge --> HOGE\n> ')
lst[6]=('./sock_ut.py cli eval 1+1 2+3\n', '1+1 --> 2\n2+3 --> 5\n> ')
lst[7]=('./sock_ut.py srv bc bc -l &\n', '[4] 81113\n> srv bc start\n')
lst[8]=('./sock_ut.py srvs\n', 'upper\neval\nbc\n> ')
lst[9]=('./sock_ut.py cli bc\n', '')
lst[10]=('1+1\n', '2\n')
lst[11]=('10/3\n', '3.33333333333333333333\n')
lst[12]=('^D', '> ')
lst[13]=('./sock_ut.py kill upper\n', 'srv upper quit\n> ')
lst[14]=('./sock_ut.py kill_base\n', 'kill_base_srv\nbase_srv quit\n[2] Done ./sock_ut.py srv upper\n> ')
lst[15]=('\n', '[1] Done ./sock_ut.py base\n[3]- Done ./sock_ut.py srv eval\n[4]+ Done ./sock_ut.py srv bc bc -l\n> ')
lst[16]=('exit\n', 'exit\n')
$
実行するコマンドはpty_spawn.pyで、引数でbashを指定します。
せっかくなので、fio_ut.py のproc_new( cmd ) を使ってみます。
lst[] の入力をbashプロセスの入力に与えて、bashプロセスからの出力を 表示してみて様子を見てみます。
test.py
#!/usr/bin/env python
import sys
import time
import fio_ut
import dbg
def run():
s = sys.stdin.read()
s = s.replace( '\r', '' )
lst = s.split( 'CMD>>>' )
def func( s ):
n = 0
if s.startswith( '^D' ):
n = 2
elif '\n' in s:
n = s.index( '\n' ) + 1
return ( s[ : n ], s[ n : ] )
lst = list( map( func, lst ) )
cmd = 'python -m pty_spawn bash'
proc = fio_ut.proc_new( cmd )
for ( si, so ) in lst:
if si:
dbg.out( 'si="{}"'.format( si ) )
if not fio_ut.p_s( proc.fo, si ):
dbg.out( 'err p_s' )
dbg.out( 'so="{}"'.format( so ) )
r = fio_ut.g_s_all( proc.fi )
if not r:
dbg.out( 'err g_s_all' )
r = r.replace( '\r', '' )
dbg.out( 'r="{}"'.format( r ) )
proc.stop()
if __name__ == "__main__":
run()
# EOF
実行結果
$ ./test.py < LOG2
so="$ "
r="$ "
si="PS1='> '
"
so="> "
r="PS1='> '
> "
si="./sock_ut.py base &
"
so="[1] 81095
> "
r="./sock_ut.py base &
"
si="./sock_ut.py srv upper &
"
so="[2] 81096
> srv upper start
"
r="[1] 81548
> ./sock_ut.py srv upper &
"
si="./sock_ut.py srv eval &
"
so="[3] 81097
> srv eval start
"
r="[2] 81549
> ./sock_ut.py srv eval &
"
si="./sock_ut.py cli upper foo bar hoge
"
so="foo --> FOO
bar --> BAR
hoge --> HOGE
> "
r="./sock_ut.py cli upper foo bar hoge
"
si="./sock_ut.py cli eval 1+1 2+3
"
so="1+1 --> 2
2+3 --> 5
> "
r="[3] 81550
> ./sock_ut.py cli eval 1+1 2+3
"
si="./sock_ut.py srv bc bc -l &
"
so="[4] 81113
> srv bc start
"
r="./sock_ut.py srv bc bc -l &
"
si="./sock_ut.py srvs
"
so="upper
eval
bc
> "
r="./sock_ut.py srvs
"
si="./sock_ut.py cli bc
"
so=""
r="./sock_ut.py cli bc
"
si="1+1
"
so="2
"
r="1+1
"
si="10/3
"
so="3.33333333333333333333
"
r="10/3
"
si="^D"
so="> "
r="^D"
si="./sock_ut.py kill upper
"
so="srv upper quit
> "
r="./sock_ut.py kill upper
"
si="./sock_ut.py kill_base
"
so="kill_base_srv
base_srv quit
[2] Done ./sock_ut.py srv upper
> "
r="./sock_ut.py kill_base
"
si="
"
so="[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
r="
"
si="exit
"
so="exit
"
r="exit
"
$
bashにコマンドの入力を与えて、 そのエコーバック出力が返ってきてから、 コマンドの実行結果が抜けてる箇所が多数あります。
fio_ut.py の proc_new( cmd ) での出力を貯めてるキューの処理が正しいとするならば、、、
bashの動作として、結果出力前に次のコマンドの入力があると、 前のコマンドの出力が出ずに捨てられて、 次のコマンドのエコーバックが出る!?
なんか釈然としませんが、 test.py でコマンドを与えてから、 出力を回収するまでに待機処理を追加して試してみると、、、
test.py
: for ( si, so ) in lst: if si: dbg.out( 'si="{}"'.format( si ) ) if not fio_ut.p_s( proc.fo, si ): dbg.out( 'err p_s' ) dbg.out( 'so="{}"'.format( so ) ) time.sleep( 1.0 ) r = fio_ut.g_s_all( proc.fi ) if not r: dbg.out( 'err g_s_all' ) r = r.replace( '\r', '' ) dbg.out( 'r="{}"'.format( r ) ) proc.stop() :
実行結果
$ ./test.py < LOG2
si+so="$ "
r="$ "
si+so="PS1='> '
> "
r="PS1='> '
> "
si+so="./sock_ut.py base &
[1] 81095
> "
r="./sock_ut.py base &
[1] 81605
> "
si+so="./sock_ut.py srv upper &
[2] 81096
> srv upper start
"
r="./sock_ut.py srv upper &
[2] 81606
> srv upper start
"
si+so="./sock_ut.py srv eval &
[3] 81097
> srv eval start
"
r="./sock_ut.py srv eval &
[3] 81607
> srv eval start
"
si+so="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
r="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
si+so="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
r="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
si+so="./sock_ut.py srv bc bc -l &
[4] 81113
> srv bc start
"
r="./sock_ut.py srv bc bc -l &
[4] 81610
> srv bc start
"
si+so="./sock_ut.py srvs
upper
eval
bc
> "
r="./sock_ut.py srvs
upper
eval
bc
> "
si+so="./sock_ut.py cli bc
"
r="./sock_ut.py cli bc
"
si+so="1+1
2
"
r="1+1
2
"
si+so="10/3
3.33333333333333333333
"
r="10/3
3.33333333333333333333
"
si+so="^D> "
r="> "
si+so="./sock_ut.py kill upper
srv upper quit
> "
r="./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> "
si+so="./sock_ut.py kill_base
kill_base_srv
base_srv quit
[2] Done ./sock_ut.py srv upper
> "
r="./sock_ut.py kill_base
kill_base_srv
base_srv quit
> "
si+so="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
r="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
si+so="exit
exit
"
r="exit
exit
"
$
: si+so="^D> " r="> " :
^Dはエコーバックは無しで。
test.py
: for ( si, so ) in lst: if si: s = '\x04' if si == '^D' else si if not fio_ut.p_s( proc.fo, s ): dbg.out( 'err p_s' ) exp = '' if si != '^D': exp = si exp += so dbg.out( 'exp="{}"'.format( exp ) ) time.sleep( 1.0 ) :
その直後の
: si+so="./sock_ut.py kill upper srv upper quit > " r="./sock_ut.py kill upper srv upper quit [2] Done ./sock_ut.py srv upper > " si+so="./sock_ut.py kill_base kill_base_srv base_srv quit [2] Done ./sock_ut.py srv upper > " r="./sock_ut.py kill_base kill_base_srv base_srv quit > " :
記録をとったときに比べて、
[2] Done ...
と
kill_base_srv base_srv quit
の表示の順序が入れ替わってます。
どっちかというと記録をとったときの "[2] Done ..." の表示が遅すぎ?
まぁこれは記録をとった時に、次のコマンド入力が早すぎた感じです。
ファイルLOG2を修正して調整してみます。
LOG2
: CMD>>>10/3 3.33333333333333333333 CMD>>>^D> CMD>>>./sock_ut.py kill upper srv upper quit [2] Done ./sock_ut.py srv upper > CMD>>>./sock_ut.py kill_base kill_base_srv base_srv quit :
実行結果
$ ./test.py < LOG2
exp="$ "
r="$ "
exp="PS1='> '
> "
r="PS1='> '
> "
exp="./sock_ut.py base &
[1] 81095
> "
r="./sock_ut.py base &
[1] 81633
> "
exp="./sock_ut.py srv upper &
[2] 81096
> srv upper start
"
r="./sock_ut.py srv upper &
[2] 81634
> srv upper start
"
exp="./sock_ut.py srv eval &
[3] 81097
> srv eval start
"
r="./sock_ut.py srv eval &
[3] 81635
> srv eval start
"
exp="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
r="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
exp="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
r="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
exp="./sock_ut.py srv bc bc -l &
[4] 81113
> srv bc start
"
r="./sock_ut.py srv bc bc -l &
[4] 81638
> srv bc start
"
exp="./sock_ut.py srvs
upper
eval
bc
> "
r="./sock_ut.py srvs
upper
eval
bc
> "
exp="./sock_ut.py cli bc
"
r="./sock_ut.py cli bc
"
exp="1+1
2
"
r="1+1
2
"
exp="10/3
3.33333333333333333333
"
r="10/3
3.33333333333333333333
"
exp="> "
r="> "
exp="./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> "
r="./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> "
exp="./sock_ut.py kill_base
kill_base_srv
base_srv quit
> "
r="./sock_ut.py kill_base
kill_base_srv
base_srv quit
> "
exp="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
r="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
exp="exit
exit
"
r="exit
exit
"
$
ここまでくれば、かなり自動化が見込めますが、あともう一歩。
: exp="./sock_ut.py base & [1] 81095 > " r="./sock_ut.py base & [1] 81633 > " :
バックグランドジョブのプロセス番号は、 一致しなくてもテスト的にOKです。
なのでファイルLOG2のこのような箇所は、 ワイルドカードとして"*"に書き換えておいて、 一致判定の方でスキップさせるようにすれば良さそうです。
2つの文字列の一致判定で、 雛形側に"*"が含まれていた時に、 スキップして必要な箇所だけを正しく比較する処理を先に作っておきます。
def wild_eq( s, t ): if '*' not in t: return s == t lst = t.split( '*' ) t = lst.pop( 0 ) if t: if not s.startswith( t ): return False s = s[ len( t ) : ] t = lst.pop( -1 ) if t: if not s.endswith( t ): return False s = s[ : -len( t ) ] for t in lst: if not t: continue if t not in s: return False i = s.index( t ) + len( t ) s = s[ i : ] return True
文字列 s, t の比較で、t側に"*"のワイルドカードを含める事を許します。
test.py に追加しておいて、確認用にt.pyを作成。
#!/usr/bin/env python import sys import test def run(): s = sys.argv[ 1 ] t = sys.argv[ 2 ] r = test.wild_eq( s, t ) print( str( r ) ) if __name__ == "__main__": run() # EOF
wild_eq()のテスト
$ chmod +x t.py
$ ./t.py abcdefghi abc
False
$ ./t.py abcdefghi abcdefghi
True
$ ./t.py abcdefghi abc*ghi
True
$ ./t.py abcdefghi abc*
True
$ ./t.py abcdefghi *abcdefghi
True
$ ./t.py abcdefghi abcdefghi*
True
$ ./t.py abcdefghi *abcdefghi*
True
$ ./t.py abcdefghi a*bc*fg*
True
$ ./t.py abcdefghi *fed*
False
$ ./t.py abcdefghi "*"
True
大丈夫そうですね。
ファイルLOG2の不確定部分をワイルドカード"*"に置き換えます。
ファイルLOG
$ CMD>>>PS1='> '
> CMD>>>./sock_ut.py base &
[1] *
> CMD>>>./sock_ut.py srv upper &
[2] *
> srv upper start
CMD>>>./sock_ut.py srv eval &
[3] *
> srv eval start
CMD>>>./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> CMD>>>./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> CMD>>>./sock_ut.py srv bc bc -l &
[4] *
> srv bc start
CMD>>>./sock_ut.py srvs
upper
eval
bc
> CMD>>>./sock_ut.py cli bc
CMD>>>1+1
2
CMD>>>10/3
3.33333333333333333333
CMD>>>^D> CMD>>>./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> CMD>>>./sock_ut.py kill_base
kill_base_srv
base_srv quit
> CMD>>>
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> CMD>>>exit
exit
test.py で exp の文字列とbashの出力 r の文字列を、 wild_eq() で比較して判定してみます。
test.py
#!/usr/bin/env python
import sys
import time
import fio_ut
import dbg
def wild_eq( s, t ):
if '*' not in t:
return s == t
lst = t.split( '*' )
t = lst.pop( 0 )
if t:
if not s.startswith( t ):
return False
s = s[ len( t ) : ]
t = lst.pop( -1 )
if t:
if not s.endswith( t ):
return False
s = s[ : -len( t ) ]
for t in lst:
if not t:
continue
if t not in s:
return False
i = s.index( t ) + len( t )
s = s[ i : ]
return True
def run():
s = sys.stdin.read()
s = s.replace( '\r', '' )
lst = s.split( 'CMD>>>' )
def func( s ):
n = 0
if s.startswith( '^D' ):
n = 2
elif '\n' in s:
n = s.index( '\n' ) + 1
return ( s[ : n ], s[ n : ] )
lst = list( map( func, lst ) )
cmd = 'python -m pty_spawn bash'
proc = fio_ut.proc_new( cmd )
res = True
for ( si, so ) in lst:
if si:
s = '\x04' if si == '^D' else si
if not fio_ut.p_s( proc.fo, s ):
dbg.out( 'err p_s' )
exp = ''
if si != '^D':
exp = si
exp += so
#dbg.out( 'exp="{}"'.format( exp ) )
time.sleep( 1.0 )
r = fio_ut.g_s_all( proc.fi )
if not r:
dbg.out( 'err g_s_all' )
r = r.replace( '\r', '' )
#dbg.out( 'r="{}"'.format( r ) )
res = wild_eq( r, exp )
if not res:
dbg.out( 'NG cmd "{}" get "{}", exp is "{}"'.format( si, r, exp ) )
break
if res:
dbg.out( 'OK' )
proc.stop()
if __name__ == "__main__":
run()
# EOF
実行結果
$ ./test.py < LOG2 NG cmd "^D" get "> ", exp is "> "
なんと!
見た目一致してますが?
: res = wild_eq( r, exp ) if not res: dbg.out( 'NG cmd "{}" get "{}", exp is "{}"'.format( si, r, exp ) ) dbg.out( '{}'.format( list( map( lambda c: '{:x}'.format( ord( c ) ), r ) ) ) ) break :
r の表示されない部分を。
$ ./test.py < LOG2 NG cmd "^D" get "> ", exp is "> " ['5e', '44', '8', '8', '3e', '20']
$ man ascii : The hexadecimal set: 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del :
なーんと
"^", "D", backspace, backspace, ">", " "
入力としては eot(0x04) を与えてるのに、 返ってくるエコーバックが 一旦 "^" と "D" の2文字をだしつつ、 続いてバックスペース 2 がきて、 そのまま表示すると何も出ない...
pty.spawnがらみのどこかの層で、そんな事がおこなわれていようとは!
最初に実行結果の記録を見てた段階で現れていた"^D^H^H"。
^Hはタイプミスの修正をしたときのものと思い込んでいましたが、 そうでなくて、^Dに付随してたものだったんですね。
となると、ファイルに記録された "^", "D", "\b", "\b"。
手動で"\b", "\b”を削除してましたが、エディタが2つの"\b"を残してくれるなら、 その方が都合良いですね。
一旦削除しましたが入れてみましょう。 emacsなら^Q^Hで入りますね。
LOG2
$ CMD>>>PS1='> '
> CMD>>>./sock_ut.py base &
[1] *
> CMD>>>./sock_ut.py srv upper &
[2] *
> srv upper start
CMD>>>./sock_ut.py srv eval &
[3] *
> srv eval start
CMD>>>./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> CMD>>>./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> CMD>>>./sock_ut.py srv bc bc -l &
[4] *
> srv bc start
CMD>>>./sock_ut.py srvs
upper
eval
bc
> CMD>>>./sock_ut.py cli bc
CMD>>>1+1
2
CMD>>>10/3
3.33333333333333333333
CMD>>>^D> CMD>>>./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> CMD>>>./sock_ut.py kill_base
kill_base_srv
base_srv quit
> CMD>>>
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> CMD>>>exit
exit
で、今のところtest.pyで^Dを絶賛特別扱い中なので、そこで修正を入れてみます。
: for ( si, so ) in lst: if si: s = '\x04' if si == '^D' else si if not fio_ut.p_s( proc.fo, s ): dbg.out( 'err p_s' ) exp = si + so dbg.out( 'exp="{}"'.format( exp ) ) :
$ ./test.py < LOG2 NG cmd "./sock_ut.py kill upper " get "./sock_ut.py kill upper srv upper quit > ", exp is "./sock_ut.py kill upper srv upper quit [2] Done ./sock_ut.py srv upper > " ['2e', '2f', '73', ... $
こ、これは、例の順番を入れ替えた箇所です。
もうちょっと待ちが必要なのかな?
: for ( si, so ) in lst: if si: s = '\x04' if si == '^D' else si if not fio_ut.p_s( proc.fo, s ): dbg.out( 'err p_s' ) exp = si + so dbg.out( 'exp="{}"'.format( exp ) ) time.sleep( 2.0 ) :
expのデバッグアウトを出して、待機を1秒から2秒に増やしてみます。
実行結果
$ ./test.py < LOG2
exp="$ "
exp="PS1='> '
> "
exp="./sock_ut.py base &
[1] *
> "
exp="./sock_ut.py srv upper &
[2] *
> srv upper start
"
exp="./sock_ut.py srv eval &
[3] *
> srv eval start
"
exp="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
exp="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
exp="./sock_ut.py srv bc bc -l &
[4] *
> srv bc start
"
exp="./sock_ut.py srvs
upper
eval
bc
> "
exp="./sock_ut.py cli bc
"
exp="1+1
2
"
exp="10/3
3.33333333333333333333
"
exp="> "
exp="./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> "
NG cmd "./sock_ut.py kill upper
" get "./sock_ut.py kill upper
srv upper quit
> ", exp is "./sock_ut.py kill upper
srv upper quit
[2] Done ./sock_ut.py srv upper
> "
['2e', '2f', '73', '6f', '63', '6b', '5f', '75', '74', '2e', '70', '79', '20', '6b', '69', '6c', '6c', '20', '75', '70', '70', '65', '72', 'a', '73', '72', '76', '20', '75', '70', '70', '65', '72', '20', '71', '75', '69', '74', 'a', '3e', '20']
$
だめですね。
明示的に改行を入力して、"[2] Done ..." が出ないか試してみます。
LOG2
$ CMD>>>PS1='> '
> CMD>>>./sock_ut.py base &
[1] *
> CMD>>>./sock_ut.py srv upper &
[2] *
> srv upper start
CMD>>>./sock_ut.py srv eval &
[3] *
> srv eval start
CMD>>>./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> CMD>>>./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> CMD>>>./sock_ut.py srv bc bc -l &
[4] *
> srv bc start
CMD>>>./sock_ut.py srvs
upper
eval
bc
> CMD>>>./sock_ut.py cli bc
CMD>>>1+1
2
CMD>>>10/3
3.33333333333333333333
CMD>>>^D> CMD>>>./sock_ut.py kill upper
srv upper quit
> CMD>>>
[2] Done ./sock_ut.py srv upper
> CMD>>>./sock_ut.py kill_base
kill_base_srv
base_srv quit
> CMD>>>
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> CMD>>>exit
exit
実行結果
$ ./test.py < LOG2
exp="$ "
exp="PS1='> '
> "
exp="./sock_ut.py base &
[1] *
> "
exp="./sock_ut.py srv upper &
[2] *
> srv upper start
"
exp="./sock_ut.py srv eval &
[3] *
> srv eval start
"
exp="./sock_ut.py cli upper foo bar hoge
foo --> FOO
bar --> BAR
hoge --> HOGE
> "
exp="./sock_ut.py cli eval 1+1 2+3
1+1 --> 2
2+3 --> 5
> "
exp="./sock_ut.py srv bc bc -l &
[4] *
> srv bc start
"
exp="./sock_ut.py srvs
upper
eval
bc
> "
exp="./sock_ut.py cli bc
"
exp="1+1
2
"
exp="10/3
3.33333333333333333333
"
exp="> "
exp="./sock_ut.py kill upper
srv upper quit
> "
exp="
[2] Done ./sock_ut.py srv upper
> "
exp="./sock_ut.py kill_base
kill_base_srv
base_srv quit
> "
exp="
[1] Done ./sock_ut.py base
[3]- Done ./sock_ut.py srv eval
[4]+ Done ./sock_ut.py srv bc bc -l
> "
exp="exit
exit
"
OK
$
ようやくOKでました。
これなら待機1秒に戻しても大丈夫かな?
: exp = si + so dbg.out( 'exp="{}"'.format( exp ) ) time.sleep( 1.0 ) :
1秒でもOKでした。
デバッグアウトを整理して、入力するコマンドだけを表示するようにしてみました。
test.py
#!/usr/bin/env python
import sys
import time
import fio_ut
import dbg
def wild_eq( s, t ):
if '*' not in t:
return s == t
lst = t.split( '*' )
t = lst.pop( 0 )
if t:
if not s.startswith( t ):
return False
s = s[ len( t ) : ]
t = lst.pop( -1 )
if t:
if not s.endswith( t ):
return False
s = s[ : -len( t ) ]
for t in lst:
if not t:
continue
if t not in s:
return False
i = s.index( t ) + len( t )
s = s[ i : ]
return True
def run():
s = sys.stdin.read()
s = s.replace( '\r', '' )
lst = s.split( 'CMD>>>' )
def func( s ):
n = 0
if s.startswith( '^D' ):
n = 2
elif '\n' in s:
n = s.index( '\n' ) + 1
return ( s[ : n ], s[ n : ] )
lst = list( map( func, lst ) )
cmd = 'python -m pty_spawn bash'
proc = fio_ut.proc_new( cmd )
res = True
for ( si, so ) in lst:
if si:
dbg.out( si, '' )
s = '\x04' if si == '^D' else si
if not fio_ut.p_s( proc.fo, s ):
dbg.out( 'err p_s' )
exp = si + so
#dbg.out( 'exp="{}"'.format( exp ) )
time.sleep( 1.0 )
r = fio_ut.g_s_all( proc.fi )
if not r:
dbg.out( 'err g_s_all' )
r = r.replace( '\r', '' )
#dbg.out( 'r="{}"'.format( r ) )
res = wild_eq( r, exp )
if not res:
dbg.out( 'NG cmd "{}" get "{}", exp is "{}"'.format( si, r, exp ) )
#dbg.out( '{}'.format( list( map( lambda c: '{:x}'.format( ord( c ) ), r ) ) ) )
break
if res:
dbg.out( 'OK' )
proc.stop()
if __name__ == "__main__":
run()
# EOF
実行結果
$ ./test.py < LOG2
PS1='> '
./sock_ut.py base &
./sock_ut.py srv upper &
./sock_ut.py srv eval &
./sock_ut.py cli upper foo bar hoge
./sock_ut.py cli eval 1+1 2+3
./sock_ut.py srv bc bc -l &
./sock_ut.py srvs
./sock_ut.py cli bc
1+1
10/3
^D./sock_ut.py kill upper
./sock_ut.py kill_base
exit
OK
$