14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Python] SocketにおけるBlocking、Non-blocking、Timeoutの違い

Last updated at Posted at 2019-01-07

Pythonのsocket.send()socket.recv()メソッドによってデータを送受信する際に、リソースのブロッキングを管理することができます。管理の種類として主にblocking、non-blocking、timemoutがあります。その他にもselectselectorsモジュールで多重化I/Oを組み合わせて管理する方法もあります。前に書いた記事を参考にして下さい ➔ [Python] selectとselectorsモジュールによる多重化I/O - Qiita
今回はblocking、non-blocking、timemoutの3つのモードについて掘り下げたいと思います。

OSはLinuxを対象とし、Pythonのバージョンは3.6を対象とします。細かいところはバージョンによって違いますが、3系なら基本的な部分は変わらないでしょう。
しかし、OSによってソケットのプロセスフローが変わるのでご注意を!

送受信の流れ

かなり省略して説明します。
Chapter 6. I/O Multiplexing: The select and poll Functions - Shichao's Notesで図などを用いて説明されているので、詳細を知りたい方は参照してくだい。

送信

  1. socket.send()を実行することでシステムコールを発行
  2. リソースをブロック
    この時は他のファイルI/Oの操作が不能となります
  3. kernel-bufferが書き込み待機
  4. ユーザによってデータ入力
  5. kernel-bufferにコピー
  6. 送信

受信

  1. socket.recv()を実行することでシステムコールを発行
  2. リソースをブロック
    この時は他のファイルI/Oの操作が不能となります
  3. kernel-bufferが読み込み待機
  4. 受信
  5. 受信したデータをkernel-buffer(カーネル空間)に格納
  6. ユーザ空間(アプリケーション)にデータコピー

各モードの説明

話をシンプルにするために受信における動作の違いにフォーカスします。

Blocking

ソケットオブジェクトのデフォルトモードはBlockingです。
Blockingモードはkernel-bufferに読み込み可能なデータが到着するまでリソースをブロックします。メインプロセス自体が強制終了、または例外が発生するまでブロックし続けます。

Blocking関連のメソッドを以下に挙げました。

メソッド 説明
socket.setblocking(flag) flagがTrueならBlocking、FalseならNon-blockingモードに設定

Non-blocking

Non-blockingモードは読み込み可能なデータがなければ即座にEWOULDBLOCKというエラーを吐いてリソースをリリースします。読み込み可能なデータがあれば、kernel-bufferからユーザ空間にデータがコピーされます。
このような特徴からNon-blockingモードはI/O Multiplexingと併用されます。

Non-blockingモードを設定する方法は、Blockingモードで紹介したsocket.setblocking(False)を使用するか、ソケットオブジェクト作成時にtype引数に値を渡す方法です。

  • socket.setblocking(False)

>>> import socket
>>> socket.SOCK_STREAM
<SocketKind.SOCK_STREAM: 1>
>>> socket.SOCK_NONBLOCK
<SocketKind.SOCK_NONBLOCK: 2048>
>>> sock = socket.socket()
>>> sock
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
>>> sock.setblocking(False)
>>> sock
<socket.socket fd=3, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('0.0.0.0', 0)>

socket.setblockingメソッドでNon-blockingモードに設定すると、ソケットオブジェクトのtypeが2049となりました。

  • type引数

>>> import socket
>>> socket.socket(type=(socket.SOCK_STREAM | socket.SOCK_NONBLOCK))
<socket.socket fd=3, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('0.0.0.0', 0)>

ビット演算socket.SOCK_STREAM | socket.SOCK_NONBLOCKによってNon-blockingを設定します。作成されたソケットオブジェクトのtypeが2049となり、socket.setblockingメソッドと同様の結果になりました。

Timeout

blockingモードと併用して使用します。
指定した時間が経過する(プロセスがタイムアウトする)まで、リソースをブロックして読み込み可能なデータが到着するまで待機します。
タイムアウトしたら、socket.timeoutという例外が発生します。この例外はversion 3.3からOSErrorのサブクラスとなりました。

socket_timeout = PyErr_NewException("socket.timeout", PyExc_OSError, NULL);1

Timeout関連のメソッドを以下に挙げてみました。

メソッド 説明
socket.getdefaulttimeout() 新規に作成されたsocketオブジェクトのtimeout値を取得
socket.setdefaulttimeout(timeout) 新規に作成されるsocketオブジェクトのtimeout値を設定
socket.gettimeout() 特定のsocketオブジェクトのtimeout値を取得
socket.settimeout(value) 特定のsocketオブジェクトのtimeout値を設定

socket.setdefaulttimeoutsocket.settimeoutで渡す引数はfloat型で単位は秒となります。
Noneを引数として渡すとtimeoutは無効になります。つまり、通常のblockingモードとなるわけです。
0を指定すればnon-blockingモードとなります。

defaultが含まれている方は全てのsocketオブジェクトに共通したメソッドです。
逆に含まれていない方は作成されたsocketオブジェクト毎に個別に実行できるメソッドです。サーバーサイドではクライアントごとにソケットが生成されるので、これらのメソッドを使用することでクライアントごとの制御が可能となります。

参考

  1. cpython/socketmodule.c at 3.6 · python/cpython
    (https://github.com/python/cpython/blob/3.6/Modules/socketmodule.c#L6607)

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?