【Python3】Pythonでソケット通信を試してみた
作るもの
ネットワークの知見を深めるべく Python でソケット通信を試してみました。
server.py
とclient.py
の二つの Python ファイルを作成し、
client.py
からリクエストを送信し、server.py
から”Welcome to the server!”とメッセージを返却します。今回は TCP/IP を使用します。
ソケット通信/ソケットとは
ソケット インターネットは TCP/IP と呼ぶ通信プロトコルを利用しますが、その TCP/IP をプログラムから利用するには、プログラムの世界と TCP/IP の世界を結ぶ特別な出入り口が必要となります。 その出入り口となるのがソケット(Socket)であり、TCP/IP のプログラミング上の大きな特徴となっています。 引用元: http://research.nii.ac.jp/~ichiro/syspro98/socket.html
環境
- python 3.7
server.py
まずはサーバー側を実装します。
server.py
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 1235)) # IPとポート番号を指定します s.listen(5) while True: clientsocket, address = s.accept() print(f"Connection from {address} has been established!") clientsocket.send(bytes("Welcome to the server!", 'utf-8')) clientsocket.close()
Socket を作成
socket は Python3 スタンダードライブラリに含まれるので import のみで利用できます。
AF_INET
で IPv4 を指定します。(IPv6 の場合は AF_INET6)今回は TCP プロトコルを利用するのでSOCK_STREAM
を指定しています。
bind
ホスト名とポート番号を指定します。
listen
引数にキューの数を指定します。 ここで指定した数だけ並列的にリクエストを処理することができます。
accept
接続の受信を行います。
close
接続を切断します。
client.py
次に クライアントを実装します。
client.py
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 1235)) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) msg = s.recv(1024) print(msg.decode("utf-8"))
recv()
一度に受け取るデータのサイズを指定します。
ハードウェアおよびネットワークの現実に最大限マッチするように、 bufsize の値は比較的小さい 2 の累乗、たとえば 4096、にすべきです。 引用元:https://docs.python.org/ja/3/library/socket.html
msg.decode("utf-8")
データは byte 形式にエンコードされて送信されるのでデコードしてあげます。
実行してみる
server.py を実行してサーバーを起動させます。次に client.py を実行した時に”Welcome to the server!”の文字が返却されたら成功です。サーバー側にはポート番号が表示されます。
ここまででかなり簡易的でありますがコネクションが確立できました。理解を深めるためにもう少しソケットに触れてみます。
データを全て受信するまで接続したままにする
今のままの実装では指定したサイズ(1028)より大きなデータが送られてきた場合はそのまま接続を切断してしまいます。試しに
msg = s.recv(1024)
に渡す引数を小さくmsg = s.recv(8)
に設定して試してみます。
"Welcome"までしかデータを受信していません。 そこで、送られてきたデータ全てを受信するまで接続したままにできるようにコードを変更します。
追記:今回のようにバッファサイズを小さく設定した場合、日本語を含む文字列を処理する際データが欠落する可能性があるため、バイト配列にして格納することでエラーを回避しています。(Thanks to yoshihitohさん)
client.py
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 1235)) full_msg = b'' while True: msg = s.recv(8) if len(msg) <= 0: break full_msg += msg print(full_msg.decode("utf-8"))
Python Object を送信する
今までの例では文字列のみのやりとりでしたが、配列や辞書等の Python オブジェクトもソケットを介して送受信することができます。
server.py
import socket import pickle data = {1: "Apple", 2: "Orange"} s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 1235)) # IPとポート番号を指定します s.listen(5) while True: clientsocket, address = s.accept() print(f"Connection from {address} has been established!") msg = pickle.dumps(data) # Output: b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00}\x94(K\x01\x8c\x05Apple\x94K\x02\x8c\x06Orange\x94u.' print(msg) clientsocket.send(msg) clientsocket.close()
client.py
import socket import pickle s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 1235)) full_msg = b'' while True: msg = s.recv(1024) if len(msg) <= 0: break full_msg += msg d = pickle.loads(full_msg) print(d)
pickle
pickle モジュールでデータをシリアライズ(byte 形式に変換)して送受信を行います。サーバー側でpickle.dumps()
でシリアライズしたデータをクライアント側でpickle.loads()
で復元します。pickle を使ってこのような処理をすることを Pickling、Unpickling と言ったりもするようです。
シリアライズすればどんな形式のデータもソケット通信でやりとりできることがわかりました。