ネットワークを学ぶ 第一回

コンソールで動く Python でのアプリケーションがひと段落したため、次はネットワークを勉強しようと思います。
第一回目はまず触ってみて、どんなものかを試してみようと思います

## What did?

- Python の `ソケットプログラミングHowTo`を読む
  - https://docs.python.org/ja/3/howto/sockets.html
- チュートリアル的なプログラミングをしてソケット通信を触ってみる
- 基本的な単語や概念を理解する

## `ソケットプログラミングHowTo`を読む

**歴史**  
ソケットは BSD Unix の一部としてバークレイで発明され、インターネットの普及と共に野火のごとく広まった。それももっともなことで、ソケットと INET のコンビによって世界中どんなマシンとも、信じられないほど簡単 (少なくとも他のスキームと比べて) に通信できるようになったのだ。

- ソケットって何？
  - INET?
    - インターネットソケット
    - `AF_INET` はアドレスファミリーの一つである
      - アドレスファミリーとは作成したソケットにバインドするアドレスの種類を示す
      - `AF_INET` は IPv4 インターネットプロトコル。TCP ソケット通信ということを示す
  - STREAM?
    - ストリームソケット（通常は TCP を使用）
    - データは連続したバイトストリームとして送受信されます。これは信頼性が高く、順序が保証されています。
  - ブロッキングとノンブロッキング
    - ブロッキング
      - ソケットがデータの送受信を行っている間、プログラムの実行が停止（ブロック）します。
    - ノンブロッキング
      - ソケットがデータの送受信を試みますが、すぐには完了しない場合でもプログラムの実行が続行されます。
- サーバーソケットって何？
  - クライアントソケット
    - クライアントプログラムがサーバーと通信するために使用するソケットです。
  - サーバーソケット
    - サーバープログラムが新しいクライアント接続を待ち受けるために使用するソケットです。
-
- IPC って何？
  - Inter Process Communication (プロセス間通信) の略
  - 異なるプロセス間でデータを送受信するための一連の手法です。ソケットは IPC の一形態でもあります

### 概要

HTTP のようなプロトコルでは、ひとつのソケットを 1 回の転送にしか使わない。クライアントは要求を送り、返答を受ける。これでソケットは破棄される。だからこの場合、クライアントは受信 0 バイトの時点で返答の末尾を検出することができる。

だが、以降の転送にもそのソケットを使い回すつもりなら、ソケットに EOT など 存在しない ことを認識する必要がある。ソケットの send や recv が 0 バイト処理で返ってきたなら、その接続は終わっている。終わって いない なら、いつまで recv を待てばいいかは分からない。ソケットは「もう読むものが (今のところ) ないぜ」などと 言わない のだから。
メッセージは必ず

- 固定長か
- 区切り文字を使うか
- 長さ標識を付けておくか
- 接続を閉じて終わらせるか

のいずれかでなければいけない

### 接続

```python
import socket

# create an INET, STREAMing socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# now connect to the web server on port 80 - the normal http port
s.connect(("www.python.org", 80))
```

### 切断

shutdown は相手ソケットに対する報告であり、渡す引数によって「これ以上こっちからは送らないけど、まだ聞いてるぜ」という意味になったり、「もう聞かない。せいせいした!」だったりする。しかしほとんどのソケットライブラリは、このエチケットを怠るプログラマに慣れてしまって、通常 close だけで shutdown(); close() と同じことになる。だから大抵はわざわざ shutdown しなくてもいい。

shutdown の効果的な使い方のひとつは、HTTP 風のやりとりだ。クライアントは要求を出してすぐに shutdown(1) する。これでサーバに、「クライアントは送信完了ですが、まだ受信可能です」と伝わる。サーバは 0 バイト受信で "EOF" を検出することができる。要求を残さず受け取ったことにして良いのだ。対してサーバは返答を送る。その send が成功したなら、クライアントは実際にまだ受信していたことになる。

Python はこの自動 shutdown をもう一歩進めて、ソケットが GC されるときに必要なら自動で close してくれると言っている。しかしこれに頼るクセをつけてはいけない。もしソケットが close せずに姿を消せば、相手ソケットはこちらが遅いだけだと思ってハングしてしまうかもしれない。

## Python でソケットプログラミングをしてみる

要件

1. サーバーとクライアントを実装
2. クライアントからサーバーに接続し、クライアントから文字列を送るとサーバーがそのまま文字列を返す

```python client.py
import socket
import os


class Client:
    def __init__(self):
        self.server_ip = "127.0.0.1"
        self.server_port = 5008
        self.create_socket(self.server_ip, self.server_port)

    def create_socket(self, ip, port):
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((ip, port))
        print("Connected to server at port {}".format(port))
        while True:
            message = self.input_message()
            client_socket.send(message.encode())

            received_data = client_socket.recv(1024).decode()
            print("Received message: {}".format(received_data))

            if message == "quit":
                client_socket.close()
                break

    def input_message(self):
        message = input("Enter your message: ")
        return message


client = Client()
```

``` python server.py
import socket
# https://docs.python.org/3/library/socket.html
# socket — Low-level networking interface
# This module provides access to the BSD socket interface.

BACKLOG = 5
BUFFER_SIZE = 1024


class Server:
    def __init__(self):
        self.ip = "127.0.0.1"
        self.port = 5008
        self.create_socket(self.ip, self.port)

    def create_socket(self, ip, port):
        # create an INET, STREAMing socket
        self.server_socket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM)
        # bind the socket to a public host, and a well-known port
        self.server_socket.bind((ip, port))
        # become a server socket
        self.server_socket.listen(BACKLOG)
        print("Server is listening on port {}".format(port))

    def accept_connections(self):
        while True:
            print("Waiting for connection...")
            # accept connections from outside
            (client_socket, address) = self.server_socket.accept()
            print("Connection from {}".format(address))

            while True:
                # receive message from client
                data = client_socket.recv(BUFFER_SIZE).decode()
                if data == "quit":
                    client_socket.close()
                    print("Connection closed")
                    break
                print("Received message: {}".format(data))
                client_socket.send(data.encode())


server = Server()
server.accept_connections()

```


## 基本的な単語や概念

- localhost
- loopback
- 127.0.0.1
- Subnet mask
- TCP/ IP/ UDP
- CIDR, IPv4, IPv6, ethernet
- DNS
- Http
- Ftp
- Https
- Default gateway