Python のネットワークプログラミング


こんにちは、切り口太郎です。

ネットワーク処理もファイルアクセスと同じで、どのプログラミング言語でも基本技術です。

Python には、ネットワーク系のライブラリーやフレームワークも多いでそれらを利用すれば、大概の事ができるので、自分であまりネットワークの処理を記述することはありません。

ただ、プログラミングでネットワーク系の技術は基本技術なので、基本的な事は理解して自分で作れるぐらいの知識と「腕」をを持っておくのができるプログラマーたるものです!

ここでは、ネットワークの基本 socket を使用してサーバーとクライアントでメッセージをやり取りする方法についてご紹介します。

1 soket 通信


クライアントとサーバーでメッセージのやり取りをするエコーをサンプルにします。
エコーは、クライアントが送信したメッセージそのままサーバーがクライアントへ返します。


① まず、サーバーを起動して、特定のポートで接続待ちに(待機状態にしておきます)
② 次に、クライアントを起動して、待機しているサーバーへソケットで接続します。
③ サーバーはクライアントからの接続を受けると、接続したクライアントとの専用ソケットを作成し、接続応答をします。
④   クライアント側でメッセージをサーバーへ出力します。
⑤ サーバーは、クライアントからメッセージを受信すると、サーバーのコンソールへ受信メッセージを表示します。
  次に受信したメッセージと同じものを、クライアントへ送信します。
⑥ サーバーからのメッセージを受信すると、クライアントは受信メッセージを表示します。
⑦ クライアントを終了します。
  サーバーは接続先のクライアントの終了を検知して、サーバープログラムを終了するようにします。


このようなソケット通信のサンプルを考えました!

クライアントとサーバー間の通信で簡単なルール(プロトコルもどき)作成しました。


送信データをヘッダー部とボディ部に分けます。

ヘッダー部には4バイトのボディ部の長を設定します。
ボディ部には、メッセージを設定します。
受信側は、先頭ヘッダー部を読んで、それからボディ部のデータを取得します。
その他、セキュリティのために、ヘッダー部にセキュリティコードなどを設定したりする方法もあります。


Python のソケット通信をする場合には、ソケットのアドレスファミリーを指定します。

アドレスファミリ
説明
AF_INET
IPv4 によるソケット
AF_INET6
IPv6 によるソケット
AF_UNIX
ローカルなプロセス間通信用のソケット

使用頻度が現在高いのは、AF_INET(IPv4) です。今後、AF_INEAT6(IPv6)に移行していくでしょう。
AF_UNIX は、UNIXで使われていた方法で、サーバーとクライアントのソケットペアを作成する方法です。今はあまり利用されていません。
サンプルは、IPv4 で作成しました。


ネットワークのサンプルコードとして3つのファイルを用意しました。

soket_server.py
サーバー用プロ9グラム
socket_client.py
クライアント用プログラム
socket_lib.py
サーバー、クライアントで共有するライブラリー
ヘッダー部とボディ部の処理をする、送信/受信関数をコーディングしています。

実際に動かしてみましょう。
サーバーとクライアント通信なので、今回は2つプログラムを実行させます。
上の表のプログラムを同じフォルダーに置いて下さい。


2 サーバーの実行


Anaconda Prompt を起動します。
Python ソースのあるフォルダーに移動して、 「python socke_server.py」と入力します。

(base) C:\>python socke_server.py
start server socket

「start server socket」と表示できたら起動は完了です。
サーバープログラムは、クライアントからの接続待機になります。

3 クライアントの実行


新しい Anaconda Prompt を起動します。

Python ソースのあるフォルダーに移動して、 「python socke_client.py」と入力します。

(base) C:\>python socket_client.py
server response : b'hello server 0'
server response : b'hello server 1'
server response : b'hello server 2'
server response : b'hello server 3'
server response : b'hello server 4'

クライアントは、即実行します。
クライアントの実行が完了すると、サーバープログラムも終了します。

FILE : socket_server.py
import socket
import socket_lib as slib


# AF = IPv4 という意味
# TCP/IP の場合は、SOCK_STREAM を使う
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    print("start server socket")
    # IPアドレスとポートを指定
    s.bind(('127.0.0.1', 50007))
    # 1 接続
    s.listen(1)
    # connection するまで待つ
    # 誰かがアクセスしてきたら、コネクションとアドレスを入れる
    conn, addr = s.accept()
    # クライアントが接続している間動作する。
    with conn:
        while True:
            # ------ 受信 -------
            data = slib.recv(conn)
            if not data:
                break

            # ----- 送信 (echo サーバーなのでそのまま返す) ------
            print('data : {}, addr: {}'.format(data, addr))
            # クライアントにデータを返す(b -> byte でないといけない)
            # データ長さ+データを送信する。
            slib.send(conn , data)
