Python UDP Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 Python UDP Socket Server/Client 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,除了 TCP 以外,想要自行客製化通訊規則的話就一定要學習 UDP 通訊方式,UDP 通訊程式通常分成伺服器端與客戶端兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 UDP 通訊程式,甚至可以寫出一個視訊或音訊聊天的程式,或者像 Skype、遠端桌面連線或 TeamViewer 這樣的通訊程式。

以下 Python UDP 內容將分為幾部分,分別為:

  • Python UDP Server/Client 通訊流程
  • Python UDP Server 伺服器端程式 (Echo Sever)
  • Python UDP Client 客戶端程式 (傳送使用者的輸入)
  • Python UDP Client 客戶端程式 (定時傳送資料)
  • Python UDP 常見問題

那麼就開始吧!

Python UDP Server/Client 通訊流程

以下 ShengYu 講解 Python UDP Server 端與 UDP Client 端的程式流程以及會如何使用這些 socket API,
UDP Server 的流程分為以下幾大步驟:

  1. 建立socket:s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM),指定 socket.AF_INET (Internet Protocol) family 的通訊協定,類型使用 socket.SOCK_DGRAM (Datagram Socket) 也就是 UDP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:s.bind()
  3. 接收客戶端傳來的資料:s.recvfrom()
  4. 傳送給對方發送資料:s.sendto()

跟 TCP 不同的是 UDP Client 不需要 listen()accept(),傳輸完畢後也不需要關閉連線。

UDP Client 的流程分為以下幾大步驟:

  1. 建立 socket:s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  2. 傳送資料:s.sendto()
  3. 接收資料:s.recvfrom()

跟 TCP 不同的是 UDP Client 不需要 connect(),傳輸完畢後也不需要關閉連線。

以上是 UDP Server/Client 通訊的重點流程,實際的 Python socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 UDP Server/Client 通訊程式吧!

Python UDP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 Python UDP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
跟 TCP 不同的是 UDP 不需使用 listen()accept(),直接使用 recvfrom 來接收任何一個 socket 地址的客戶端資料,以及 sendto 傳送資料給指定 socket 位址的客戶端
這邊用一個迴圈不斷地重複 recvfrom 接收資料與 sendto 傳送資料,這樣便是一個完整的 Python UDP 伺服器程式。

本篇範例是以 Python 3 作為示範,另外補充一下 Python 2 與 Python 3 的差異處,
Python 2 的 recvfrom()sendto() 傳入的參數類型為 str
到了 Python 3 的 recvfrom()sendto() 傳入的參數類型改為 byte
所以 str 要轉成 byte 要透過 decode(),以及使用 decode() 轉回 str

python3-udp-socket-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))

print('server start at: %s:%s' % (HOST, PORT))
print('wait for connection...')

while True:
indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

outdata = 'echo ' + indata.decode()
s.sendto(outdata.encode(), addr)
s.close()

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 socket.error: [Errno 98] Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Python UDP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 UDP 伺服器端的程式,接著再用另一個終端機執行 UDP 客戶端的程式。Python UDP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,跟 TCP 不同的是 UDP 不需要 connect() 而是直接用 sendto() 將資料送往指定的主機 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,需要注意的是要取得使用者輸入的函式 Python 2 裡是使用 raw_input(),而 Python 3 是使用 input()
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

python3-udp-socket-client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000
server_addr = (HOST, PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
outdata = input('please input message: ')
print('sendto ' + str(server_addr) + ': ' + outdata)
s.sendto(outdata.encode(), server_addr)

indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
$ python3 python3-udp-socket-client.py 
please input message: hello
sendto ('0.0.0.0', 7000): hello
recvfrom ('127.0.0.1', 7000): echo hello
please input message: hello udp
sendto ('0.0.0.0', 7000): hello udp
recvfrom ('127.0.0.1', 7000): echo hello udp
please input message: ^CTraceback (most recent call last):
File "python3-udp-socket-client.py", line 12, in <module>
outdata = input('please input message: ')
KeyboardInterrupt

伺服器端輸出結果如下,

server
1
2
3
4
5
$ python3 python3-udp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
recvfrom ('127.0.0.1', 36042): hello
recvfrom ('127.0.0.1', 36042): hello udp

Python UDP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 UDP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 UDP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 time.sleep(1) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

python3-udp-socket-client-heartbeat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import time

HOST = '0.0.0.0'
PORT = 7000
server_addr = (HOST, PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
outdata = 'heartbeat'
print('sendto ' + str(server_addr) + ': ' + outdata)
s.sendto(outdata.encode(), server_addr)

indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

time.sleep(1)

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 python3-udp-socket-client-heartbeat.py 
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
^CTraceback (most recent call last):
File "python3-socket-udp-client-heartbeat.py", line 20, in <module>
time.sleep(1)
KeyboardInterrupt

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
$ python3 python3-udp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat

Python UDP 常見問題

在 UDP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recvfrom 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recvfrom 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recvfrom 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recvfrom 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

以上就是 Python UDP Server/Client 網路通訊教學介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
socket — Low-level networking interface — Python 3 documentation
https://docs.python.org/3/library/socket.html

其它相關文章推薦
下一篇將會介紹如何用 Python 3 提供的 socketserver 來撰寫 server 端的程式,
Python socketserver 伺服器端網路通訊程式