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

# socketモジュール

https://docs.python.org/3/library/socket.html#module-socket

*このモジュールは、BSDソケットインターフェイスへのアクセスを提供します。*

*Python インターフェイスは、ソケット用の Unix システム コールとライブラリ インターフェイスを Python のオブジェクト指向スタイルに直訳したものです*

Pythonのリファレンスを読んでいて不明な点がある場合は、システムコールのリファレンスを参考にすると良さそうです。

# 実装例

以下のWebページにサーバ・クライアントの実装例が載っており、とても参考になったので引用させていただきました。

https://realpython.com/python-sockets/

## サーバ

```python
# 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)
```

## クライアント

```python
# 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を指しています。

https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family

Linuxシステムコールでは`protocol family`と呼称しているようです。

```sh
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などで利用されるようです。

https://docs.oracle.com/cd/E19253-01/819-0392/sockets-4/index.html

### proto
IANAがメンテナンス・公開している`protocol number` を指定する。
https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml

socketモジュールでは、`socket.IPPROTO_TCP`のようにして値を取得することもできる。

```python
>>> socket.IPPROTO_TCP
6
>>> socket.IPPROTO_UDP
17
>>> socket.IPPROTO_IPV6
41
```

IANAのページにもある通り、8bitで表される。
```python
>>> socket.IPPROTO_MAX
256
```

#### typeとprotoで不整合な値を指定する

想定通りエラーになっていることが確認できます。

```python
# デフォルトでは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
```

```python
>>> 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`が自動で設定されていることを確認してみたかったのですが、残念ながら以下のような方法で確認することはできませんでした。

```python
>>> 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)で挙動が変わることは確認できました。
```python
>>> 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>
```

```python
>>> 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)>
```