ソースの説明です。

7行目でサーバーのソケットを作成しています。
with は、このブロックを抜ける時にソケットをクローズしてくれます。

9行目で起動メッセージを表示しています。
10行目と12、15行目は、サーバー側のソケットの作成処理です。

サーバーソケットは、リッスン用ポート(クライアントが接続するポート)に対して①bind 、② listen 、③ accept の3つの処理で接続待機状態にします。
10行目の bind 処理で、待ち受けるサーバーのIPアドレス(またはホスト名)とポート番号を指定しています。
今回は、アプリケーションでの予約の無い 50007 番ポートを指定しています。
15行目は、クライアントが接続してくるまで待機する処理です。
クライアントが接続してくると、「接続先のクライアントとの専用 socket」と「接続先クライアントのアドレス情報」の2つが返ってきます。

18行目は、クライアントソケットとの送受信ループです。
ループアウトの条件は、22行目のクライアントソケットのデータが ない場合です。
この判定は、クライアントが終了した判定に利用しています。

20行目は、共通で利用する socket_lib.py の受信関数を呼び出しています。この関数はデータを受信するまで待機します。
28行目は、共通の送信関数を呼び出して、受信データをそのままクライアントへ返送します。

FILE : socket_client.py
import socket
import socket_lib as slib

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # サーバを指定
    s.connect(('127.0.0.1', 50007))

    # 送信回数
    max = 5

    # 送信カウンター
    counter = 0
    message = 'hello server '
    while counter < max:
        # 送信メッセージを作成する (message + 送信回数)
        mes = message + str(counter)

        # ---- 送信 ------
        # 必ずバイト列に変換する
        by = mes.encode(encoding='utf-8')
        slib.send(s , by)
        # ------- 受信 --------
        data = slib.recv(s)
        print("server response : " + repr(data))
        counter += 1
ソースの説明です。

4行目で、ソケットを作成しています。作成の方法はサーバーのソケットと同じです。
6行目で、サーバーに接続します。引数で、サーバーのIPアドレス(またはホスト名)とポート番号を指定します。

9行目、サーバーへ送信する回数用変数です。サンプルは5回送信します。

16行目で、サーバーへ送信するメッセージを作成しています。メッセージは、'hello server ' に 送信回数を設定して毎回違うメッセージを送信します。
21,23行目は、サーバーへの送受信です。こちらは共通関数を使用しています。


FILE : socket_lib.py
'''
 ソケット共通処理
'''

# 受信用関数
def recv(conn):
    # 始めに受信データ長を取得する 第二引数はブロッキングモード指定=0
    data_len = conn.recv(4 , 0)
    # データが取得できない場合は、クライアントが終了しているのでサーバー処理を中止する。
    if not data_len:
        print("client disconnected.")
        return None

    length = int.from_bytes(data_len , 'big')
    # データを読み取る
    data = conn.recv(length , 0)
    return data

# 送信用関数
def send(conn , data):
    # 先頭に送信データ長を設定するために、データ長をバイト列に変換する
    length = len(data).to_bytes(4 , 'big')
    # 送信データ長+データを送信する。
    conn.sendall(length + data)

ソースの説明です。

6行目が、socket を使った受信処理です。
パラメータの conn は、ソケットです。

8行目で、ヘッダー部分の4バイトを読み込み、データ送信長さを取得します。もし、ヘッダーが読み込め無いときには、None を返します。
16行目で、データ送信長さのデータを取得します。

20行目からは、送信関数です。
パラメーターは、ソケットと送信データを受け取ります。

22行目で、データ長をバイト列に変換してヘッダーを作成します。
24行目で、ヘッダー+データをソケットに送信します。

実行結果です。

クライアント
サーバー
server response : b'hello server 0'
server response : b'hello server 1'
server response : b'hello server 2'
server response : b'hello server 3'
server response : b'hello server 4'
start server socket
data : b'hello server 0', addr: ('127.0.0.1', 63832)
data : b'hello server 1', addr: ('127.0.0.1', 63832)
data : b'hello server 2', addr: ('127.0.0.1', 63832)
data : b'hello server 3', addr: ('127.0.0.1', 63832)
data : b'hello server 4', addr: ('127.0.0.1', 63832)
client disconnected.

クライアントとサーバーのメッセージを比較すると、クライアントから送信したメッセージがサーバー側で表示され、サーバーから同じメッセージが戻されてクライアント側で表示されているのが確認できます。

コメント

このブログの人気の投稿

Hyper-V で Docker Desktop for Windows を使う(その2)

Python の命名規約 - ネーミングルール

VS Code で Hyper-V + Docker Desktop for Windows