14歳の自分に衝撃の事実を告げてください

UDP通信を使うチャットプログラムを改変して、
小さいサイズの画像も送信できないかと、
画像を分割して送信するようにプログラムをつくったつもりですが、
画像データを受信できませんでした。
片方のパソコンからもう片方のパソコンに同じこのプログラムのtest.pyファイルを入れて、お互いIPアドレスを入力して通信する環境です。
テキストの通信はできています。
次のプログラムをどう修正すれば画像が遅れて受信できるようになりますでしょうか?
よろしくお願いします。

import tkinter as tk
from PIL import Image, ImageTk
import socket
import threading
from tkinter import filedialog
import base64
import io

# Port番号
PORTNUM = 8000
BUFFER_SIZE = 1024 # 画像データのバッファサイズ

class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)

self.master.title("画像の表示") # ウィンドウタイトル
self.master.geometry("600x400") # ウィンドウサイズ(幅x高さ)

# Label for displaying received image
self.image_label = tk.Label(self.master)
self.image_label.place(x=280, y=160, width=160, height=120)

# Label for displaying image preview
self.preview_label = tk.Label(self.master)
self.preview_label.place(x=10, y=190, width=160, height=120)

# Button for selecting an image
self.select_img_button = tk.Button(self.master, text='画像を選択', command=self.select_image)
self.select_img_button.place(x=280, y=290, width=120, height=30)

# Button for sending an image
self.send_img_button = tk.Button(self.master, text='画像を送信', command=self.send_image)
self.send_img_button.place(x=10, y=290, width=120, height=30)

# UDP socket setup
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('', PORTNUM))
self.sock.settimeout(0.1)

# Entry widgets
self.ipaddr = tk.StringVar()
self.ipaddr.set('192.168.8.x')
ent1 = tk.Entry(self.master, textvariable=self.ipaddr)
ent1.place(x=120, y=20, width=120, height=20)

self.txdata = tk.StringVar()
self.txdata.set('send data')
ent2 = tk.Entry(self.master, textvariable=self.txdata)
ent2.place(x=10, y=100, width=260, height=20)

self.rxdata = tk.StringVar()
self.rxdata.set('receive data')
ent3 = tk.Entry(self.master, textvariable=self.rxdata)
ent3.place(x=10, y=160, width=260, height=20)

# Button for sending data
btn = tk.Button(self.master, text='送信', command=self.button_click)
btn.place(x=280, y=100, width=120, height=40)

# Start the timer thread
self.thread = threading.Thread(target=self.timerctrl)
self.thread.daemon = True
self.thread.start()

def resize_image(self, img_data, size=(160, 120)):
img = Image.open(io.BytesIO(img_data))
img = img.resize(size, Image.LANCZOS) # Use LANCZOS resampling filter
img = ImageTk.PhotoImage(img)
return img

# タイマー処理(約1秒周期)
def timerctrl(self):
try:
# UDP受信
recvdata, fromdata = self.sock.recvfrom(BUFFER_SIZE)
# 受信データを変換
recvtext = recvdata.decode('utf-8')
except socket.timeout: # 受信タイムアウト
recvtext = ''

# データを受信した場合
if recvtext.startswith("IMG:"):
# 受信データが画像データである場合
img_data = base64.b64decode(recvtext[4:])
img = self.resize_image(img_data)
self.image_label.config(image=img)
self.image_label.image = img
elif recvtext: # 画像データでなく、かつデータが存在する場合
self.rxdata.set(recvtext)

# タイマーを再設定
self.master.after(1000, self.timerctrl)

