Python TCP Socket Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 Python TCP Socket Server/Client 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中又以 TCP 通訊最常被使用,TCP 通訊程式通常分成伺服器端與客戶端的兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 TCP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 這樣的通訊程式。

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

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

Python 提供了兩個基本的 socket 模組:
Socket:標準的 BSD Socket API
SocketServer:Python 封裝好的 class,簡化了網絡服務器的撰寫開發,共有這4種:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer
本篇先以 Socket 的標準 BSD Socket API 來介紹,下一篇再介紹 SocketServer 的部份。

常見的 Socket API 函式 Overview 總覽

Python 的 socket 模組它提供了標準的 BSD Socket API,主要的 socket API 函式如下:
socket():建立 socket 與設定使用哪種通訊協定
socket.bind(address):將 socket 綁定到地址,在 AF_INET 下,以 tuple(host, port) 的方式傳入,如 socket.bind((host, port))
socket.listen(backlog):開始監聽 TCP 傳入連接,backlog 指定在拒絕連線前,操作系統可以掛起的最大連接數,該值最少為1,通常設為5就夠用了
socket.accept():等待連線,接受到 TCP 連線後回傳(conn, address),回傳的 conn 是新的 socket 類型,可以用來接收和發送資料,address 是連線客戶端的地址。
socket.connect(address):連線到 address 處的 socket,一般 address 的格式為 tuple(host, port),如果連線出錯,則回傳 socket.error 錯誤
socket.connect_ex(address):功能與 socket.connect(address) 相同,但成功回傳0,失敗回傳 errno 的值
socket.recv(bufsize[, flag]):接收 TCP 資料,Python3 回傳資料為 byte 類型,Python2 回傳資料為 str 類型,buffsize 指定要接收的最大資料量,flag 提供有關消息的其他訊息,通常可以忽略
socket.send(string[, flag]):發送 TCP 資料,Python3 是將 byte 發送到已連線的 socket,Python2 是將 str 發送到連線的socket,回傳值是發送的 byte 數
socket.sendall(string[, flag]):完整發送 TCP 資料,將 str 中的資料發送到已連線的 socket,但在回傳之前嘗試發送所有資料。成功回傳 None,失敗則拋出異常
socket.close():關閉 socket

Python Socket TCP Server/Client 通訊流程

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

  1. 建立socket:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM),指定 socket.AF_INET (Internet Protocol) family 的通訊協定,類型使用 socket.SOCK_STREAM (Stream Socket) 也就是 TCP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:s.bind()
  3. 開始監聽:s.listen()
  4. 等待與接受客戶端的請求連線:s.accept()
  5. 接收客戶端傳來的資料:s.recv()
  6. 傳送給對方發送資料:s.send()s.sendall()
  7. 傳輸完畢後,關閉 socket:s.close()

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

  1. 建立 socket:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  2. 連線至遠端地址:s.connect()
  3. 傳送資料:s.send()s.sendall()
  4. 接收資料:s.recv()
  5. 傳輸完畢後,關閉 socket:s.close()

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

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

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

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,
一旦有客戶端連線的話,就會從 accept() 繼續往下執行,
之後是另一個迴圈來服務這個連線,不斷地從這個連線 recv 接收資料與 send 傳送資料,
如果 recv() 收到的資料長度為0,表示客戶端已斷開連線,此時我們也關閉這個連線,
之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 Python TCP 伺服器程式。

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

python3-tcp-socket-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(5)

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

while True:
conn, addr = s.accept()
print('connected by ' + str(addr))

while True:
indata = conn.recv(1024)
if len(indata) == 0: # connection closed
conn.close()
print('client closed connection.')
break
print('recv: ' + indata.decode())

outdata = 'echo ' + indata.decode()
conn.send(outdata.encode())
s.close()

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

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

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

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

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

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

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

while True:
outdata = input('please input message: ')
print('send: ' + outdata)
s.send(outdata.encode())

indata = s.recv(1024)
if len(indata) == 0: # connection closed
s.close()
print('server closed connection.')
break
print('recv: ' + indata.decode())

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

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

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
$ python3 python3-tcp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
connected by ('127.0.0.1', 57060)
recv: hello
recv: hello tcp
client closed connection.

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

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

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

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

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

while True:
outdata = 'heartbeat'
print('send: ' + outdata)
s.send(outdata.encode())

indata = s.recv(1024)
if len(indata) == 0: # connection closed
s.close()
print('server closed connection.')
break
print('recv: ' + indata.decode())

time.sleep(1)

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 python3-tcp-socket-client-heartbeat.py 
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
^CTraceback (most recent call last):
File "python3-tcp-socket-client-heartbeat.py", line 24, in <module>
time.sleep(1)
KeyboardInterrupt

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
9
10
$ python3 python3-tcp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
connected by ('127.0.0.1', 50130)
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
client closed connection.

Python TCP 常見問題

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

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

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

其他參考
socket — Low-level networking interface — Python 3 documentation
https://docs.python.org/3/library/socket.html
Python Socket 编程详细介绍
https://gist.github.com/kevinkindom/108ffd675cb9253f8f71
shengyu7697/PyReportMyIP: This is a program that automatically updates the IP addresses to server.
https://github.com/shengyu7697/PyReportMyIP
Socket Programming in Python (Guide) – Real Python
https://realpython.com/python-sockets/
Python3 网络编程 | 菜鸟教程
https://www.runoob.com/python3/python3-socket.html
Python中的TCP socket寫法示例 | 程式前沿
https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/356876/
Python Socket 编程:基于TCP套接字网络错位的梦寐的博客-CSDN博客
https://blog.csdn.net/HHG20171226/article/details/93519772
Python Socket 编程:基于TCP套接字(1)网络错位的梦寐的博客-CSDN博客
https://blog.csdn.net/HHG20171226/article/details/93524184
Python學習:socketserver實現並發網絡錯位的夢寐的博客-CSDN博客
https://blog.csdn.net/HHG20171226/article/details/93711478
Python Socket 编程–基于UDP的套接字网络错位的梦寐的博客-CSDN博客
https://blog.csdn.net/HHG20171226/article/details/93590850

python tcp socket client / server examples
https://gist.github.com/tuxfight3r/bfd95575ce34af6bd3317611dc04006c
bind_socket.py
socket_client.py
socket_server.py
socket_server_threads.py
socket_server_with_select.py

其他相關參考
c# - Why does my python TCP server need to bind to 0.0.0.0 and not localhost or it’s IP address? - Stack Overflow
https://stackoverflow.com/questions/38256851/why-does-my-python-tcp-server-need-to-bind-to-0-0-0-0-and-not-localhost-or-its
Python 3 TypeError: Can’t convert ‘bytes’ object to str implicitly - Mkyong.com
https://mkyong.com/python/python-3-typeerror-cant-convert-bytes-object-to-str-implicitly/

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