ネットワークソケット
ここでは、低レベルネットワークインターフェースであるソケットを使ったネットワーク通信を取り上げる。具体的には、ポート番号を指定した、UDPおよびTCP通信である。また、tkinterによりGUI化している。
参考URL
- Pythonでネットワークプログラミング
- How to Work with TCP Sockets in Python (with Select Example)
- Pythonのソケットrecvメソッドでタイムアウトを設定するにはどうすればよいですか?
他にも参照したサイトがあったが、記録が残っておらず、、。
ソースコード
import、タブ名、ボタン名、変数定義
from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM
import select
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# List
tab_list = [' UdpClient ', ' UdpServer ', ' TcpClient ', ' TcpServer ']
btn1_list = [' Send ', ' Start '] # Initial Button for Client and Server
btn2_list = [' Send ', ' Sending '] # For Client
btn3_list = [' Start ', ' Stop '] # For Server
# Flag
Sending = [0, 0] # UDP, TCP
Receiving = [0, 0] # UDP, TCP
最後の変数は、送信中か否か、受信中か否かのフラグである。
送信側処理
def ClientThread(frm, arg, ip, num, txt):
global Sending
#print('Start of Thread')
frm.Btn['text'] = btn2_list[1]
frm.Btn['state'] = 'disabled'
if arg == 0:
sock = socket(AF_INET, SOCK_DGRAM)
else:
sock = socket(AF_INET, SOCK_STREAM)
txt = txt + '\n'
try:
if arg == 0:
sock.sendto(txt.encode('utf-8'), (ip, int(num)))
else:
sock.settimeout(10) # ToDo: Timeout Value
sock.connect((ip, int(num)))
sock.settimeout(None)
sock.send(txt.encode('utf-8'))
# Receive from Server
out = ''
fds = set()
fds.add(sock)
r, w, x = select.select(list(fds), [], [], 10) # ToDo: Timeout Value
#print('After Select')
for s in r:
if s is sock:
if arg == 0:
msg, srvaddr = sock.recvfrom(64)
else:
msg = sock.recv(64)
srvaddr = ip
msg = msg.decode(encoding='utf-8')
out = 'From: ' + str(srvaddr) + ' Replied Text: ' + msg
if len(out) == 0:
out = 'No data received (Timeout).' + '\n'
except:
out = 'Cannot connect to server.' + '\n'
sock.close()
Sending[int(arg/2)] = 0
frm.text.config(state='normal')
frm.text.insert(tk.END, out)
frm.text.config(state='disabled')
frm.text.see('end')
frm.Btn['text'] = btn2_list[0]
frm.Btn['state'] = 'normal'
#print('End of Thread')
return
後述する関数から呼び出されるスレッドである。
- ボタン表示を「Sending」に変更し、クリック不可状態にする。
- UDP or TCPに合わせてソケット作成(SOCK_DGRAM or SOCK_STREAM)。
- UDP or TCPに合わせて、メッセージ送信(sendto or connect+send)。TCP時、connectタイムアウトを10秒とする。
- サーバからのリプライ(データ受信)を10秒間待つ(select)。
- データ受信時、UDP or TCPに合わせて、受信(recvfrom or recv)データを表示用Bufferに入れる。IPアドレスも取得。
- ソケットクローズ。
- 表示用Bufferの内容をテキストボックスに書き出し。
- ボタン表示を「Send」に変更し、クリック可能な状態にする。
受信側処理
def ServThread(frm, arg, num, txt):
global Receiving
if arg == 1:
sock = socket(AF_INET, SOCK_DGRAM)
else:
sock = socket(AF_INET, SOCK_STREAM)
try:
sock.bind(('0.0.0.0', int(num))) # for all I/Fs
except:
frm.text.config(state='normal') # editable
frm.text.insert(tk.END, 'Bind Error.' + '\n')
frm.text.config(state='disabled') # not editable
frm.text.see('end')
Receiving[int((arg-1)/2)] = 0
frm.Btn['text'] = btn3_list[Receiving[int((arg-1)/2)]]
sock.close()
return
if arg == 3:
sock.listen(1)
fds = set()
fds.add(sock)
txt = txt + '\n'
while 1:
out = ''
try:
r, w, x = select.select(list(fds), [], [], 1)
for s in r:
if s is sock:
#print('Received')
if arg == 1:
msg, cliaddr = s.recvfrom(64)
else:
clisock, cliaddr = s.accept()
msg = clisock.recv(64)
msg = msg.decode(encoding='utf-8')
out = 'From: ' + str(cliaddr) + ' Received Text: ' + msg
# Reply to Client
time.sleep(0.5)
if arg == 1:
s.sendto(txt.encode('utf-8'), cliaddr)
else:
clisock.send(txt.encode('utf-8'))
clisock.close()
except:
out = 'Some Errors.' + '\n'
Receiving[int((arg-1)/2)] = 0
if len(out):
frm.text.config(state='normal') # editable
frm.text.insert(tk.END, out)
frm.text.config(state='disabled') # not editable
frm.text.see('end')
if Receiving[int((arg-1)/2)] == 0:
break
sock.close()
return
こちらも、後述する関数から呼び出されるスレッドである。
- UDP or TCPに合わせてソケット作成(SOCK_DGRAM or SOCK_STREAM)。
- すべてのIPアドレス(”0.0.0.0”)に対してバインディング(bind)。NG時は、Stop状態にして、エラーメッセージを表示。
- TCP時はListen。
- 1秒毎にデータ受信のチェック(select)。
- UDP or TCPに合わせて、クライアントからの受信(recvfrom or accept+recv)データを表示用Bufferに入れる。IPアドレスも取得。
- UDP or TCPに合わせて、メッセージをリプライ(sendto or send)。
- 表示用Bufferの内容をテキストボックスに書き出し。
- 「Stop」ボタンがクリックされていれば(Receiving[int((arg-1)/2)] == 0)、ループを抜け、ソケットをクローズして、スレッド終了。
送信側クリック時に呼び出される関数
def ExecSend(frm, arg, ip, num, txt):
global Sending
if Sending[int(arg/2)] == 0:
Sending[int(arg/2)] = 1
th = threading.Thread(target=ClientThread, args=(frm, arg, ip, num, txt), daemon=True)
th.start()
else:
Sending[int(arg/2)] = 0
return
- 送信中(リプライ待ち)の2度押し(クリック)を避けるため、実際の送信処理はスレッド化(前述の「ClientThread」)する。
受信側クリック時に呼び出される関数
def ExecRecv(frm, arg, num, txt):
global Receiving
if Receiving[int((arg-1)/2)] == 0:
Receiving[int((arg-1)/2)] = 1
th = threading.Thread(target=ServThread, args=(frm, arg, num, txt), daemon=True)
th.start()
else:
Receiving[int((arg-1)/2)] = 0
frm.Btn['text'] = btn3_list[Receiving[int((arg-1)/2)]]
return
- 待ち受け中でもサーバーをStopできるよう、実際の待ち受け処理はスレッド化(前述の「ServThread」)する。
- 状態(フラグ)に合わせて、ボタンの表示を変える。(Stop/Start)
ボタンクリック時の動作
def btn_clk(frm, arg):
if arg%2 == 0:
ip = frm.entryIP.get()
num = frm.entryPort.get()
txt = frm.entryText.get()
if arg%2 == 0:
ExecSend(frm, arg, ip, num, txt)
else:
ExecRecv(frm, arg, num, txt)
return
エントリーボックスから、IPアドレス(送信側のみ)、ポート番号、テキストを取得し、前述した送信関数(ExecSend())および受信側関数(ExecRecv())を呼び出す。
tkinter(GUI)
def create_content(frm, arg):
frm1 = tk.Frame(frm)
frm2 = tk.Frame(frm)
frm1.pack()
frm2.pack()
if arg%2 == 0:
label0 = ttk.Label(frm1, text=' IP address ')
else:
label0 = ttk.Label(frm1, text='')
label0.grid(row=0, column=0)
if arg%2 == 0:
frm.entryIP = ttk.Entry(frm1, width=15)
frm.entryIP.grid(row=0, column=1)
else:
label05 = ttk.Label(frm1, text='')
label05.grid(row=0, column=1)
label1 = ttk.Label(frm1, text=' Port ')
label1.grid(row=0, column=2)
frm.entryPort = ttk.Entry(frm1, width=7)
frm.entryPort.grid(row=0, column=3)
label2 = ttk.Label(frm1, text=' Text ')
label2.grid(row=0, column=4)
frm.entryText = ttk.Entry(frm1, width=20)
frm.entryText.grid(row=0, column=5)
frm.Btn = ttk.Button(frm1, text=btn1_list[arg%2], command=lambda:btn_clk(frm, arg))
frm.Btn.grid(row=0, column=6)
frm.text = tk.Text(frm2, width=80, height=40)
ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=frm.text.yview)
frm.text["yscrollcommand"] = ysc.set
ysc.pack(side=tk.RIGHT, fill="y")
frm.text.config(state='disabled')
frm.text.pack()
return
ここは、その5あたりを参照されたし。送信側処理と受信側処理とをできるだけ共通化できるよう、IPアドレス不要の受信側処理には、空テキストを利用している。
メインプログラム
# Start of main program
# root main window
root = tk.Tk()
root.title("Send/Receive at UDP and TCP")
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 = tk.Frame(note)
tab1 = tk.Frame(note)
tab2 = tk.Frame(note)
tab3 = tk.Frame(note)
# Add tab
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])
note.add(tab2, text=tab_list[2])
note.add(tab3, text=tab_list[3])
# Create content of tab
create_content(tab0, 0)
create_content(tab1, 1)
create_content(tab2, 2)
create_content(tab3, 3)
# Locate tab
note.pack(expand=True, fill='both')
# main loop
root.mainloop()
こちらも、その5あたりを参照されたし。
全体
from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM
import select
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# List
tab_list = [' UdpClient ', ' UdpServer ', ' TcpClient ', ' TcpServer ']
btn1_list = [' Send ', ' Start '] # Initial Button for Client and Server
btn2_list = [' Send ', ' Sending '] # For Client
btn3_list = [' Start ', ' Stop '] # For Server
# Flag
Sending = [0, 0] # UDP, TCP
Receiving = [0, 0] # UDP, TCP
def ClientThread(frm, arg, ip, num, txt):
global Sending
#print('Start of Thread')
frm.Btn['text'] = btn2_list[1]
frm.Btn['state'] = 'disabled'
if arg == 0:
sock = socket(AF_INET, SOCK_DGRAM)
else:
sock = socket(AF_INET, SOCK_STREAM)
txt = txt + '\n'
try:
if arg == 0:
sock.sendto(txt.encode('utf-8'), (ip, int(num)))
else:
sock.settimeout(10) # ToDo: Timeout Value
sock.connect((ip, int(num)))
sock.settimeout(None)
sock.send(txt.encode('utf-8'))
# Receive from Server
out = ''
fds = set()
fds.add(sock)
r, w, x = select.select(list(fds), [], [], 10) # ToDo: Timeout Value
#print('After Select')
for s in r:
if s is sock:
if arg == 0:
msg, srvaddr = sock.recvfrom(64)
else:
msg = sock.recv(64)
srvaddr = ip
msg = msg.decode(encoding='utf-8')
out = 'From: ' + str(srvaddr) + ' Replied Text: ' + msg
if len(out) == 0:
out = 'No data received (Timeout).' + '\n'
except:
out = 'Cannot connect to server.' + '\n'
sock.close()
Sending[int(arg/2)] = 0
frm.text.config(state='normal')
frm.text.insert(tk.END, out)
frm.text.config(state='disabled')
frm.text.see('end')
frm.Btn['text'] = btn2_list[0]
frm.Btn['state'] = 'normal'
#print('End of Thread')
return
def ServThread(frm, arg, num, txt):
global Receiving
if arg == 1:
sock = socket(AF_INET, SOCK_DGRAM)
else:
sock = socket(AF_INET, SOCK_STREAM)
try:
sock.bind(('0.0.0.0', int(num))) # for all I/Fs
except:
frm.text.config(state='normal') # editable
frm.text.insert(tk.END, 'Bind Error.' + '\n')
frm.text.config(state='disabled') # not editable
frm.text.see('end')
Receiving[int((arg-1)/2)] = 0
frm.Btn['text'] = btn3_list[Receiving[int((arg-1)/2)]]
sock.close()
return
if arg == 3:
sock.listen(1)
fds = set()
fds.add(sock)
txt = txt + '\n'
while 1:
out = ''
try:
r, w, x = select.select(list(fds), [], [], 1)
for s in r:
if s is sock:
#print('Received')
if arg == 1:
msg, cliaddr = s.recvfrom(64)
else:
clisock, cliaddr = s.accept()
msg = clisock.recv(64)
msg = msg.decode(encoding='utf-8')
out = 'From: ' + str(cliaddr) + ' Received Text: ' + msg
# Reply to Client
time.sleep(0.5)
if arg == 1:
s.sendto(txt.encode('utf-8'), cliaddr)
else:
clisock.send(txt.encode('utf-8'))
clisock.close()
except:
out = 'Some Errors.' + '\n'
Receiving[int((arg-1)/2)] = 0
if len(out):
frm.text.config(state='normal') # editable
frm.text.insert(tk.END, out)
frm.text.config(state='disabled') # not editable
frm.text.see('end')
if Receiving[int((arg-1)/2)] == 0:
break
sock.close()
return
def ExecSend(frm, arg, ip, num, txt):
global Sending
if Sending[int(arg/2)] == 0:
Sending[int(arg/2)] = 1
th = threading.Thread(target=ClientThread, args=(frm, arg, ip, num, txt), daemon=True)
th.start()
else:
Sending[int(arg/2)] = 0
return
def ExecRecv(frm, arg, num, txt):
global Receiving
if Receiving[int((arg-1)/2)] == 0:
Receiving[int((arg-1)/2)] = 1
th = threading.Thread(target=ServThread, args=(frm, arg, num, txt), daemon=True)
th.start()
else:
Receiving[int((arg-1)/2)] = 0
frm.Btn['text'] = btn3_list[Receiving[int((arg-1)/2)]]
return
def btn_clk(frm, arg):
if arg%2 == 0:
ip = frm.entryIP.get()
num = frm.entryPort.get()
txt = frm.entryText.get()
if arg%2 == 0:
ExecSend(frm, arg, ip, num, txt)
else:
ExecRecv(frm, arg, num, txt)
return
def create_content(frm, arg):
frm1 = tk.Frame(frm)
frm2 = tk.Frame(frm)
frm1.pack()
frm2.pack()
if arg%2 == 0:
label0 = ttk.Label(frm1, text=' IP address ')
else:
label0 = ttk.Label(frm1, text='')
label0.grid(row=0, column=0)
if arg%2 == 0:
frm.entryIP = ttk.Entry(frm1, width=15)
frm.entryIP.grid(row=0, column=1)
else:
label05 = ttk.Label(frm1, text='')
label05.grid(row=0, column=1)
label1 = ttk.Label(frm1, text=' Port ')
label1.grid(row=0, column=2)
frm.entryPort = ttk.Entry(frm1, width=7)
frm.entryPort.grid(row=0, column=3)
label2 = ttk.Label(frm1, text=' Text ')
label2.grid(row=0, column=4)
frm.entryText = ttk.Entry(frm1, width=20)
frm.entryText.grid(row=0, column=5)
frm.Btn = ttk.Button(frm1, text=btn1_list[arg%2], command=lambda:btn_clk(frm, arg))
frm.Btn.grid(row=0, column=6)
frm.text = tk.Text(frm2, width=80, height=40)
ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=frm.text.yview)
frm.text["yscrollcommand"] = ysc.set
ysc.pack(side=tk.RIGHT, fill="y")
frm.text.config(state='disabled')
frm.text.pack()
return
# Start of main program
# root main window
root = tk.Tk()
root.title("Send/Receive at UDP and TCP")
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 = tk.Frame(note)
tab1 = tk.Frame(note)
tab2 = tk.Frame(note)
tab3 = tk.Frame(note)
# Add tab
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])
note.add(tab2, text=tab_list[2])
note.add(tab3, text=tab_list[3])
# Create content of tab
create_content(tab0, 0)
create_content(tab1, 1)
create_content(tab2, 2)
create_content(tab3, 3)
# Locate tab
note.pack(expand=True, fill='both')
# main loop
root.mainloop()
検証状況
以降、上側のキャプチャが送信側(クライアント)、下側のキャプチャが受信側(サーバー)である。
UDP
ループバック送受信
指定IPアドレス送受信
ループバック送受信と本結果から、複数のIPアドレスがバインディングされた状況にも対応できていることがわかる。
ポートクローズ
送信中のボタン
タイムアウト
TCP
ループバック送受信
指定IPアドレス送受信
ループバック送受信と本結果から、複数のIPアドレスがバインディングされた状況にも対応できていることがわかる。
ポートクローズ
EOF