ソケットによるファイル転送
ソケットレベル(TCP)を用いて、ファイル転送を実現した。
画面
最初に結果の紹介。
Client(ファイル送信側)
後述する「Start」ボタンをクリックすると、転送するファイルを選ぶダイアログボックスが表示される。
転送中には、ボタンが「Stop」に変わり(クリック可能)、その右に転送の進行状況(%表記)が表示される。
結果は下記のようなもの。
転送時間(msとs)および転送速度(KbpsとMbps)を表示するだけではなく、エラーも表示する。また、転送時間と転送速度とは、CSV化(ファイル化)する。
Server(ファイル受信側)
「Start」をクリックすると開始(下記は待受中のため「Stop」が表示)。
Client側のIPアドレスや受信したファイルサイズの表示。
Configuration
ソースコード
いつものように、キーとなるところを中心に記載(主にコード内)。
import
### import ###
import os
import time
from datetime import datetime
import threading
from socket import socket, AF_INET, SOCK_STREAM, inet_aton
import select
import logging
import logging.handlers
from tkinter import *
import tkinter.ttk as ttk
import tkinter.filedialog as fd # ファイル選択ダイアログボックス用
パラメーター関連
### List ###
tab_list = [' Client ', ' Server ', ' Configuration '] # タブリスト
btn_list = [' Start ', ' Stop '] # ボタンリスト
### Flag ###
Doing = [0, 0] # Client, Server 進行中かどうかのフラグ
### Timeout & Port Value (Configurable) ### タイムアウト値およびポート番号
default_connection_timeout = 10
default_connection_port = 60001
connection_timeout = default_connection_timeout
connection_port = default_connection_port
### Other Parameters ###
log_name = 'Result'
read_size = 4096 # read size from file 一度にファイルから読み込むサイズ
refresh_unit = 5 # refresh by value(%) 5%単位で進行状況の表示
recv_size = 4096 # read size from file Serverにて一度に受信するサイズ
メインプログラム
##### Start of Main Program #####
# Root Main Window ウィンドウの設定
root = Tk()
root.title("File Transfer v0.92")
root.geometry("600x300")
# Create an Instance of ttk style タブの色付けなど
fgcolor = "lightskyblue2"
bgcolor = "gray80"
style = ttk.Style()
style.theme_create("style1", parent="alt", settings={
"TNotebook.Tab": {
"configure": {"background": bgcolor },
"map": {"background": [("selected", fgcolor)],
} } } )
style.theme_use("style1")
# Create Notebook Widget
note = ttk.Notebook(root)
# Create Tab フレーム作成
tab0 = Frame(note) # Client
tab1 = Frame(note) # Server
tab2 = Frame(note) # Configuration
# Add Tab タブの追加
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])
note.add(tab2, text=tab_list[2])
# Create Content of Tab タブ及びフレームの作成
create_content(tab0, 0)
create_content(tab1, 1)
create_config(tab2)
# Locate Tab 配置
note.pack(expand=True, fill='both')
# Initial Log Setting ログ(Clientのみ)の初期化
log = logging.getLogger(log_name)
log.setLevel(logging.DEBUG)
fmt = '{:-%Y%m%d-%H%M%S}.csv'.format(datetime.now())
log_file = log_name + fmt
log_first = True
# Main Loop
root.mainloop()
Configuration画面関連
### Save configufation parameters: save_btn_clk: ###
def save_btn_clk(frm): # Configuration画面でSaveクリック時の処理
try:
connection_timeout = int(frm.connection_timeout.get())
except: # エラー時はデフォルト値を設定
connection_timeout = default_connection_timeout
try:
connection_port = int(frm.connection_port.get())
except:
connection_port = default_connection_port
frm.connection_timeout.delete(0, END)
frm.connection_timeout.insert(0, connection_timeout) # 入力値をセット
frm.connection_port.delete(0, END)
frm.connection_port.insert(0, connection_port)
return
### Display configuration: create_config ###
def create_config(frm): # Configuration画面の作成
label_Timeout = Label(frm, text='Connect Timeout (sec)')
label_Timeout.place(x=10, y=10)
frm.connection_timeout = Entry(frm, width=6) # 接続タイムアウト用エントリ
frm.connection_timeout.insert(0, default_connection_timeout)
frm.connection_timeout.place(x=200, y=10)
label_Port = Label(frm, text='Connection Port')
label_Port.place(x=10, y=40)
frm.connection_port = Entry(frm, width=6) # ポート番号用エントリ
frm.connection_port.insert(0, default_connection_port)
frm.connection_port.place(x=200, y=40)
frm.btn = Button(frm, text=" Save ", command=lambda:save_btn_clk(frm)) # Saveボタンおよびクリック時の関数定義
frm.btn.place(x=200, y=70)
return
ClientおよびServer画面
### Display Client & Server: create_content ###
def create_content(frm, arg): # arg - 0:Client 1:Server
frm1 = Frame(frm) # 入力エントリおよびボタンがあるサブフレーム
frm2 = Frame(frm) # 結果表示テキストボックスがあるサブフレーム
frm1.pack()
frm2.pack()
if arg == 0: # Client画面にはIPアドレスエントリあり
label_IP = Label(frm1, text=' IP address ')
label_IP.grid(row=0, column=0)
frm.entryIP = Entry(frm1, width=18)
frm.entryIP.grid(row=0, column=1)
frm.Btn = Button(frm1, text=btn_list[0], command=lambda:btn_clk(frm, arg)) # Start/Stopボタン(初期値はStart)およびクリック時の関数定義
if arg == 0: # Client画面には転送状況表示用フィールドあり
frm.Btn.grid(row=0, column=2)
frm.progress = Label(frm1, text = '')
frm.progress.grid(row = 0, column = 3)
else:
frm.Btn.grid(row=0, column=0)
frm.text = Text(frm2, width=80, height=40) 結果表示テキストボックス
ysc = Scrollbar(frm2, orient=VERTICAL, command=frm.text.yview)
frm.text["yscrollcommand"] = ysc.set
ysc.pack(side=RIGHT, fill="y")
frm.text.config(state='disabled')
frm.text.pack()
return
ボタンクリック
### Check IP Address Format: is_valid_ip ###
def is_valid_ip(addr): # IPアドレスフォーマットチェック
try:
inet_aton(addr)
return True
except:
return False
### Click button: btn_clk ### ボタンクリック時間数
def btn_clk(frm, arg): # arg - 0:Client 1:Server
if arg == 0: # Client時にはIPアドレス取得
ip = frm.entryIP.get()
if is_valid_ip(ip) == False:
ip = '127.0.0.1' # 入力IPアドレスエラー時はLoopbackセット
else:
ip = '' # Dummy
ExecThread(frm, arg, ip) スレッド呼び出し関数
return
スレッド呼び出し
### Execute Client&Server Thread: ExecThread ###
def ExecThread(frm, arg, ip):
if Doing[arg%2] == 0: # フラグが非進行中
Doing[arg%2] = 1 # フラグを進行中にセット
if arg == 0: # Clientスレッド呼び出し
th = threading.Thread(target=ClientThread, args=(frm, ip), daemon=True)
else: # Serverスレッド呼び出し
th = threading.Thread(target=ServerThread, args=(frm, ), daemon=True)
th.start()
else: # フラグが進行中
Doing[arg%2] = 0 # フラグを非進行中にセット
frm.Btn['text'] = btn_list[Doing[arg%2]] # ボタンをフラグに合わせて変更
return
結果表示
### Show text: add_text ###
def add_text(frm, txt):
frm.text.config(state='normal') # テキストボックス編集可状態へ
frm.text.insert(END, txt) # テキストボックスの最後に結果を追記
frm.text.config(state='disabled') # テキストボックス編集不可状態へ
frm.text.see('end')
return
Serverスレッド
### Server: ServerThread ###
def ServerThread(frm):
try:
sock = socket(AF_INET, SOCK_STREAM) # TCPソケット
sock.bind(('0.0.0.0', connection_port)) # for all I/Fs すべてのNetwork I/Fが対象
except: # エラー時処理
out = 'Socket/Bind Error.' + '\n'
add_text(frm, out)
Doing[1] = 0
frm.Btn['text'] = btn_list[Doing[1]]
sock.close()
return
sock.listen(1) # リッスン
fds = set()
fds.add(sock) # 待ち受けソケットの追加
while 1:
try:
r, w, x = select.select(list(fds), [], [], 1) # 待受
for s in r:
if s is sock: # ソケットに入力あり
clisock, cliaddr = s.accept()
out = 'Receiving from ' + str(cliaddr)
add_text(frm, out) # テキストボックスに送信元IPアドレス情報表示
total = 0
while 1:
data = clisock.recv(recv_size) # データ受信
# received data is thrown away
if len(data) == 0:
out = '\tReceived Size = ' + str(total) + '\n'
add_text(frm, out) # 受信サイズの表示
break
elif Doing[1] == 0: # 受信中にStopクリック
out = '\tStop Clicked.' + '\n'
add_text(frm, out)
break
total += len(data)
except:
out = 'Some Errors.' + '\n'
add_text(frm, out)
Doing[1] = 0
if Doing[1] == 0: # Listen中にStopクリック
break
sock.close()
frm.Btn['text'] = btn_list[Doing[1]] # ボタンをStartに変更
return
CLientスレッド
### Client: ClientThread ###
def ClientThread(frm, ip):
global log_first
if log_first: # 初回時のみログファイル設定
fh = logging.FileHandler(log_file)
log.addHandler(fh)
log_first = False
out = 'Time(msec), Speed(Kbps), Time(sec), Speed(Mbps)' # CSVファイルのタイトル
log.debug(out) # ログ化
out += '\n'
add_text(frm, out) # テキストボックスへの表示
try:
send_file = fd.askopenfilename(title = "Choose a file") # 転送ファイルの選択
fdr = open(send_file, 'rb') # ファイルオープン
except: # エラー時処理
out = 'File Open Error.' + '\n'
add_text(frm, out)
Doing[0] = 0
frm.Btn['text'] = btn_list[Doing[0]]
return
frm.progress["text"] = '\t0' # 転送状況を初期値(0)にセット
file_size = os.path.getsize(send_file) # ファイルサイズ取得
sock = socket(AF_INET, SOCK_STREAM) # TCPソケット
try:
sock.settimeout(connection_timeout) # タイムアウト値設定
sock.connect((ip, connection_port)) # 接続
total_size = 0
previous = 0
start_time = time.perf_counter_ns() # 開始時間
while 1:
send_data = fdr.read(read_size) # ファイルからデータ読み込み
sent_length = len(send_data)
if sent_length:
sock.send(send_data) # Serverへ送信
else: # ファイルからの読み込み終了
end_time = time.perf_counter_ns() # 終了時間
elapsed_time = (end_time - start_time) / 1000000
speed = file_size * 8 / elapsed_time
out = str(elapsed_time) + ', ' + str(speed) + ', '\
+ str(elapsed_time/1000)+ ', ' + str(speed/1000) # 転送時間および転送速度
log.debug(out) # ログ化
out += '\n'
add_text(frm, out) # テキストボックスへの表示
frm.progress["text"] = '\tDone' # 進行状況(終了)
break
total_size += sent_length
percent = total_size / file_size * 100
about = round(percent)
current = int(about/refresh_unit)
if current - previous > 0:
frm.progress["text"] = '\t' + str(about) # 進行状況をrefresh_unit単位で表示
previous = current
if Doing[0] == 0: # 転送中にStopクリック
out = 'Stop clicked' + '\n'
add_text(frm, out)
break
except: # エラー時処理
out = 'Cannot connect to server.' + '\n'
add_text(frm, out)
sock.close()
fdr.close()
Doing[0] = 0 # フラグを落とす
frm.Btn['text'] = btn_list[Doing[0]] # ボタンをStartに変更
return
全体
### import ###
import os
import time
from datetime import datetime
import threading
from socket import socket, AF_INET, SOCK_STREAM, inet_aton
import select
import logging
import logging.handlers
from tkinter import *
import tkinter.ttk as ttk
import tkinter.filedialog as fd
### List ###
tab_list = [' Client ', ' Server ', ' Configuration ']
btn_list = [' Start ', ' Stop ']
### Flag ###
Doing = [0, 0] # Client, Server
### Timeout & Port Value (Configurable) ###
default_connection_timeout = 10
default_connection_port = 60001
connection_timeout = default_connection_timeout
connection_port = default_connection_port
### Other Parameters ###
log_name = 'Result'
read_size = 4096 # read size from file
refresh_unit = 5 # refresh by value(%)
recv_size = 4096 # read size from file
### Show text: add_text ###
def add_text(frm, txt):
frm.text.config(state='normal')
frm.text.insert(END, txt)
frm.text.config(state='disabled')
frm.text.see('end')
return
### Client: ClientThread ###
def ClientThread(frm, ip):
global log_first
if log_first:
fh = logging.FileHandler(log_file)
log.addHandler(fh)
log_first = False
out = 'Time(msec), Speed(Kbps), Time(sec), Speed(Mbps)'
log.debug(out)
out += '\n'
add_text(frm, out)
try:
send_file = fd.askopenfilename(title = "Choose a file")
fdr = open(send_file, 'rb')
except:
out = 'File Open Error.' + '\n'
add_text(frm, out)
Doing[0] = 0
frm.Btn['text'] = btn_list[Doing[0]]
return
frm.progress["text"] = '\t0'
file_size = os.path.getsize(send_file)
sock = socket(AF_INET, SOCK_STREAM)
try:
sock.settimeout(connection_timeout)
sock.connect((ip, connection_port))
total_size = 0
previous = 0
start_time = time.perf_counter_ns()
while 1:
send_data = fdr.read(read_size)
sent_length = len(send_data)
if sent_length:
sock.send(send_data)
else:
end_time = time.perf_counter_ns()
elapsed_time = (end_time - start_time) / 1000000
speed = file_size * 8 / elapsed_time
out = str(elapsed_time) + ', ' + str(speed) + ', '\
+ str(elapsed_time/1000)+ ', ' + str(speed/1000)
log.debug(out)
out += '\n'
add_text(frm, out)
frm.progress["text"] = '\tDone'
break
total_size += sent_length
percent = total_size / file_size * 100
about = round(percent)
current = int(about/refresh_unit)
if current - previous > 0:
frm.progress["text"] = '\t' + str(about)
previous = current
if Doing[0] == 0:
out = 'Stop clicked' + '\n'
add_text(frm, out)
break
except:
out = 'Cannot connect to server.' + '\n'
add_text(frm, out)
sock.close()
fdr.close()
Doing[0] = 0
frm.Btn['text'] = btn_list[Doing[0]]
return
### Server: ServerThread ###
def ServerThread(frm):
try:
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(('0.0.0.0', connection_port)) # for all I/Fs
except:
out = 'Socket/Bind Error.' + '\n'
add_text(frm, out)
Doing[1] = 0
frm.Btn['text'] = btn_list[Doing[1]]
sock.close()
return
sock.listen(1)
fds = set()
fds.add(sock)
while 1:
try:
r, w, x = select.select(list(fds), [], [], 1)
for s in r:
if s is sock:
clisock, cliaddr = s.accept()
out = 'Receiving from ' + str(cliaddr)
add_text(frm, out)
total = 0
while 1:
data = clisock.recv(recv_size)
# received data is thrown away
if len(data) == 0:
out = '\tReceived Size = ' + str(total) + '\n'
add_text(frm, out)
break
elif Doing[1] == 0:
out = '\tStop Clicked.' + '\n'
add_text(frm, out)
break
total += len(data)
except:
out = 'Some Errors.' + '\n'
add_text(frm, out)
Doing[1] = 0
if Doing[1] == 0:
break
sock.close()
frm.Btn['text'] = btn_list[Doing[1]]
return
### Execute Client&Server Thread: ExecThread ###
def ExecThread(frm, arg, ip):
if Doing[arg%2] == 0:
Doing[arg%2] = 1
if arg == 0:
th = threading.Thread(target=ClientThread, args=(frm, ip), daemon=True)
else:
th = threading.Thread(target=ServerThread, args=(frm, ), daemon=True)
th.start()
else:
Doing[arg%2] = 0
frm.Btn['text'] = btn_list[Doing[arg%2]]
return
### Check IP Address Format: is_valid_ip ###
def is_valid_ip(addr):
try:
inet_aton(addr)
return True
except:
return False
### Click button: btn_clk ###
def btn_clk(frm, arg): # arg - 0:Client 1:Server
if arg == 0:
ip = frm.entryIP.get()
if is_valid_ip(ip) == False:
ip = '127.0.0.1' # Loopback
else:
ip = '' # Dummy
ExecThread(frm, arg, ip)
return
### Display Client & Server: create_content ###
def create_content(frm, arg): # arg - 0:Client 1:Server
frm1 = Frame(frm)
frm2 = Frame(frm)
frm1.pack()
frm2.pack()
if arg == 0:
label_IP = Label(frm1, text=' IP address ')
label_IP.grid(row=0, column=0)
frm.entryIP = Entry(frm1, width=18)
frm.entryIP.grid(row=0, column=1)
frm.Btn = Button(frm1, text=btn_list[0], command=lambda:btn_clk(frm, arg))
if arg == 0:
frm.Btn.grid(row=0, column=2)
frm.progress = Label(frm1, text = '')
frm.progress.grid(row = 0, column = 3)
else:
frm.Btn.grid(row=0, column=0)
frm.text = Text(frm2, width=80, height=40)
ysc = Scrollbar(frm2, orient=VERTICAL, command=frm.text.yview)
frm.text["yscrollcommand"] = ysc.set
ysc.pack(side=RIGHT, fill="y")
frm.text.config(state='disabled')
frm.text.pack()
return
### Save configufation parameters: save_btn_clk: ###
def save_btn_clk(frm):
try:
connection_timeout = int(frm.connection_timeout.get())
except:
connection_timeout = default_connection_timeout
try:
connection_port = int(frm.connection_port.get())
except:
connection_port = default_connection_port
frm.connection_timeout.delete(0, END)
frm.connection_timeout.insert(0, connection_timeout)
frm.connection_port.delete(0, END)
frm.connection_port.insert(0, connection_port)
return
### Display configuration: create_config ###
def create_config(frm):
label_Timeout = Label(frm, text='Connect Timeout (sec)')
label_Timeout.place(x=10, y=10)
frm.connection_timeout = Entry(frm, width=6)
frm.connection_timeout.insert(0, default_connection_timeout)
frm.connection_timeout.place(x=200, y=10)
label_Port = Label(frm, text='Connection Port')
label_Port.place(x=10, y=40)
frm.connection_port = Entry(frm, width=6)
frm.connection_port.insert(0, default_connection_port)
frm.connection_port.place(x=200, y=40)
frm.btn = Button(frm, text=" Save ", command=lambda:save_btn_clk(frm))
frm.btn.place(x=200, y=70)
return
##### Start of Main Program #####
# Root Main Window
root = Tk()
root.title("File Transfer v0.92")
root.geometry("600x300")
# Create an Instance of ttk style
fgcolor = "lightskyblue2"
bgcolor = "gray80"
style = ttk.Style()
style.theme_create("style1", parent="alt", settings={
"TNotebook.Tab": {
"configure": {"background": bgcolor },
"map": {"background": [("selected", fgcolor)],
} } } )
style.theme_use("style1")
# Create Notebook Widget
note = ttk.Notebook(root)
# Create Tab
tab0 = Frame(note) # Client
tab1 = Frame(note) # Server
tab2 = Frame(note) # Configuration
# Add Tab
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])
note.add(tab2, text=tab_list[2])
# Create Content of Tab
create_content(tab0, 0)
create_content(tab1, 1)
create_config(tab2)
# Locate Tab
note.pack(expand=True, fill='both')
# Initial Log Setting
log = logging.getLogger(log_name)
log.setLevel(logging.DEBUG)
fmt = '{:-%Y%m%d-%H%M%S}.csv'.format(datetime.now())
log_file = log_name + fmt
log_first = True
# Main Loop
root.mainloop()
EOF