0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ろうとるがPythonを扱う、、(その16:ファイル転送)

Posted at

ソケットによるファイル転送

ソケットレベル(TCP)を用いて、ファイル転送を実現した。

画面

最初に結果の紹介。

Client(ファイル送信側)

後述する「Start」ボタンをクリックすると、転送するファイルを選ぶダイアログボックスが表示される。
ファイル選択.png
転送中には、ボタンが「Stop」に変わり(クリック可能)、その右に転送の進行状況(%表記)が表示される。
送信中.png
結果は下記のようなもの。
Client.png
転送時間(msとs)および転送速度(KbpsとMbps)を表示するだけではなく、エラーも表示する。また、転送時間と転送速度とは、CSV化(ファイル化)する。

Server(ファイル受信側)

「Start」をクリックすると開始(下記は待受中のため「Stop」が表示)。
Server.png
Client側のIPアドレスや受信したファイルサイズの表示。

Configuration

設定画面.png
TCP接続タイムアウト値やポート番号の設定である。

ソースコード

いつものように、キーとなるところを中心に記載(主にコード内)。

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?