python核心編程筆記——網絡編程(二)

Python中的網絡編程

. 本節中使用的主要模塊是socket模塊,在模塊中可以找到socket()函數,該函數用於創建套接字對象。套接字也有自己的方法集,這些方法可以實現基於套接字的網絡通信。

socket()模塊函數

. 創建套接字要使用socket.socket()函數,其一般語法如下:

socket(socket_family,socket_type,protocol = 0)

. 其中socket_family是AF_UNIX或AF_INET,socket_type是SOCK_STREAM或SOCK_DGRAM,protocol通常省略,默認爲0。因此創建TCP或UDP套接字的形式如下:

tcpSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
udpSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

. 在有了套接字對象後,就可以使用套接字對象的方法來做進一步交互。對Python來說,可以使用“from socket import * ”將socket屬性引入命名空間中,這樣能夠大大縮短代碼:

tcpSock = socket.socket(AF_INET,SOCK_STREAM)

套接字對象(內置)方法

. 以下的表列舉了一些常見的套接字對象的方法和屬性:

名稱 描述
服務器套接字方法
s.bind() 將地址(主機號、端口號對)綁定到套接字上
s.listen() 設置並啓動TCP監聽器
s.accept() 被動接受TCP客戶端連接,一直等待知道連接到達(阻塞),每個連接進來的客戶端,都會通過accept函數返回一個不同的客戶端的socket對象和屬於客戶端的套接字
客戶端套接字方法
s.connect() 主動發起TCP服務器連接
s.connect_ex() 上個方法的升級版,會以錯誤碼的形式返回問題,而不是拋出異常
普通的套接字方法
s.recv() 接受TCP消息
s.recv_into() 接受TCP消息到指定的緩衝區
s.send() 發送TCP消息
s.sendall() 完整的發送TCP消息
s.recvfrom() 接受UDP消息
s.recvfrom_into() 接受UDP消息到指定的緩衝區
s.sendto() 發送UDP消息
s.getpeername() 連接到套接字(TCP)的遠程地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回給定套接字選項的值
s.setsockopt() 設置給定套接字選項的值
s.shutdown() 關閉連接
s.close() 關閉套接字
s.detach() 在未關閉文件描述符的情況下關閉套接字,返回文件描述符
s.ioctl() 控制套接字的模式(僅支持 Windows)
面向阻塞的套接字方法
s.setblocking() 設置套接字的阻塞或非阻塞模式
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 獲取阻塞套接字操作的超時時間
面向文件的套接字方法
s.fileno() 套接字的文件描述符
s.makefile() 創建與套接字關聯的文件對象
數據屬性
s.family 套接字家族
s.type 套接字類型
s.proto 套接字協議

創建TCP服務器

. 首先看看創建TCP服務器的一般僞代碼:

ss = socket()					#創建服務器套接字
ss.bind()						#套接字與地址綁定
ss.listen()						#監聽連接
inf_loop:						#服務器無限連接
	cs = ss.accept()			#接受客戶端連接
	comm_loop:					#通信循環
		cs.recv()/cs.send()		#對話(接受/發送)
	cs.close()					#關閉客戶端套接字
ss.close()						#關閉服務器套接字(可選)

. 所有的套接字都通過socket()函數來創建對象,因爲服務器需要佔用一個端口等待客戶端的請求,所以需要綁定一個本地地址,特別的TCP服務器必須監聽(傳入的)連接,一旦這個過程完了之後,服務器就可以開始它的無限循環。
  調用accept()函數之後,就開啓了一個簡單的(單線程)服務器,它會等待客戶端的連接。默認情況下,accept()是阻塞的,這意味着執行將被暫停,直到一個連接到達(套接字也確實支持非阻塞模式)。
  一旦服務器接收了一個連接,就會返回(利用accept())一個獨立的客戶端套接字,用來與即將到來的消息進行交換。也就是說,當一個傳入的請求到達時,服務器會創建一個新的通信端口來直接與客戶端進行通信,再次空出主要的端口(原始服務器套接字),以使其能夠接受新的客戶端連接;而通過使用這個新的套接字,客戶端和服務器之間就可以開始參與發送和接受的對話中,直到連接終止(當一方關閉連接或者向對方發送一個空字符串時,通常就會關閉連接)。
  接下來創建一個TCP服務器,它接受來自客戶端的消息,並返回加了時間戳的相同內容的消息:

