taxin's notes

読書、勉強メモ etc.

【勉強メモ】Network Programming with Python #1

この記事では、下記の内容についてまとめます。

  • ソケット通信自体の説明
  • ソケット通信のフロー
  • ソケット通信を行うサンプルプログラム(サーバー側)

ソケットとは?

TCP/IPを利用して通信を行う際に必要な送受信処理をプログラム側で利用するためのインターフェース的なもの。
ソケットを用いた通信の裏側ではTCP/IP (or UDP) がベースとなっているので、勉強の第一歩として最適かと思いました。

下記のサイトでは、ソケット通信に対して下記のような記載をしています。

capm-network.com

ソケットとは、アプリケーションがデータを送受信するための仕組みを抽象化したものです。 ソフトウェア開発者は、ソケットの仕組みに則ってプログラムを記述すれば、具体的な通信手段や手順の詳細を知らなくても、通信を行うことができます。

他のサイトなども見た上で、ソケット通信に対するポイントは下記の通り。

  • ソケット通信の裏側にはTCP/IP (UDP) などのプロトコルが存在する
  • 開発者はTCP / IPの仕様を意識しないで、通信の処理を実装できる

Pythonでも、ソケット通信を行うためのライブラリーが提供されているのでこちらを利用する。

docs.python.org

ソケット通信のフロー

ソケット通信のフローは下記の通り。

【クライアント】

  1. ソケットの作成 (socket())
  2. ソケットに接続先のIPアドレスとポート番号を渡して接続要求を行う (connect())
  3. ソケットにデータを送信する (send())
  4. コネクションを終了する (close())

【サーバー】

  1. ソケットの作成 (socket())
  2. ソケットに接続を待ち受けるIPアドレスとポート番号を渡す (bind())
  3. 接続の受け付けを開始する (listen())
  4. 新しい接続要求を受け付ける (accept())
  5. ソケットからデータを受信する (recv())
  6. コネクションを終了する (close())

サンプルコード

今回はサーバー側のプログラムのみ準備しました。(コードはリンク先のサンプルを参考に少しだけ修正)
IPアドレス127.0.0.1、ポート番号:7777で接続を待ち受けるプログラムを用意して、クライアント側はncコマンドを利用して先ほどのIPアドレスとポートを利用して接続要求するようにしました。

qiita.com

import socket

def create_server_socket(host, port):
    # アドレスファミリ(AF_INET:Ipv4)とソケットタイプ(SOCK_STREAM:TCP)を指定
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # ソケットオプションでソケットの再利用フラグをONに設定
    # クライアントと通信途中で中断した場合同じIPアドレスとポートでバインドできるようにする
    server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    return server_sock


def accept_loop(server_sock):
    while True:
        # 4.新しい接続要求を受け付ける
        # server_sock.accept()はブロッキング処理なので、接続が来るまでは次の処理に進まない
        print("start accepting...")
        conn, (client_host, client_port) = server_sock.accept()

        # ソケットのファイル記述子、新規に作成されたソケットにBindされたIPアドレス・ポートを表示する
        print('[FD:{}] Accept: {}: {}'.format(conn.fileno(), client_host, client_port))

        # ソケットからデータを受信し、結果をbytesオブジェクトで返す(一度に512bytes受信する)
        data = conn.recv(512)
        print('[FD:{}] Recv: {}'.format(conn.fileno(), data))

        # 送信されてきたデータを返す
        conn.send(data)

        # 5.接続を切断する
        conn.close()

if __name__ == '__main__':
    host, port = "localhost", 7777

    # 1.ソケットの作成
    server_sock = create_server_socket(host, port)

    # 2.接続を待ち受けるIPアドレスとポートを指定する
    server_sock.bind((host, port))

    # 3.接続要求の受け付けを開始する
    server_sock.listen(5)
    print('Server Run Port: {}'.format(port))

    try:
        accept_loop(server_sock)
    except KeyboardInterrupt:
        server_sock.close()

検証

最初にサーバー側のプログラムを起動します。

❯ python simple_socket.py 
Server Run Port: 7777

netstatコマンドで確認するとポート:7777でLISTENしていることがわかります。 この段階でサーバー側では、ソケットの作成〜接続要求の受付開始まで完了している状態です。

❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

次にncコマンドを実行して、先ほどのIPアドレス・ポートに接続要求を行います。

❯ nc localhost 7777

プログラムの標準出力・netstatコマンドの結果を見ると、接続要求を受け付けてTCPコネクションを確立した上でaccept()によって新たなソケットの作成(接続要求の受付まで対応可能)が行われていることがわかります。

❯ python simple_socket.py
Server Run Port: 7777
start accepting...
[FD:6] Accept: 127.0.0.1: 63897
❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         127.0.0.1.63897        ESTABLISHED
tcp4       0      0  127.0.0.1.63897        127.0.0.1.7777         ESTABLISHED
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

最後にncコマンド側で任意のメッセージを送信します。
手順としては、コンソール上でncコマンドの実行後に任意の文字列を入力してエンターを押すだけです。

❯ nc localhost 7777
aaa
aaa

プログラム側の標準出力では、クライアント側から送信したメッセージaaaが受信できていることがわかります。

❯ python simple_socket.py
Server Run Port: 7777
start accepting...
[FD:6] Accept: 127.0.0.1: 63897
[FD:6] Recv: b'aaa\n'

netstatコマンドで確認すると、先ほどのコネクションが存在しないことが確認できます。 これはプログラム上でメッセージのやり取りをした後で、コネクションを解放する処理 (close()) を実行したためです。

❯ netstat -an | grep 7777
tcp4       0      0  127.0.0.1.7777         *.*                    LISTEN

3-way handshakeなど細かい部分はメソッドによって隠蔽されていますが、大まかな流れは掴めた気がします。 ncコマンドを利用すれば細かい状態遷移も再現できるみたいなので、別途検証してみようと思います。

わからない点

まだ、調べきれていない点を下記に列挙していきます。 (次回の記事で調べた内容もpostするかもしれません。)

  • close()の実行時には、どのようなフローを経てコネクションを解放しているのか? (アクティブクローズを行なっている?)
  • setsockopt()によってクライアントと通信途中で中断した場合に同じIPアドレスとポートでバインドできるが、これは「同じIPアドレスとポートを利用したソケットを作成できる」 or 「作成したソケットを再利用できる」のどちらが正しい?

qiita.com