簡介
網絡編程就是在程序中實現網絡中兩臺計算機的通信。而用Python進行網絡編程,就是在Python程序本身這個進程內,連接別的服務器進程的通信端口進行通信。
初識Socket
Socket(又稱套接字)起源於Unix,是應用層與TCP/IP協議族通信的中間軟件抽象層。複雜的TCP/IP協議族隱藏在了Socket接口內部,用戶只需要簡單地使用Socket接口來進行網絡編程。應用程序通常是通過Socket向網絡發出請求或者應答網絡請求,使主機間或者一臺計算機上的進程間可以通訊。
創建Socket
在Python中,我們用 socket()函數來創建套接字,語法格式如下:
-> socket(family,type[,protocol])
參數解釋:
family:socket家族 | 描述 |
---|---|
socket.AF_UNIX | 只能夠用於單一的Unix系統進程間通信 |
socket.AF_INET | 指定使用IPv4協議進行服務器間網絡通信 |
socket.AF_INET6 | 指定使用IPv6協議進行服務器間網絡通信 |
type:socket類型 | 描述 |
---|---|
socket.SOCK_STREAM | 流式socket , for TCP |
socket.SOCK_DGRAM | 數據報式socket , for UDP |
socket.SOCK_RAW | 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。 |
socket.SOCK_SEQPACKET | 可靠的連續數據包服務 |
protocol:socket協議 | 描述 |
---|---|
一般不填且默認爲0 | 系統會根據地址格式和套接字類別,自動選擇一個合適的協議。 |
Socket常用方法
服務器端方法 | 描述 |
---|---|
socket.bind(address) | 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。 |
socket.listen(backlog) | 開始監聽TCP傳入連接。backlog指定在拒絕連接之前,操作系統可以掛起的最大連接數量。該值至少爲1,大部分應用程序設爲5就可以了。 |
socket.accept() | 被動接受TCP客戶端連接並返回(sock,address),其中sock是新的套接字對象,可以用來接收和發送數據,address是連接客戶端的地址。 |
客戶端方法 | 描述 |
---|---|
socket.connect(address) | 主動初始化TCP服務器連接,一般address的格式爲元組(hostname,port),如果連接出錯,返回socket.error錯誤。 |
socket.connect_ex(address) | connect()的擴展版本,出錯時返回出錯碼,而不是拋出異常。 |
公共方法 | 描述 |
---|---|
socket.recv(bufsize[,flag]) | 接受TCP套接字的數據。數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。 |
socket.send(string[,flag]) | 發送TCP數據。將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 |
socket.sendall(string[,flag]) | 完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。 |
socket.recvfrom(bufsize[.flag]) | 接受UDP套接字的數據。與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 |
socket.sendto(string[,flag],address) | 發送UDP數據。將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。 |
socket.close() | 關閉套接字。 |
TCP編程
大多數網絡通信連接都是可靠的TCP連接。創建TCP連接時,主動發起連接的叫客戶端,被動響應連接的叫服務器;連接成功後,通信雙方都能以流的形式發送數據。
在Python中用TCP協議進行Socket編程十分簡單,對於客戶端,要主動連接服務器的IP和指定端口,對於服務器,要首先監聽指定端口,然後,對每一個新的連接,創建一個線程或進程來處理。通常,服務器程序會無限運行下去。要注意的是,一個端口不能同被兩個Socket綁定。
下圖展示了TCP服務端和客戶端各自Socket創建以及它們之間的交互過程:
接下來是一個簡單的示例程序(建議自己敲下來跑一遍),服務器端接收客戶端的連接請求,把客戶端發過來的字符串加上Hello再發回去。
服務器端代碼:
#!/usr/bin/python
# 文件名:tcp_server.py
import socket
import threading
import time
# 實現連接成功後的交互,參數sock爲套接字對象,addr爲客戶端地址
def tcplink(sock, addr):
# 輸出連接成功的提示:
print('Accept new connection from %s:%s...' % addr)
# 發送TCP數據:
sock.send(b'Hello, What\'s your name?')
while True:
# 接收小於 1024 字節的數據
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'disconnect':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
# 關閉Socket,一次完整的網絡通信就此結束
sock.close()
print('Connection from %s:%s closed.' % addr)
# 創建一個基於IPv4和TCP協議的socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定地址(host,port)到套接字:
s.bind(('127.0.0.1', 9999))
# 開始監聽,最大連接數設爲5:
s.listen(5)
print('TCP Server is running...')
print('Waiting for connection...')
while True:
# 被動接受TCP客戶端連接,(阻塞式)等待連接的到來:
sock, addr = s.accept()
# 創建新線程來處理TCP連接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
客戶端代碼:
#!/usr/bin/python
# 文件名:tcp_client.py
import socket
# 創建一個基於IPv4和TCP協議的socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接,指定主機和端口:
s.connect(('127.0.0.1', 9999))
# 接收歡迎消息:
print(s.recv(1024).decode('utf-8'))
# 持續與服務器交互:
while True:
# 獲取用戶輸入:
msg = input('Your input:')
if not msg or msg == 'quit':
break
# 發送數據:
s.send(msg.encode('utf-8'))
# 輸出服務器返回的消息
print('From server:',s.recv(1024).decode('utf-8'))
# 發送斷開連接的指令
s.send(b'disconnect')
# 套接字關閉
s.close()
打開三個命令行窗口,一個運行服務器端程序,另外兩個運行客戶端程序,效果如下圖:
UDP編程
相對TCP,UDP則是面向無連接的協議。使用UDP協議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以直接發數據包。但是,數據包能否到達是無法確定的。
雖然用UDP傳輸數據不可靠,但它的優點是和TCP比,速度快,對於不要求可靠到達的數據,就可以使用UDP協議。
接下來我們使用UDP協議來實現與上面示例的程序。
服務器端代碼:
#!/usr/bin/python
# 文件名:udp_server.py
import socket
# 創建一個基於IPv4和UDP協議的socket:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定地址(host,port)到套接字:
s.bind(('127.0.0.1', 9999))
print('UDP Server is running...')
print('Waiting for message...')
while True:
# 接收數據,recvfrom()方法返回數據和客戶端的地址與端口:
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)
客戶端代碼:
#!/usr/bin/python
# 文件名:udp_client.py
import socket
# 創建一個基於IPv4和UDP協議的socket:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 持續與服務器交互:
while True:
# 獲取用戶輸入
msg = input('Your input:')
if not msg or msg == 'quit':
break
# 無需連接,直接發送數據到服務器Socket綁定的地址:
s.sendto(msg.encode('utf-8'), ('127.0.0.1', 9999))
# 輸出服務器返回的消息:
print('From server:',s.recv(1024).decode('utf-8'))
# 套接字關閉
s.close()
打開三個命令行窗口,一個運行服務器端程序,另外兩個運行客戶端程序,效果如下圖:
參考文章:
- python socket編程
- 菜鳥教程-python3網絡編程
- 廖雪峯-python3教程