from socket import *
from time import ctime

Host = ''
Port = 21567
Bufsize = 1024
Addr = (Host,Port)

tcpSerSock = socket(AF_INET,SOCK_STREAM)
tcpSerSock.bind(Addr)
tcpSerSock.listen(5)

while True:
    print('waiting for connection...')
    tcpCliSock,addr = tcpSerSock.accept()
    print('...connected from:',addr)

    while True:
        data = tcpCliSock.recv(Bufsize)
        if not data:
            break
        senddata = '[%s] %s.' % (bytes(ctime(),'utf-8'),data)
        tcpCliSock.send(senddata.encode())

    tcpCliSock.close()
tcpSerSock.close()


創建TCP客戶端

. 創建客戶端相對要簡單一些,首先還是看看僞碼:

cs = socket() 				#  創建客戶端套接字
cs.connect() 				#  嘗試連接服務器
comm_loop:					#  通信循環
	cs.send()/cs.recv() 	#  對話(發送 / 接收)
cs.close() 					#  關閉客戶端套接字

. 一旦客戶端擁有了一個套接字,它就可以利用套接字的connect()方法直接創建一個到服務器的連接,當連接建立之後就可以參與到與服務器的對話中,最後,如果客戶端完成了它的事務,它就可以關閉套接字,終止此次連接。
  以下是TCP客戶端的代碼示例:

from socket import *

Host = 'localhost'
Port = 21567
Bufsize = 1024
Addr = (Host,Port)

tcpCliSock = socket(AF_INET,SOCK_STREAM)
tcpCliSock.connect(Addr)

while True:
    data = input('> ')
    if not data:
        break
    
    tcpCliSock.send(data.encode())
    data = tcpCliSock.recv(Bufsize)
    if not data:
        break
    print(data.decode('utf-8'))

tcpCliSock.close()

. 客戶端也有一個無限循環,但是不會意味着它會永遠運行下去,客戶端循環在以下兩種情況將會跳出:用戶沒有輸入;或者服務器終止且對recv()方法的調用失敗。

執行TCP服務器和客戶端

. 首先要運行的應該是服務器,因爲如果先運行客戶端,那麼將無法進行任何連接。服務器可以視爲一個被動夥伴,因爲必須首先建立自己,然後被動地等待連接。另一方面,客戶端是一個主動的合作伙伴,因爲它主動發起一個連接。

創建UDP服務器

. UDP服務器不需要TCP服務器那麼多設置,因爲它不是面向連接的,除了等待傳入的連接外,幾乎不需要做任何其他工作。它的僞碼如下:

ss = socket()									#  創建服務器套接字
ss.bind() 										#  綁定服務器套接字
inf_loop:										#  服務器無限循環
	cs = ss.recvfrom()/ss.sendto()  			#  關閉(接收 / 發送)
ss.close()  									#  關閉服務器套接字(可選)

. 從上面的僞代碼中可以看出,除了普通的創建套接字並將其綁定到本地地址(主機名/端口號對)外,並沒有額外的工作。UDP 和 TCP 服務器之間的另一個顯著差異是,因爲數據報套接字是無連接的,所以就沒有爲了成功通信而使客戶端連接到一個獨立的套接字“轉換“的操作,以下是UDP加時間戳的服務器代碼示例:

from socket import *
from time import ctime

Host = ''
Port = 21567
Bufsize = 1024
Addr = (Host,Port)

udpSerSock = socket(AF_INET,SOCK_DGRAM)
udpSerSock.bind(Addr)

