3
3

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 1 year has passed since last update.

socket.socket()の仕様を理解する

Last updated at Posted at 2022-09-24

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()は、familytypeがあれば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)>
3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?