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


コメント
コメントを投稿