while True:
    print('waiting for message...')
    data,addr = udpSerSock.recvfrom(Bufsize)
    senddata = '[%s] %s.' % (bytes(ctime(),'utf-8'),data)
    udpSerSock.sendto(senddata.encode(),addr)
    print('...received from and returned to :',addr)

udpSerSock.close()

創建UDP客戶端

. UDP的客戶端代碼是最短的,它的僞代碼如下:

cs = socket() 							#  創建客戶端套接字
comm_loop: 								#  通信循環
	cs.sendto()/cs.recvfrom()  			#  對話(發送 / 接收)
cs.close() 								#  關閉客戶端套接字

. 一旦創建了套接字對象,就進入對話循環中與服務器進行信息交互,最後當通信結束的時候會關閉套接字。以下是UDP的時間戳客戶端代碼示例:

from socket import *

Host = 'localhost'
Port = 21567
Bufsize = 1024
Addr = (Host,Port)

udpCliSock = socket(AF_INET,SOCK_DGRAM)

while True:
    data = input('> ')
    if not data:
        break
    udpCliSock.sendto(data.encode(),Addr)
    data,Addr = udpCliSock.recvfrom(Bufsize)
    if not data:
        break
    print(data)

udpCliSock.close()

socket模塊屬性

. 除了現在熟悉的 socket.socket()函數之外,socket 模塊還提供了更多用於網絡應用開發的屬性,下標列出了常用的屬性:

屬性名 描述
數據屬性
AF_UNIX、AF_INET、AF_INET6 、AF_NETLINK 、AF_TIPC Python 中支持的套接字地址家族
SO_STREAM、SO_DGRAM 套接字類型(TCP=流,UDP=數據報)
has_ipv6 指示是否支持 IPv6 的布爾標記
異常
error 套接字相關錯誤
herror 主機和地址相關錯誤
gaierror 地址相關錯誤
timeout 超時時間
函數
socket() 以給定的地址家族、套接字類型和協議類型(可選)創建一個套接字對象
socketpair() 以給定的地址家族、套接字類型和協議類型(可選)創建一對套接字對象,函數用於創建一對無名的、相互連接的套接字。 如果函數成功,則返回0,創建好的套接字分別是sv[0]和sv[1];否則返回-1,錯誤碼保存於errno中
create_connection() 常規函數,它接收一個地址(主機名,端口號)對,返回套接字對象
fromfd() 以一個打開的文件描述符創建一個套接字對象
ssl() 通過套接字啓動一個安全套接字層連接;不執行證書驗證
getaddrinfo() 獲取一個五元組序列形式的地址信息
getnameinfo() 給定一個套接字地址,返回(主機名,端口號)二元組
getfqdn() 返回完整的域名
gethostname() 返回當前主機名
gethostbyname() 將一個主機名映射到它的 IP 地址
gethostbyname_ex() gethostbyname()的擴展版本,它返回主機名、別名主機集合和 IP 地址列表
gethostbyaddr() 將一個 IP 地址映射到 DNS 信息;返回與 gethostbyname_ex()相同的 3 元組
getprotobyname() 將一個協議名(如‘tcp’)映射到一個數字
getservbyname()/getservbyport() 將一個服務名映射到一個端口號,或者反過來;對於任何一個函數來說,協議名都是可選的
ntohl()/ntohs() 將來自網絡的整數轉換爲主機字節順序
htonl()/htons() 將來自主機的整數轉換爲網絡字節順序
inet_aton()/inet_ntoa() 將 IP 地址八進制字符串轉換成 32 位的包格式,或者反過來(僅用於 IPv4 地址)
inet_pton()/inet_ntop() 將IP 地址字符串轉換成打包的二進制格式,或者反過來(同時適用於 IPv4 和IPv6 地址)
getdefaulttimeout()/setdefaulttimeout() 以秒(浮點數)爲單位返回默認套接字超時時間;以秒(浮點數)爲單位設置默認套接字超時時間
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章