2024/MAR/17
更新履歴
日付
変更内容 2024/MAR/17
新規作成
ちょいとお仕事でTCPソケットを使うので、基本動作の確認をば。
2台のUbunut PCを1つをwifiアクセスポイントにして、もう1つでそのwifiに接続して、TCPソケットでデータをやりとりします。
ただし、PCお距離が遠ざかったり近づいたりしてwifiの接続が切断、復旧を繰り返します。
アクセスポイント側のPCで、TCPソケットのポートを開いてサーバとして待ちます。
もう一台のPCはクライアントとして、サーバに接続しにいきます。
test_sock.py
#!/usr/bin/env python3
import sys
import time
import select
import socket
import empty
import thr
def close( sock ):
sock.close()
def conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
cs.connect( ( host, port ) )
except:
return None
return cs
def send( sock, s ):
try:
b = s.encode()
sock.sendall( b )
except:
return False
return True
def recv( sock ):
while True:
r = select.select( [ sock ], [], [], 1.0 )
if r[ 0 ]:
break
s = ''
try:
bufmax = 100 * 1024
b = sock.recv( bufmax )
s = b.decode()
except:
return None
return s
def recv_thr_new( sock, cb ):
e = empty.new()
e.quited = False
def f():
s = recv( sock )
if not s:
quit()
return
cb( s )
th = thr.loop_new( f )
def quit():
if e.quited:
return
e.quited = True
th.quit_ev.set()
close( sock )
cb( None )
th.start()
def stop():
quit()
th.stop()
return empty.add( e, locals() )
def srv_sock_new( 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 )
return ss
def accept( sock ):
try:
( cs, adr ) = sock.accept()
except:
return None
return cs
def acc_new( cs, cb_accept, accs, quits ):
acc = empty.new()
accs.append( acc )
th_acc = thr.th_new( cb_accept, ( acc, ) )
def quit():
if not th_acc.quit_ev.is_set():
th_acc.quit_ev.set()
if acc in accs:
accs.remove( acc )
quits.append( acc )
def stop():
quit()
th_acc.stop()
if acc in quits:
quits.remove( acc )
empty.add( acc, locals() )
th_acc.start()
return acc
def srv_new( port, cb_accept ):
srv = empty.new()
ss = srv_sock_new( port )
accs = []
quits = []
def gc():
while quits:
acc = quits.pop( 0 )
acc.stop()
def f_loop():
cs = accept( ss )
if not cs:
return
acc_new( cs, cb_accept, accs, quits )
gc()
th = thr.loop_new( f_loop )
th.start()
def stop():
th.quit_ev.set()
close( ss )
th.stop()
while accs:
acc = accs.pop( 0 )
acc.stop()
gc()
return empty.add( srv, locals() )
def cb_accept( acc ):
def cb( s ):
print( "srv recv {}".format( s ) )
print( ">srv send {}".format( s ) )
r = send( acc.cs, s )
print( "<srv send r={}".format( r ) )
recv_thr = recv_thr_new( acc.cs, cb )
recv_thr.th.quit_ev.wait()
def run():
argv = sys.argv[ 1 : ]
port = 1234
quit_ev = thr.event_new()
if not argv: # srv
srv_new( port, cb_accept )
quit_ev.wait()
return
# cli
host = argv[ 0 ]
cs = conn( port, host )
print( "cli conn {}".format( cs ) )
if not cs:
return
ev = thr.event_new()
def cb( s ):
print( "cli recv {}".format( s ) )
ev.set()
recv_th = recv_thr_new( cs, cb )
i = 0
while True:
ev.clear()
s = str( i )
print( "cli> send {}".format( s ) )
r = send( cs, s )
print( "cli send r={}".format( r ) )
ev.wait()
i += 1
time.sleep( 1 )
if __name__ == "__main__":
run()
# EOF
をつかってます。
test_sock.py の前半の srv_new() まではライブラリ的な部品です。
close( sock ) | 単に sock.close() です。
以前に sock.shutdown() を実行していた名残りで、ラッパーのまま残してます。 |
conn( port, host='localhost' ) | クライアントのconnect用です。 |
send( sock, s ) | 送信っす。 |
recv( sock ) | 受信っす。 |
recv_thr_new( sock, cb ) | 受信用のスレッドを生成します。
内部で recv( sock) を呼び出して受信すると、コールバック関数 cb( s ) を呼び出します。 |
srv_sock_new( port ) | サーバソケットを生成します。 |
accept( sock ) | サーバでの accept 用です。 |
acc_new( cs, cb_accept, accs, quits ) | サーバでアクセプトしたときにスレッドを生成して、コールバック関数 cb_accept を呼び出す処理です。 |
srv_new( port, cb_accept ) | サーバを生成します。
クライアントが接続しにくると、acc_new() を使用してスレッドを生成して、コールバック関数 cb_accept を呼び出します。 |
test_sock.py の後半がテスト動作用の処理です。
cb_accept( acc ) | サーバ側でクライアントが接続しにきて accept すると、スレッドが生成されてこの関数が実行されます。 |
recv_thr_new() で受信用のスレッドを走らせておいて、クライアントからデータを受信すると、cb( s ) が呼び出されます。 | |
cb( s ) では、データを表示したあと、同じデータをクライアントに送り返しています。 | |
run() | 引数なしで実行すると、サーバとして動作します。 |
引数で、ホスト名(やIPアドレス)を指定すると、クライアントとして動作します。 | |
指定したホスト名(やIPアドレス)のサーバに接続にしきます。 | |
文字列をサーバにデータ送信して、サーバから送り返されてくるデータを待ちます。 |
自宅のwifi環境に Mac と ThinkPad を接続。
サーバ側 | MacBook Air | OS X (10.11.6) |
クライアント側 | ThinkPad | Ubuntu 18.04 |
$ ./test_sock.py
./test_sock.py 192.168.11.10 cli conn <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.11.11', 37276), raddr=('192.168.11.10', 1234)> cli> send 0 cli send r=True cli recv 0 cli> send 1 cli send r=True cli recv 1 cli> send 2 cli send r=True cli recv 2 cli> send 3 cli send r=True cli recv 3 cli> send 4 cli send r=True cli recv 4 cli> send 5 cli send r=True cli recv 5 cli> send 6 cli send r=True cli recv 6 cli> send 7 cli send r=True cli recv 7 cli> send 8 cli send r=True cli recv 8 cli> send 9 cli send r=True
srv recv 0 >srv send 0 <srv send r=True srv recv 1 >srv send 1 <srv send r=True srv recv 2 >srv send 2 <srv send r=True srv recv 3 >srv send 3 <srv send r=True srv recv 4 >srv send 4 <srv send r=True srv recv 5 >srv send 5 <srv send r=True srv recv 6 >srv send 6 <srv send r=True srv recv 7 >srv send 7 <srv send r=True srv recv 8 >srv send 8 <srv send r=True
上記の状態のまま、双方の画面の更新は停止したたままに。
クライアント側は データ "9" の send() が成功 (True) で返ってきて、 受信スレッドが recv() でブロックしたまま。
: cli> send 9 cli send r=True
サーバ側はデータ "8" の send(0 が 成功 (True ) で返ってきて、 受信スレッドが recv() でブロックしたまま。
: >srv send 8 <srv send r=True
よって、クライアント側が send() に成功した "9" は、まだ届いてない状態。
(クライアント側の send() では、サーバにデータが届いてなくても、 とりあえず成功で返るものなんだな...)
このまま 3分くらい待機。
サーバ側
srv recv 9 >srv send 9 <srv send r=True srv recv 10 >srv send 10 <srv send r=True srv recv 11 >srv send 11 <srv send r=True srv recv 12 >srv send 12 <srv send r=True srv recv 13 >srv send 13 <srv send r=True srv recv 14 >srv send 14 <srv send r=True
クライアント側が最後にsend()成功していたはずのデータ "9" を受信。
何事もなかったように、続きのデータも順調に受信して、送り返している。
クライアント側
cli recv 9 cli> send 10 cli send r=True cli recv 10 cli> send 11 cli send r=True cli recv 11 cli> send 12 cli send r=True cli recv 12 cli> send 13 cli send r=True cli recv 13 cli> send 14
再開してサーバが送り返した "9" を受信。
こちらも何事もなかったように、後続のデータを送信。
このように、常にハンドシェイクしたやりとりだと、まず問題なさそう。
TCP接続は信頼性のある、再送アリのプロトコルなので、まぁ当たり前。
お仕事で想定しているのは、サーバからクライアントに向けて、一方通的にデータが流れるタイプ。
この場合、wifi切れた状態でサーバからの複数の send() はどうなるか?
wifi復帰したときは?
test_sock2.py
#!/usr/bin/env python3
import sys
import time
import select
import socket
import empty
import thr
def close( sock ):
sock.close()
def conn( port, host='localhost' ):
cs = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
try:
cs.connect( ( host, port ) )
except:
return None
return cs
def send( sock, s ):
try:
b = s.encode()
sock.sendall( b )
except:
return False
return True
def recv( sock ):
while True:
r = select.select( [ sock ], [], [], 1.0 )
if r[ 0 ]:
break
s = ''
try:
bufmax = 100 * 1024
b = sock.recv( bufmax )
s = b.decode()
except:
return None
return s
def recv_thr_new( sock, cb ):
e = empty.new()
e.quited = False
def f():
s = recv( sock )
if not s:
quit()
return
cb( s )
th = thr.loop_new( f )
def quit():
if e.quited:
return
e.quited = True
th.quit_ev.set()
close( sock )
cb( None )
th.start()
def stop():
quit()
th.stop()
return empty.add( e, locals() )
def srv_sock_new( 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 )
return ss
def accept( sock ):
try:
( cs, adr ) = sock.accept()
except:
return None
return cs
def acc_new( cs, cb_accept, accs, quits ):
acc = empty.new()
accs.append( acc )
th_acc = thr.th_new( cb_accept, ( acc, ) )
def quit():
if not th_acc.quit_ev.is_set():
th_acc.quit_ev.set()
if acc in accs:
accs.remove( acc )
quits.append( acc )
def stop():
quit()
th_acc.stop()
if acc in quits:
quits.remove( acc )
empty.add( acc, locals() )
th_acc.start()
return acc
def srv_new( port, cb_accept ):
srv = empty.new()
ss = srv_sock_new( port )
accs = []
quits = []
def gc():
while quits:
acc = quits.pop( 0 )
acc.stop()
def f_loop():
cs = accept( ss )
if not cs:
return
acc_new( cs, cb_accept, accs, quits )
gc()
th = thr.loop_new( f_loop )
th.start()
def stop():
th.quit_ev.set()
close( ss )
th.stop()
while accs:
acc = accs.pop( 0 )
acc.stop()
gc()
return empty.add( srv, locals() )
def cb_accept( acc ):
i = 0
while True:
s = str( i )
print( "srv> send {}".format( s ) )
r = send( acc.cs, s )
print( "srv send r={}".format( r ) )
i += 1
time.sleep( 1 )
def run():
argv = sys.argv[ 1 : ]
port = 1234
quit_ev = thr.event_new()
if not argv: # srv
srv_new( port, cb_accept )
quit_ev.wait()
return
# cli
host = argv[ 0 ]
cs = conn( port, host )
print( "cli conn {}".format( cs ) )
if not cs:
return
def cb( s ):
print( "cli recv {}".format( s ) )
recv_th = recv_thr_new( cs, cb )
quit_ev.wait()
if __name__ == "__main__":
run()
# EOF
クライアントからサーバへ接続しにいくと、 サーバからデータを送り続けるタイプに変更しました。
クライアント側では受信したデータを表示するだけです。
$ ./test_sock2.py
$ ./test_sock2.py 192.168.11.10 cli conn <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.11.11', 49192), raddr=('192.168.11.10', 1234)> cli recv 0 cli recv 1 cli recv 2 cli recv 3 cli recv 4 cli recv 5 cli recv 6 cli recv 7 cli recv 8
サーバからのデータを順調に受信して表示してます。
サーバ側
srv> send 0 srv send r=True srv> send 1 srv send r=True srv> send 2 srv send r=True srv> send 3 srv send r=True srv> send 4 srv send r=True srv> send 5 srv send r=True srv> send 6 srv send r=True srv> send 7 srv send r=True srv> send 8 srv send r=True
クライアント側の表示は上記のまま固定。
サーバ側
srv> send 9 srv send r=True srv> send 10 srv send r=True srv> send 11 srv send r=True srv> send 12 srv send r=True srv> send 13 srv send r=True srv> send 14 srv send r=True srv> send 15 :
全然 send() 成功し続けて、何事も無いように進行してます。
しばし待機。
サーバ側は、何事もないまま、send() 成功の表示を順調に繰り返したままです。
クライアント側
cli recv 9101112131415161718192021222324252627282930313233343536 cli recv 37 cli recv 38 cli recv 39 cli recv 40
突然、受信スレッドが 1回の recv() で、一気に溜まってるデータを返してきました。
"9", "10", "11", ... "36" まで、抜けなく文字列が合体して返ってきました。
サーバ側は
: srv send r=True srv> send 31 srv send r=True srv> send 32 srv send r=True srv> send 33 srv send r=True srv> send 34 srv send r=True srv> send 35 srv send r=True srv> send 36 srv send r=True srv> send 37 srv send r=True srv> send 38 srv send r=True srv> send 39 srv send r=True srv> send 40 :
クライアント側のwifiがOFFになってようがONになってようが、 全然関係ないかのように順調な表示が続いてます。
^Cキーで停止してみます。
: cli recv 41 cli recv 42 cli recv 43 cli recv 44 ^CTraceback (most recent call last): File "./test_sock2.py", line 198, in <module> run() File "./test_sock2.py", line 194, in run quit_ev.wait() File "/usr/lib/python3.6/threading.py", line 551, in wait signaled = self._cond.wait(timeout) File "/usr/lib/python3.6/threading.py", line 295, in wait waiter.acquire() KeyboardInterrupt
サーバ側
: srv> send 43 srv send r=True srv> send 44 srv send r=True srv> send 45 srv send r=True srv> send 46 srv send r=False srv> send 47 srv send r=False srv> send 48 srv send r=False srv> send 49 srv send r=False srv> send 50 :
さすがに send() False で送信失敗が返ってます。
クライアント側でソケットがクローズされるので、サーバ側のsend()が失敗します。
ということは、この一方通行のタイプでは、 クライアント側でwifiが届かない事を、サーバ側で検知できませんね。
サーバ側で、もっともっと大量のデータを送ろうとすると、 途絶えている間のバッファリングが、やばそうです。
クライアント側からサーバ側へ、定期的に生存確認的なデータを流すなどして、 wifiが届いて無い事をサーバ側で検知させるしかなさそうですね。