【Python3】Pythonでソケット通信を試してみた

Socketとはなんぞや
2020.04.13

作るもの

ネットワークの知見を深めるべく Python でソケット通信を試してみました。

server.pyclient.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!”の文字が返却されたら成功です。サーバー側にはポート番号が表示されます。

Output screenshot

ここまででかなり簡易的でありますがコネクションが確立できました。理解を深めるためにもう少しソケットに触れてみます。

データを全て受信するまで接続したままにする

今のままの実装では指定したサイズ(1028)より大きなデータが送られてきた場合はそのまま接続を切断してしまいます。試しに msg = s.recv(1024)に渡す引数を小さくmsg = s.recv(8)に設定して試してみます。

Output

"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"))

Output

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)

Output

pickle

pickle モジュールでデータをシリアライズ(byte 形式に変換)して送受信を行います。サーバー側でpickle.dumps()でシリアライズしたデータをクライアント側でpickle.loads()で復元します。pickle を使ってこのような処理をすることを Pickling、Unpickling と言ったりもするようです。

シリアライズすればどんな形式のデータもソケット通信でやりとりできることがわかりました。

参考