質問者からの補足コメント

  • # 画像を選択する関数
    def select_image(self):
    img_path = filedialog.askopenfilename()
    with open(img_path, "rb") as img_file:
    img_data = base64.b64encode(img_file.read()).decode('ascii')
    selected_img = self.resize_image(base64.b64decode(img_data))
    self.preview_label.config(image=selected_img)
    self.preview_label.image = selected_img

      補足日時:2023/12/09 19:11
  • # 画像データを送信
    self.sock.sendto(bytes("IMG:" + img_data, 'utf-8'), (self.ipaddr.get(), PORTNUM))


    # 画像を送信する関数
    def send_image(self):
    img_path = filedialog.askopenfilename()
    with open(img_path, "rb") as img_file:
    img_data = base64.b64encode(img_file.read()).decode('ascii')

      補足日時:2023/12/09 19:11
  • # 画像データを分割して送信
    chunk_size = 800 # データを分割するサイズ
    img_data_bytes = bytes("IMG:" + img_data, 'utf-8')
    for i in range(0, len(img_data_bytes), chunk_size):
    chunk = img_data_bytes[i:i+chunk_size]
    self.sock.sendto(chunk, (self.ipaddr.get(), PORTNUM))

      補足日時:2023/12/09 19:12
  • # 画像データを分割して送信
    chunk_size = 800 # データを分割するサイズ
    img_data_bytes = bytes("IMG:" + img_data, 'utf-8')
    for i in range(0, len(img_data_bytes), chunk_size):
    chunk = img_data_bytes[i:i+chunk_size]
    self.sock.sendto(chunk, (self.ipaddr.get(), PORTNUM))

      補足日時:2023/12/09 19:12
  • # ボタンが押されたときに実行する関数
    def button_click(self):
    addr = self.ipaddr.get() # 送信先IPアドレスを格納する
    data = self.txdata.get() # 送信データを格納する

    # UDP送信
    self.sock.sendto(bytes(data, 'utf-8'), (addr, PORTNUM))

    if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master=root)
    app.mainloop()

      補足日時:2023/12/09 19:12
  • (誤)どう修正すれば画像が遅れて受信できるようになります
    →(正)どう修正すれば画像が送信できて受信できるようになります

      補足日時:2023/12/09 19:19

A 回答 (1件)

Pythonはいまいち理解度がアレですが…



UDPパケットの場合、もろもろの送達保証はなかったはずです。
https://ja.wikipedia.org/wiki/User_Datagram_Prot …

送信側は複数回(?)で送信しているようですが、受信側はそれを正しく再構築できているんでしょうか?
複数回のどれか一つでも欠落した場合に再送処理なんてものはしてくれませんし、そもそも受信する順番自体が保証されません。
1/2/3/4/5/6…という順番で送信しても、受信側で2/6/4/2/1…のように受信される可能性もあるわけですが、そこらへんは大丈夫ですか?
(同じパケットが複数届く(通った経路が違う)ということもある…とされています)
# ローカルネットワークとか、LANケーブル直結とかならそうそうパケット欠落やら順番が入れ替わることはないかもしれませんけど。

チャット程度であればたいしたサイズのパケットにはならないでしょうから順番が入れ替わるということは考慮しなくても大丈夫かもしれませんが、
バイナリデータで欠落があった場合にどうなるか…とかも考慮が必要でしょう。
UDPヘッダ的には1パケットで約64k程度は送れる…ようですが、下位レイヤーで分割されたパケットが正しく復元されるのかはちょっと不明ですね。
# 判定用のチェックサムがあるので、チェックサム異常となれば、UDPパケット自体が破棄されておしまいでしょう。(破棄されたこと自体の通知もない…んじゃないでしょうかねぇ)


ということで、UDPでやるならばそういうパケットロスやら順番やら再送やらのプロトコルは自前で処理する必要があるかと。
いやなら素直にTCPでやりとり…でしょうね。
多対多になるチャットの場合にTCPで…というのは厳しいかもしれませんげと。


あと…本筋ではありませんが、バイナリデータをBASE64で送る場合は元データより最終的なサイズが増えますので注意しましょう。
https://ja.wikipedia.org/wiki/Base64#%E5%95%8F%E …
    • good
    • 0
この回答へのお礼

UDPでやるならばパケットロスやらデータ送信順番やら再送やらのプロトコルは自前で処理する必要があるんですね。もう一度一から考え直します。ありがとうございました。

お礼日時:2023/12/11 17:41

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報