このページでは、Python でのソケット通信を利用したファイルの送信し方法について説明していきます。
これを使いこなせば、複数の PC 間でテキストファイルや画像ファイル等のやり取りができるようになります。
ソケット通信自体については下記ページで解説していますので、まずソケット通信について学びたいという方は先に下記ページを読んでみていただければと思います。
Pythonでのソケット通信(ポート番号・プロトコル・サーバー / クライアント)Contents
ファイルの送信方法
では、ソケット通信でのファイルの送信方法について説明していきます。まず、この章でファイルを送信するための実現方法や考え方、注意点について解説し、次の ファイルの送受信プログラム で実際にファイルの送受信を行うプログラムのサンプルを紹介します。すぐにプログラムの作り方について学びたいという方は、ファイルの送受信プログラム までスキップしていただければと思います。
結局データを送信すればよいだけ
ファイルの送信自体は非常に簡単で、ファイルの送信側でファイルを読み込み、その読み込んだデータを送信してやれば良いだけです。これにより、「画像ファイル」「テキストファイル」「動画ファイル」「ZIP ファイル」等のどんな種類のファイルでも送信することが可能となります。
つまり、ファイルの送信も結局は単なるデータの送信です。その送信するデータが “ファイルから読み込んだデータ” となる点が異なるだけです。
また、受信したファイルをファイルとして保存したいのであれば、ファイルの受信側で相手から受信したデータをファイルに書き込む処理を実行するだけで実現できます。
ただし、ファイルの受信側で実行すべき処理は開発する対象となるアプリやプログラムによって異なります。このページでは、ファイルの受信側は受信したデータをファイルとして保存することを前提に解説していきますが、例えば受信したファイルを加工して通信相手に返却するようなことも出来ますし、画像ファイルを受信するのであればファイルに保存することなくプログラムで画像を表示するようなことも可能です。後者の場合は Pillow などを利用すれば簡単に実現可能です。
まずは、受信したデータをファイルに保存する処理の流れを理解していただき、その後、開発する対象となるアプリやプログラムに応じて受信側の処理を拡張していただければと思います。
スポンサーリンク
ファイルはバイナリモードで開く
また、送受信前後に実施する “ファイルからの読み込み” や “ファイルへの書き込み” に関しては、基本的にはいつも通りに行えば良いです。
ファイルの読み込み・書き込みは通常通り行えばよい
具体的には、ファイルからの読み込みに関しては、Python の組み込み関数である open
で送信したいファイルを開き、open
関数から返却されるファイルオブジェクトに対して read
を実行させることで実現できます。そして、この read
で読み込んだデータをソケット通信で送信することでファイルの送信が実現できます。
また、ファイルへの書き込みに関しては、受信したデータの保存先となるファイルを open
で開き、open
関数から返却されるファイルオブジェクトに対して write
を実行させることで実現できます。
おそらく、ソケット通信を利用したことのある方や、これからソケット通信を利用しようとしている方であれば、上記のようなファイルの読み込みやファイルの書き込みを既に経験済みの方は多いのではないかと思います。
open
時のモードにはバイナリモードを選択する
こんな感じで、基本的には通常通りにファイル操作を行えば良いのですが、一つ注意点があって、それは、特に送信するファイルはバイナリモードで開く必要があるという点になります。これはファイルの種類に関わらず共通の注意点となり、例えファイルがテキストファイルであってもバイナリモードで開く必要があります。
open
関数では第2引数に “モード” を指定することができ、これにより第1引数に指定したファイルパスのファイルを開くときのモードが変化します。例えば第2引数に 'r'
を指定すればファイルがテキストモードで読み込まれることになり、読み込まれたデータは文字列になります。なんですが、ソケット通信の send
メソッド等では文字列は送信不可です。なので、この場合、そのままデータを送信しようとすると例外が発生します。
それに対し、第2引数に 'rb'
を指定すればファイルがバイナリモードで開かれることになり、読み込まれるデータはバイト型のデータになります。そして、ソケット通信の send
メソッドではバイト型のデータは送信可能ですので、バイナリモードで読み込んだデータをそのままソケット通信で送信することが可能になります。つまり、送信するファイルは open
関数の第2引数に 'rb'
を指定して開く必要があります。
まぁ、テキストモードで読み込んだとしても、送信する前にデータをバイト型のデータに変換してやれば良いだけなのですが、バイナリモードで読み込んだ方が処理はシンプルになると思います。
また、open
関数では第2引数を省略した場合は 'r'
を指定したときと同じ振る舞いとなり、テキストモードでファイルの読み込みが行われることになるので注意してください。ファイルから読み込んだデータをそのままソケット通信で送信する場合は open
関数の第2引数に 'rb'
を明示的に指定するようにしましょう。
確実にデータ全体を送受信する
また、ファイルを送信するときには送受信するデータのサイズが大きくなりがちなので注意が必要です。
例えば画像の場合は 1MB を超えるサイズのファイルも普通にありますし、動画などはもっと大きなサイズのファイルになる場合もあります。こういった大きなサイズのファイルのデータを送信することになりますので、大きなデータを確実に送受信できるようにすることが重要となります。
送信時には sendall
を利用する
具体的には、TCP 通信の場合、送信時には sendall
メソッドを利用する必要があります。sendall
メソッドを利用することで、確実に全てのデータを相手に送信することができます。
受信時には recv
を繰り返し実行する
また、受信時には相手が送信してきたデータの全てを確実に受信できるように recv
メソッドを繰り返し実行する必要があります。下記ページで詳細を解説していますが、recv
メソッドは引数にバッファーサイズを指定して実行することになりますが、このバッファーサイズに十分大きな値を指定したとしても受信したデータが途切れてしまう可能性があります。そのため、下記ページで解説しているように、データの全体が受信できるまで recv
メソッドを繰り返し実行する必要があります。
send
メソッドを一回のみ実行するだけだとデータの一部しか送信できない、recv
メソッドを一回のみ実行するだけだとデータの一部しか受信できない、といった現象は、ソケット通信を行っている限りどんなデータの送受信時にも起こりうります。ですが、特に送受信するデータのサイズが大きい場合に起きやすく、ファイルを送信する場合はデータのサイズが大きくなりがちなので注意が必要となります。
ファイルの送受信プログラム
では、ファイルの送信方法 の解説内容を踏まえて作成したファイルの送受信プログラムのサンプルスクリプトを紹介していきます。
今回は、ファイルの送信プログラムとファイルの受信プログラムを紹介していきます。ファイルの送信プログラムとしては、コマンドライン引数で指定されたファイルパスのファイルを受信プログラムに対して送信を行うサンプルを、ファイルの受信プログラムとしては、受信したデータをコマンドライン引数で指定されたファイルパスに保存するサンプルを紹介します。
これらのプログラムは同じ PC 上で動作することを前提としていますが、ファイルの受信プログラムのコードを変更すれば、異なる2つの PC 上でファイルの送受信を行うことができるようになります。
スポンサーリンク
ファイルの送信プログラム
まず、ファイルの送信プログラムのサンプルスクリプトを紹介します。そのスクリプトが下記となります。
import socket
import sys
# 送信するファイルのファイルパスをコマンドライン引数から取得
file_path = sys.argv[1]
# ファイルのデータを読み込み
with open(file_path, 'rb') as f:
byte_data = f.read()
# "送信するデータのサイズ"を示すデータのバイト長
SIZE_DATA_LEN = 8
# ソケットを生成
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 接続要求の送信
address = ('127.0.0.1', 40001)
sock.connect(address)
# 送信するデータの先頭にデータのサイズを示すバイトデータを結合
size_data_len = len(byte_data)
size_data = size_data_len.to_bytes(SIZE_DATA_LEN, 'little')
send_data = size_data + byte_data
# データ全体を送信
sock.sendall(send_data)
# ソケットをクローズ
sock.close()
上記は、コマンドライン引数で指定されたファイルパスのファイルを読み込み、それを送信するスクリプトになっています。クライアントサーバーモデルのクライアントの位置づけで作成しています。
ファイルを開く際にはバイナリモードで開く必要があるため、open
関数の第2引数には 'rb'
を指定するようにしています。また、データ全体を確実に送信するため、送信時には sendall
メソッドを利用するようにしています。さらに、送信先に送信するデータのサイズを伝えるため、ファイルを読み込んで取得したデータの先頭に “送信するデータのサイズ” を示すバイト型のデータを結合した上で、結合後のデータを送信するようにしています。
これによって、受信側は送信されてきたデータのサイズを知ることができ、そのサイズ分のデータを受信するまで recv
メソッドを繰り返し実行することで、確実にデータの全体を受信することができるようになります。ファイルの送信方法 で解説したように、このあたりがファイルをソケット通信で送信する際のポイントになると思います。
また、上記のスクリプトは、このスクリプトを実行した PC と同じ PC 上で動作しているプログラムに対してファイルを送信するようになっています。これは、connect
メソッドに指定する引数で '127.0.0.1'
を指定しているからで、ここを変更すれば他の PC にファイルを送信することも可能です。
ファイルの受信プログラム
次に、先ほど示したファイルの送信プログラムの相手となる、ファイルの受信プログラムのサンプルスクリプトを紹介します。そのスクリプトが下記となります。
import socket
import sys
# 受信したデータの保存先のファイルパスをコマンドライン引数から取得
file_path = sys.argv[1]
# "受信するデータのサイズ"を示すデータのバイト長
SIZE_DATA_LEN = 8
# ソケットを生成してバインド
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 40001))
# ソケットをリッスン状態にセット
sock.listen()
# 接続を確立
e_sock, addr = sock.accept()
"""まずは'受信するデータのサイズ'を受信"""
byte_data_len = bytes()
size_data_len = SIZE_DATA_LEN
# size_data_lenのサイズを受信するまでrecvを繰り返し
while len(byte_data_len) < size_data_len:
recv_data = e_sock.recv(SIZE_DATA_LEN)
# 受信したデータを結合
byte_data_len += recv_data
# バイト型のデータをint型に変換
data_len = int.from_bytes(byte_data_len, 'little')
"""データの本文を受信"""
byte_data = bytes()
# data_lenのサイズを受信するまでrecvを繰り返し
while len(byte_data) < data_len:
recv_data = e_sock.recv(SIZE_DATA_LEN)
# 受信したデータを結合
byte_data += recv_data
# 接続を確立したソケットをクローズ
e_sock.close()
# 受信したデータをファイルとして保存
with open(file_path, 'wb') as f:
f.write(byte_data)
# ソケットをクローズ
sock.close()
上記は、通信相手からデータを受信し、そのデータをコマンドライン引数で指定されたファイルパスのファイルに書き込むスクリプトになっています。クライアントサーバーモデルのサーバーの位置づけで作成しています。
ポイントは、受信したデータの先頭 8
バイトを先に読み込んで “受信するデータのサイズ” を取得し、そのサイズを受信できるまで recv
メソッドを繰り返し実行している点になります。これにより、送信側が送信してきたデータの全体を確実に受信できるようになります。このあたりの仕組みは下記ページの 方法①:送信するデータのサイズをデータの本文の前に付加する で解説していますので、詳しく知りたい方は下記ページの 方法①:送信するデータのサイズをデータの本文の前に付加する を参照していただければと思います。
プログラムの動作確認
最後に、先ほど示したファイルの送信プログラムおよびファイルの受信プログラムの動作確認をしておきましょう!
まずは、ファイルの送信プログラム で示したスクリプトを file_send.py
、ファイルの受信プログラム で示したスクリプトを file_recv.py
という名前で適当なフォルダにファイル保存してください。また、file_send.py
から実際にファイルの送信を行いますので、事前に送信するファイルを選んでおいてください。送信するファイルとしては画像ファイルが分かりやすいと思いますので、ここでは画像ファイルを選ぶことを前提に解説していきます。また、送信する画像ファイルの拡張子を覚えておいてください。
続いて、ターミナルやコマンドプロンプト等のコマンド実行可能なアプリを2つ起動し、さらにファイルを保存したフォルダに移動します。
そして、一方のコマンド実行可能なアプリから下記コマンドを実行してファイルの受信プログラムを起動します。前述のとおり、ファイルの受信プログラムはクライアントサーバーモデルにおけるサーバーの位置付けで作成しており、基本的にサーバーはクライアントよりも先に起動して待機状態にしておく必要があります。なので、先に file_recv.py
を実行する必要があります。また、下記の 拡張子
部分には、送信する画像ファイルの拡張子と同じものを指定してください。
python file_recv.py recv_file.拡張子
続いて、もう一方のコマンド実行可能なアプリから下記コマンドを実行してファイルの送信プログラムを起動します。下記の ファイルパス
部分には送信したい画像ファイルのファイルパスを指定してください。
python file_send.py ファイルパス
上記コマンドを実行すれば、ファイルの送信プログラムが起動して ファイルパス
で指定したパスのファイルが受信プログラムに対して送信されることになります。そして、その受信プログラムがファイルを受信し、それが recv_file.拡張子
という名前で file_recv.py
が存在するフォルダにファイル保存されるはずです。
ということで、次は recv_file.拡張子
をダブルクリックしてファイルを開いてみましょう!送信したファイルが画像ファイルであれば、画像のビューワーアプリが起動して送信したファイルと同じ画像が表示されるはずです。
同じ PC 上でのファイルの送受信のため、少し実感が湧きにくいかもしれませんが、この recv_file.拡張子
はファイルの受信プログラムがソケット通信を利用して受信したファイルであり、これらの動作より、ソケット通信を利用したファイルの送信が実現できていることが確認できたことになります。
もし可能であれば、2つの PC を用意して動作確認を実施し、異なる PC 間でもソケット通信を利用したファイルの送受信が実現できていることを確認してみていただければと思います。
スポンサーリンク
まとめ
このページでは、Python でのソケット通信でファイルを送信する方法について説明しました!
“ファイルの送信” と聞くと難しそうに感じるかもしれませんが、ファイルの送信も結局は単なるデータの送信で、ファイルから読み込んだデータを送信すればよいだけです。ただし、読み込むファイルはバイナリモードで開く必要がある点や、サイズが大きくなりがちなので送信(受信)するデータが途切れてしまわないように対策する必要がある点には注意してください。
ソケット通信ではバイト型のデータしか送受信できないという制約がありますが、逆にバイト型のデータに変換さえしてやれば何でも送受信できると考えることもできます。ファイルもバイナリモードで開いてバイト型のデータを読み込んでしまえば送信可能ですし、同様に他のデータもバイト型に変換してしまえば送信可能です。
これを利用した辞書の送信方法についても下記ページで解説していますので、興味があれば是非読んでみてください!
【Python】ソケット通信で辞書を送信する方法(JSONの利用)