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