https://docs.python.org/ja/3/howto/sockets.html
こちらを読んだ際のメモです。
読んでいるときの脳内をダンプしたものなので、単純に内容を知りたい方は直接ドキュメントを読むことをおすすめします。
python3.8.2
なぜドキュメントを読んでいるか
- ここらでドキュメントを読んで、知らないだけで実は使うと便利な機能を知っておきたい
- pythonを使い始めてかれこれ8年ほどになるが、雰囲気でやっているので未だに知らない機能がある
- flask・djangoやzmqなどたまに使っているが、内部のソケットプログラミングからは目をそらしながら生きてきた
- 詳しいことはともかくざっくりとソケットって何やってんだろうと知っておきたい
概要
- 一般的なソケットプログラミング、なかでもINETのSTREAM(すなわちIPv4のTCP)について、pythonで書くことを通して簡単に説明するとのこと
- 現時点では何を言っているのか全くわからない
- ソケットプログラミングについても、pythonでの書き方についても、詳解するわけではなく、
恥ずかしくない使い方ができるようになる程度の情報は得られるはず
とのこと
メモ
歴史
- ソケットはBSD Unixの一部としてバークレイで発明され、めっちゃ普及した
ソケットの作成
- client sideとserver sideのそれぞれのソケットが存在する
- このあたりはzmqでもそうなので、ソケットレベルを意識したインターフェースになってそう
- なのでzmqでイメージしながら読んでみる
client side
client_socket.py
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("qiita.com", 80))
これだけ。簡単
server side
server_side.py
# server socketを作る
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 80))
serversocket.listen(5)
# serving
while True:
clientsocket, address = serversocket.accept()
# threaded server
ct = client_thread(clientsocket)
ct.run()
ちょっと多いが、結構単純。
- 1行目は、変数名はserversocketだがやってることはclientsocketと全く同じ
- サーバーソケットとは、listenしているportに何かがconnect()してきたら、そいつと喋る(=送信する)クライアントソケットを作るやつのことだった
- 作ったら自分は引き続きlistenに戻る
-
昨日読んだディスクリプタの文脈では、例えばオブジェクトに関数をboundするとは関数のデフォルトの1個目の引数に常に自分自身を代入することだった
- ここでのbindも同じ'束縛'と解釈した
- つまり、まずは匿名のserversocketを作って、聞きに行くホストとしてsocket.gethostname()を登録したということか
- ディスクリプタの例から類推すると、bindしなくても動的にlistenする先を調節したりといったことをしているものも世の中にはありそう
- また、clientsocket.connectではホストとportを渡しているが、serversocketではここで使っているもっとプリミティブなめそっどにデフォルトで渡すようになっているのではないかと想像する
ソケットの例
- もう少し具体的な例で説明している
- server sideで生成されたclientsocketはclient sideで精製したものと同じ
- 上述のclientからserverにconnect→serverがclientsocketを生成→clientsocket同士が喋るという流れはあくまで1レベル抽象的な設計であってソケット自体の仕様ではない
- コード例はsocketでのメッセージの送受信のわかりやすい例
- メッセージを全部送りきれたか、ネットワークバッファに残っているか否かを気にしなければいけない
- あとはzmqと似たような感じ?
- 複数のメッセージを受け取るのはむずかしく、切れ目が自明ではない
- いくつか解決策とその問題点が示されている
- 冒頭に長さをまず送るのは良い解法に思えるが、例えば5桁の数字でデータ長を示しても、高負荷ネットワークでは5文字を一度に受信出来るとは限らない
バイナリデータ
- エンディアンが異なると変換が必要
- また、longを大量に送る場合、0はlongでは4バイトだがASCIIなら2バイト
- データをよく見て選ぼうということか
切断
- 切断時、本来は
shutdown()
してclose()
する- pythonのライブラリは
close()
のみでshutdown(); close();
をやってくれる
- pythonのライブラリは
- しかもpythonではGCされるときに自動で
close
してくれる- しかし意図せずsocketが死ぬと相手がハングするかもしれないのでこれに頼り切らないほうが良い
ノンブロッキングソケット
-
socket.setblocking(0)
とすればノンブロッキング - ブロッキングソケットと異なるのは、send, recv, connect, acceptが何もしないで戻ってくる可能性がある
- 選択肢1: 返り値とエラーをハンドリングする→発狂するらしい
- 選択肢2: selectを使う
- select.selectにsocketのリストを渡すと、読み/書き可能な状態のもののみ帰ってくるのでそれらを使って上記動詞を使えばいい
実装
せっかくなので実装もちらっと見てみた
https://github.com/python/cpython/blob/3.8/Lib/socket.py#L213
- with構文で使えばコンテキストを出ればcloseするようにかけるっぽい
- 人間が使う際は、SocketIOやcreate_serverを使うのが良いのだろう
感想
- もっと深く理解するにはINET, STREAM以外も見たほうが良い気がする
- 直接使うことは今後も無いだろうが、zmqはかなり良い抽象化だと実感できた