socketモジュール
このモジュールは、BSDソケットインターフェイスへのアクセスを提供します。
Python インターフェイスは、ソケット用の Unix システム コールとライブラリ インターフェイスを Python のオブジェクト指向スタイルに直訳したものです
Pythonのリファレンスを読んでいて不明な点がある場合は、システムコールのリファレンスを参考にすると良さそうです。
実装例
以下のWebページにサーバ・クライアントの実装例が載っており、とても参考になったので引用させていただきました。
サーバ
# echo-server.py
import socket
HOST = "127.0.0.1" # Standard loopback interface address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
クライアント
# echo-client.py
import socket
HOST = "127.0.0.1" # The server's hostname or IP address
PORT = 65432 # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b"Hello, world")
data = s.recv(1024)
print(f"Received {data!r}")
詳説
基本的に引用元の解説がわかりやすいのですが、socketインスタンスの生成部分に関する記述が少なかったので、少しまとめてみます。
class socket.socket
Create a new socket using the given address family, socket type and protocol number.
パラメータ | デフォルト値 |
---|---|
family | AF_INET |
type | SOCK_STREAM |
proto | 0 |
fileno | None |
重要そうなので、1つずつ確認していきます。
family
アドレスファミリー(プロトコルファミリー)を指定します。アドレスファミリーはIPv4, IPv6などが代表的です。デフォルトで設定されるAF_INET
はIPv4を指しています。
Linuxシステムコールではprotocol family
と呼称しているようです。
man socket
DESCRIPTION
socket() creates an endpoint for communication and returns a descriptor.
The domain parameter specifies a communications domain within which
communication will take place; this selects the protocol family which
should be used. These families are defined in the include file
⟨sys/socket.h⟩. The currently understood formats are
PF_LOCAL Host-internal protocols, formerly called PF_UNIX,
PF_UNIX Host-internal protocols, deprecated, use PF_LOCAL,
PF_INET Internet version 4 protocols,
PF_ROUTE Internal Routing protocol,
PF_KEY Internal key-management function,
PF_INET6 Internet version 6 protocols,
PF_SYSTEM System domain,
PF_NDRV Raw access to network device,
PF_VSOCK VM Sockets protocols
type
リファレンスに以下のような記述がありますが、それ以上の記述が見当たりません。
The socket type should be SOCK_STREAM (the default), SOCK_DGRAM, SOCK_RAW or perhaps one of the other SOCK_ constants.
Linuxシステムコールのsocketのリファレンスを参照します。
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
A SOCK_STREAM type provides sequenced, reliable, two-way connection based
byte streams. An out-of-band data transmission mechanism may be supported.
A SOCK_DGRAM socket supports datagrams (connectionless, unreliable messages
of a fixed (typically small) maximum length). SOCK_RAW sockets provide
access to internal network protocols and interfaces. The type SOCK_RAW,
which is available only to the super-user.
デフォルト値のSOCK_STREAM
はTCPなどで利用されるようです。
proto
IANAがメンテナンス・公開しているprotocol number
を指定する。
https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
socketモジュールでは、socket.IPPROTO_TCP
のようにして値を取得することもできる。
>>> socket.IPPROTO_TCP
6
>>> socket.IPPROTO_UDP
17
>>> socket.IPPROTO_IPV6
41
IANAのページにもある通り、8bitで表される。
>>> socket.IPPROTO_MAX
256
typeとprotoで不整合な値を指定する
想定通りエラーになっていることが確認できます。
# デフォルトではtype=socket.SOCK_STREAM
>>> socket.socket(proto=socket.IPPROTO_TCP)
<socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 0)>
>>> socket.socket(proto=socket.IPPROTO_UDP)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/socket.py", line 144, in __init__
_socket.socket.__init__(self, family, type, proto, fileno)
OSError: [Errno 41] Protocol wrong type for socket
>>> socket.socket(type=socket.SOCK_DGRAM, proto=socket.IPPROTO_TCP)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/socket.py", line 144, in __init__
_socket.socket.__init__(self, family, type, proto, fileno)
OSError: [Errno 41] Protocol wrong type for socket
>>> socket.socket(type=socket.SOCK_DGRAM, proto=socket.IPPROTO_UDP)
<socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=17, laddr=('0.0.0.0', 0)>
デフォルト値(0)の挙動
The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN the protocol should be one of CAN_RAW, CAN_BCM, CAN_ISOTP or CAN_J1939.
通常は0
とありますが、なぜprotocol numbers
を指定しなくても良いのでしょうか。socketモジュールのドキュメントからはこれ以上読み解けませんでした。
Linuxシステムコールのsocketのリファレンスを参照します。
SYNOPSIS
#include <sys/socket.h>
int
socket(int domain, int type, int protocol);
The protocol specifies a particular protocol to be used with the socket.
Normally only a single protocol exists to support a particular socket type
within a given protocol family.
通常は特定のプロトコルファミリー内の特定のソケットタイプに対してプロトコルは1つ、とあります。つまりsocket.socket()
は、family
とtype
があればproto
の情報は不要ということなのでしょう。
proto
が自動で設定されていることを確認してみたかったのですが、残念ながら以下のような方法で確認することはできませんでした。
>>> s = socket.socket()
>>> s
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
fileno
If fileno is specified, the values for family, type, and proto are auto-detected from the specified file descriptor.
ファイルディスクリプタを指定すると、その他の引数の値が自動で設定されるようです。fileno
についてはLinuxシステムコールのsocket
には引数として存在しませんでした。
filenoとして、生成済みのsocket(s1)のファイルディスクリプタを指定するかどうか(s2 or s3)で挙動が変わることは確認できました。
>>> import socket
>>> s1 = socket.socket()
>>> s1
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
>>> s2 = socket.socket(fileno=3)
>>> s2
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
>>> s3 = socket.socket(fileno=4)
>>> s3
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
>>> s4
<socket.socket fd=4, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=0, laddr=('::', 0, 0, 0)>
>>> s5 = socket.socket(fileno=4)
>>> s5
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('::ffff:ffff:ffff:ffff', 0, 0, 110741408)>