[Python基礎語法] 專題七.網絡編程之套接字Socket、TCP和UDP通信實例
一. 服務器和客戶端架構
1.什么是客戶端/服務區架構?
書中的定義是服務器是一個軟件或硬件,用于向一個或多個客戶端(客戶)提供所需要的“服務”。服務器存在的唯一目的就是等待客戶的請求,給這些客戶服務,然后再等待其他的請求。而客戶連接上(預先已知的)服務器,提出自己的請求,發送必要的數據,然后等待服務器完成請求或說明失敗原因的反饋。
服務器不停的處理外來的請求,而客戶一次只能提出一個服務的請求,等待結果。再結束這個事務。客戶之后可以再提出其他的請求,只是這個請求會被視為另一個不同的事務了。
2.硬件客戶端/服務器架構和軟件客戶端/服務器架構
硬件的客戶端/服務器架構,例如打印服務器、文件服務器(客戶可以遠程把服務器的磁盤映射到自己本體并使用);軟件客戶端/服務器架構主要是程序的運行、數據收發、升級等,最常見的是Web服務器、數據庫服務器。如一臺機器存放一些網頁或Web應用程序,然后啟動服務。其服務器的任務就是接受客戶端的請求,把網頁發給客戶端(如用戶計算機上的瀏覽器),然后再等待下一個客戶端請求。
3.客戶端/服務器網絡編程
在完成服務之前,服務器必須要先完成一些設置。先要先創建一個通訊端點,讓服務器能“監聽”請求。你可以把我們服務器比作一個公司的接待員或回答公司總線電話的話務員,一旦電話和設備安裝完成,話務員也就到位后,服務就開始了。
同樣一旦通信端點創建好之后,我們在“監聽”的服務器就可以進入它那等待和處理客戶請求的無限循環中了。服務器準備好之后,也要通知潛在的客戶,讓它們知道服務器已經準備好處理服務了,否則沒人會提請求的。所以需要把公司電話公開給客戶。
而客戶端只要創建一個通信端點,建立到服務器的連接,然后客戶端就可以提出請求了。請求中也可以包含必要的數據交互。一旦請求處理完成,客戶端收到了結果,通信就結束了。這就是客戶端和服務器的簡單網絡通信。
二. 套接字Socket
1.什么是套接字
套接字是一種具有之前所說的“通信端點”概念的計算網絡數據結構。相當于電話插口,沒它無法通信,這個比喻非常形象。
套接字起源于20世紀70年代加州伯克利分校版本的Unix,即BSD Unix。又稱為“伯克利套接字”或“BSD套接字”。最初套接字被設計用在同一臺主機上多個應用程序之間的通訊,這被稱為進程間通訊或IPC。
套接字分兩種:基于文件型和基于網絡的
第一個套接字家族為AF_UNIX,表示“地址家族:UNIX”。包括Python在內的大多數流行平臺上都使用術語“地址家族”及其縮寫AF。由于兩個進程都運行在同一臺機器上,而且這些套接字是基于文件的,所以它們的底層結構是由文件系統來支持的。可以理解為同一臺電腦上,文件系統確實是不同的進程都能進行訪問的。
第二個套接字家族為AF_INET,表示”地址家族:Internet“。還有一種地址家族AF_INET6被用于網際協議IPv6尋址。Python 2.5中加入了一種Linux套接字的支持:AF_NETLINK(無連接)套接字家族,讓用戶代碼與內核代碼之間的IPC可以使用標準BSD套接字接口,這種方法更為精巧和安全。
Python只支持AF_UNIX、AF_NETLINK和AF_INET家族。網絡編程關注AF_INET。
如果把套接字比作電話的查看——即通信的最底層結構,那主機與端口就相當于區號和電話號碼的一對組合。一個因特網地址由網絡通信必須的主機與端口組成。
而且另一端一定要有人接聽才行,否則會提示”對不起,您撥打的電話是空號,請查詢后再撥“。同樣你也可能會遇到如”不能連接該服務器、服務器無法響應“等。合法的端口范圍是0~65535,其中小于1024端口號為系統保留端口。
2.面向連接與無連接
面向連接:通信之前一定要建立一條連接,這種通信方式也被成為”虛電路“或”流套接字“。面向連接的通信方式提供了順序的、可靠地、不會重復的數據傳輸,而且也不會被加上數據邊界。這意味著,每發送一份信息,可能會被拆分成多份,每份都會不多不少地正確到達目的地,然后重新按順序拼裝起來,傳給正等待的應用程序。
實現這種連接的主要協議就是傳輸控制協議TCP。要創建TCP套接字就得創建時指定套接字類型為SOCK_STREAM。TCP套接字這個類型表示它作為流套接字的特點。由于這些套接字使用網際協議IP來查找網絡中的主機,所以這樣形成的整個系統,一般會由這兩個協議(TCP和IP)組合描述,即TCP/IP。
無連接:無需建立連接就可以通訊。但此時,數據到達的順序、可靠性及不重復性就無法保障了。數據報會保留數據邊界,這就表示數據是整個發送的,不會像面向連接的協議先拆分成小塊。它就相當于郵政服務一樣,郵件和包裹不一定按照發送順序達到,有的甚至可能根本到達不到。而且網絡中的報文可能會重復發送。
那么這么多缺點,為什么還要使用它呢?由于面向連接套接字要提供一些保證,需要維護虛電路連接,這都是嚴重的額外負擔。數據報沒有這些負擔,所有它會更”便宜“,通常能提供更好的性能,更適合某些場合,如現場直播要求的實時數據講究快等。
實現這種連接的主要協議是用戶數據報協議UDP。要創建UDP套接字就得創建時指定套接字類型為SOCK_DGRAM。這個名字源于datagram(數據報),這些套接字使用網際協議來查找網絡主機,整個系統叫UDP/IP。
3.socket()模塊函數
使用socket模塊的socket()函數來創建套接字。語法如下:
socket(socket_family, socket_type, protocol=0)
其中socket_family不是AF_VNIX就是AF_INET,socket_type可以是SOCK_STREAM或者SOCK_DGRAM,protocol一般不填,默認值是0。
創建一個TCP/IP套接字的語法如下:
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
同樣創建一個UDP/IP套接字的語法如下:
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
由于socket模塊中有太多屬性,所以使用"from socket import *"語句,把socket模塊里面的所有屬性都帶到命名空間中,大幅縮短代碼。調用如下:
tcpSock = socket(AF_INET, SOCK_STREAM)
4.套接字對象方法
下面是最常用的套接字對象方法:
服務器端套接字函數
客戶端套接字函數
公共用途的套接字函數
面模塊的套接字函數
面文件的接字函數
提示:在運行網絡應用程序時,如果能夠使用在不同的電腦上運行服務器和客戶端最好不過,它能讓你更好理解通信過程,而更多的是方位localhost或127.0.0.1.
三. TCP通信實例
1.服務器 tcpSerSock.py
核心操作如下:
ss = socket() ? ? ? ? ? ? ? ?# 創建服務器套接字
ss.bind() ? ? ? ? ? ? ? ?# 地址綁定到套接字上
ss.listen() ? ? ? ? ? ? ? ? ? ? ?# 監聽連接
inf_loop: ? ? ? ? ? ? ? ? ? ? ? # 服務器無限循環
cs = ss.accept() ? ? ? # 接受客戶端連接 阻塞式:程序連接之前處于掛起狀態
comm_loop: ? ? ? ? ? ? ? ? # 通信循環
cs.recv()/cs.send() ? # 對話 接受與發送數據
cs.close() ? ? ? ? ? ? ? ? ? ? ?# 關閉客戶端套接字
ss.close() ? ? ? ? ? ? ? ? ? ? ?# 關閉服務器套接字 (可選)
# -*- coding: utf-8 -*- from socket import * from time import ctime HOST = 'localhost' #主機名 PORT = 21567 #端口號 BUFSIZE = 1024 #緩沖區大小1K ADDR = (HOST,PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) #綁定地址到套接字 tcpSerSock.listen(5) #監聽 最多同時5個連接進來 while True: #無限循環等待連接到來 try: print 'Waiting for connection ....' tcpCliSock, addr = tcpSerSock.accept() #被動接受客戶端連接 print u'Connected client from : ', addr while True: data = tcpCliSock.recv(BUFSIZE) #接受數據 if not data: break else: print 'Client: ',data tcpCliSock.send('[%s] %s' %(ctime(),data)) #時間戳 except Exception,e: print 'Error: ',e tcpSerSock.close() #關閉服務器
2.客戶端 tcpCliSock.py
核心操作如下:
cs = socket() ? ? ? ? ? ? ? ??# 創建客戶端套接字
cs.connect() ? ? ? ? ? ? ? ? ?# 嘗試連接服務器
comm_loop: ? ? ? ? ? ? ? ? # 通訊循環
cs.send()/cs.recv() ? ?# 對話 發送接受數據
cs.close() ? ? ? ? ? ? ? ? ? ? ? # 關閉客戶端套接字
# -*- coding: utf-8 -*- from socket import * HOST = 'localhost' #主機名 PORT = 21567 #端口號 與服務器一致 BUFSIZE = 1024 #緩沖區大小1K ADDR = (HOST,PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) #連接服務器 while True: #無限循環等待連接到來 try: data = raw_input('>') if not data: break tcpCliSock.send(data) #發送數據 data = tcpCliSock.recv(BUFSIZE) #接受數據 if not data: break print 'Server: ', data except Exception,e: print 'Error: ',e tcpCliSock.close() #關閉客戶端
3.運行結果及注意
由于服務器被動地無限循環等待連接,所以需要先運行服務器,再開客戶端。又因為我的Python總會無法響應,所以采用cmd運行服務器Server程序,Python IDLE運行客戶端進行通信。運行結果如下圖所示:
如果出現錯誤[Error] Bad file descriptor表示服務器關閉客戶端連接了,刪除即可。
建議:創建線程來處理客戶端請求。SocketServer模塊是一個基于socket模塊的高級別的套接字通信模塊,支持新的線程或進處理戶端請求。同時建議在退出和調用服務器close()函數時使用try-except語句。
四. UDP通信實例
1.服務器 udpSerSock.py
核心操作如下:
ss = socket() ? ? ? ? ? ? ? ?# 創建服務器套接字
ss.bind() ? ? ? ? ? ? ? ?# 綁定服務器套接字
inf_loop: ? ? ? ? ? ? ? ? ? ? ??# 服務器無限循環
cs = ss.recvfrom()/ss.sendto()
# 對話 接受與發送數據
ss.close() ? ? ? ? ? ? ? ? ? ? ?# 關閉服務器套接字
# -*- coding: utf-8 -*- from socket import * from time import ctime HOST = '' #主機名 PORT = 21567 #端口號 BUFSIZE = 1024 #緩沖區大小1K ADDR = (HOST,PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) udpSerSock.bind(ADDR) #綁定地址到套接字 while True: #無限循環等待連接到來 try: print 'Waiting for message ....' data, addr = udpSerSock.recvfrom(BUFSIZE) #接受UDP print 'Get client msg is: ', data udpSerSock.sendto('[%s] %s' %(ctime(),data), addr) #發送UDP print 'Received from and returned to: ',addr except Exception,e: print 'Error: ',e udpSerSock.close() #關閉服務器
2.客戶端?udpCliSock.py
核心操作如下:
cs = socket() ? ? ? ? ? ? ? ? ? ? ? ? ? ?# 創建客戶端套接字
inf_loop: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?# 服務器無限循環
cs.sendto()/cs.recvfrom()? ?# 對話 接受與發送數據
cs.close() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??# 關閉客戶端套接字
# -*- coding: utf-8 -*- from socket import * HOST = 'localhost' #主機名 PORT = 21567 #端口號 與服務器一致 BUFSIZE = 1024 #緩沖區大小1K ADDR = (HOST,PORT) udpCliSock = socket(AF_INET, SOCK_DGRAM) while True: #無限循環等待連接到來 try: data = raw_input('>') if not data: break udpCliSock.sendto(data, ADDR) #發送數據 data,ADDR = udpCliSock.recvfrom(BUFSIZE) #接受數據 if not data: break print 'Server : ', data except Exception,e: print 'Error: ',e udpCliSock.close() #關閉客戶端
3.運行結果及注意
UDP服務器不是面向連接的,所以不需要設置什么東西,直接等待連接就好。同時由于數據報套接字是無連接的,所以無法把客戶端連接交給另外的套接字進行后續的通訊,這些服務器只是接受消息,需要的話加時間錯后返回一個收到的結果給客戶端。
UDP客戶端與TCP客戶端唯一區別就是不用去UDP服務器建立連接,而是直接把消息發送出去,然后等待服務器回復即可。
運行結果如下圖所示:白色為客戶端輸入消息,黑色為服務器收到消息并回復。當Client輸入"Hello, I am Client"時,服務器顯示該消息并返回時間戳和收到的信息給客戶端。
總結
后面大家自己可以閱讀下SocketServer模塊,它是標準庫中一個高級別的模塊,用于簡化實現網絡客戶端和服務器所需的大量樣板代碼。該模塊中已經實現了一些可供使用的類直接調用幾塊。
Twisted框架是一個完全事件驅動的網絡框架。它允許你使用和開發完全異步的網絡應用程序和協議。
這些東西我更傾向于分享原理和底層的一些東西吧!同時最近考到的筆試題包括:TCP和UDP的區別、socket其中的參數含義、TCP三次握手及傳遞的參數、寫個socket通訊偽代碼。
總之,希望文章對你有所幫助~
2014年分享的文章,原文地址:https://blog.csdn.net/Eastmount/article/details/39854689
希望能與大家一起在華為云社區共同進步
(By:Eastmount 2021-07-21 夜于武漢)
Python Socket編程 TCP/IP 網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。