python ==》 網絡編程
一、服務端和客戶端
BS架構 (騰訊通軟件:server+client)
CS架構 (web網站)
C/S架構與socket的關系:
我們學習socket就是為了完成C/S架構的開發
二、OSI七層模型
互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層
學習socket一定要先學習互聯網協議:
1.首先:本節課程的目標就是教會你如何基於socket編程,來開發一款自己的C/S架構軟件
2.其次:C/S架構的軟件(軟件屬於應用層)是基於網絡進行通信的
3.然後:網絡的核心即一堆協議,協議即標準,你想開發一款基於網絡通信的軟件,就必須遵循這些標準。
4.最後:就讓我們從這些標準開始研究,開啟我們的socket編程之旅
socket:就是位於 應用層和傳輸層 之間。socket幫我們封裝了一系列協議,統一標準。
三、socket是什麽?
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標準的。
四、套接字發展史及分類
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基於文件型的和基於網絡型的。
1、基於文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
2、基於網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麽是只用於某個平臺,要麽就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我麽只使用AF_INET)
五、套接字工作流程
生活中的場景,你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結束,掛斷電話結束此次交談。
生活中的場景就解釋了這工作原理,也許TCP/IP協議族就是誕生於生活中,這也不一定。
socket例子:
1.服務端與客戶端的正常通信。
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) print(‘really ==== go!!‘) conn,client_addr=phone.accept() print(conn,client_addr) data=conn.recv(1024) conn.send(data.upper()) print(‘client data:<%s>‘%data) conn.close() phone.close()服務端
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect ((‘127.0.0.1‘,8080)) phone.send(‘hello‘.encode(‘utf-8‘)) data1 = phone.recv(1024) print(‘server back res:<%s>‘%data1) phone.close()客戶端
首先: 服務端 先開始運行,等待接收, 之後,客戶端運行,向服務端發送信息。 結果如下: 服務端: really ==== go!! <socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53789)> (‘127.0.0.1‘, 53789) client data:<b‘hello‘> 客戶端: server back res:<b‘HELLO‘> 客戶端發了 hello 給服務端, 服務端收到信息,做了 大寫化處理,返回給客戶端。
2.服務端與客戶端的正常通信。socket通信循環
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) print(‘really ==== go!!‘) conn,client_addr=phone.accept() print(conn,client_addr) while True: #通信循環 data=conn.recv(1024) # print(‘server has recv‘) conn.send(data.upper()) print(‘client data:<%s>‘%data) conn.close() phone.close()服務端
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect ((‘127.0.0.1‘,8080)) while True: cmd = input(‘>>:‘).strip() if not cmd:continue #如果cmd為空,繼續發 phone.send(cmd.encode(‘utf-8‘)) print(‘====> has send‘) data = phone.recv(1024) print(‘server back res:<%s>‘%data) phone.close()客戶端
首先: 這裏比上一個例子,優化了,這裏設置了input,可以自己輸入。 添加了個循環,當客戶端輸入為空,不在報錯,而是需要繼續輸入。 結果如下: 服務端: really ==== go!! <socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53865)> (‘127.0.0.1‘, 53865) client data:<b‘1‘> client data:<b‘2‘> client data:<b‘3‘> client data:<b‘a‘> client data:<b‘b‘> client data:<b‘c‘> 客戶端: >>:1 ====> has send server back res:<b‘1‘> >>:2 ====> has send server back res:<b‘2‘> >>:3 ====> has send server back res:<b‘3‘> >>:a ====> has send server back res:<b‘A‘> >>:b ====> has send server back res:<b‘B‘> >>:c ====> has send server back res:<b‘C‘> 註意: 這裏服務端在接到客戶端的額信息是,只做了加大化吃力,所以把abc處理後為ABC返回給客戶端。
3.服務端與客戶端的正常通信。socket鏈接循環。
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) print(‘really ==== go!!‘) while True: #鏈接循環 conn,client_addr=phone.accept() print(conn,client_addr) while True: #通信循環 try: data=conn.recv(1024) # print(‘server has recv‘) conn.send(data.upper()) print(‘client data:<%s>‘%data) except Exception: break conn.close() phone.close()服務端
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect ((‘127.0.0.1‘,8080)) while True: cmd = input(‘>>:‘).strip() if not cmd:continue #如果cmd為空,繼續發 phone.send(cmd.encode(‘utf-8‘)) data = phone.recv(1024) print(‘server back res:<%s>‘%data) phone.close()客戶端1
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect ((‘127.0.0.1‘,8080)) while True: cmd = input(‘>>:‘).strip() if not cmd:continue #如果cmd為空,繼續發 phone.send(cmd.encode(‘utf-8‘)) print(‘====> has send‘) data = phone.recv(1024) print(‘server back res:<%s>‘%data) phone.close()客戶端2
首先,這裏加的鏈接循環是為了防止,當我們有多個客戶端時, 要關閉其中一個,而不導致整個程序出錯。 在沒做鏈接循環前,當我們關閉了其中一個客戶端,服務端那裏是不能在運行的。 輸出結果: 服務端: really ==== go!! <socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53898)> (‘127.0.0.1‘, 53898) client data:<b‘1‘> client data:<b‘2‘> client data:<b‘a‘> client data:<b‘b‘> client data:<b‘c‘> <socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53911)> (‘127.0.0.1‘, 53911) <socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 53911)> (‘127.0.0.1‘, 53911) client data:<b‘a‘> client data:<b‘s‘> client data:<b‘d‘> 客戶端1: >>:1 server back res:<b‘1‘> >>:2 server back res:<b‘2‘> >>:3 server back res:<b‘3‘> >>:a server back res:<b‘A‘> >>:b server back res:<b‘B‘> >>:c server back res:<b‘C‘> 客戶端2: >>:a ====> has send server back res:<b‘A‘> >>:s ====> has send server back res:<b‘S‘> >>:d ====> has send server back res:<b‘D‘>
4.socket模擬ssh遠程執行。
import subprocess import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind((‘127.0.0.1‘,8081)) phone.listen(5) print(‘really ==== go!!‘) while True: #鏈接循環 conn,client_addr=phone.accept() print(conn,client_addr) while True: #通信循環 try: cmd=conn.recv(1080) if not cmd: break #針對linux #執行cmd命令,拿到cmd的結果,結果應該是bytes類型 #。。。 #發送命令結果 res = subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, stdout=subprocess.PIPE, # 正確 stderr=subprocess.PIPE # 錯誤 ) stdout = res.stdout.read() stderr = res.stderr.read() conn.send(stdout+stderr) except Exception: break conn.close() phone.close()服務端
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect ((‘127.0.0.1‘,8081)) while True: cmd = input(‘>>:‘).strip() if not cmd:continue #如果cmd為空,繼續發 phone.send(cmd.encode(‘utf-8‘)) cmd_res = phone.recv(1080) print(cmd_res.decode(‘gbk‘)) phone.close()客戶端
說明: 1.客戶端遠程執行服務端。 2.登錄的是windows系統,用的是‘gbk’ 編碼。 服務端: really ==== go!! <socket.socket fd=400, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8081), raddr=(‘127.0.0.1‘, 53955)> (‘127.0.0.1‘, 53955) 客戶端: >>:dir 驅動器 E 中的卷沒有標簽。 卷的序列號是 0001-0682 E:\zbk\work_\work_8.21 socket模擬ssh遠程執行 的目錄 2017/08/21 19:25 <DIR> . 2017/08/21 19:25 <DIR> .. 2017/08/21 19:25 333 客戶端1.py 2017/08/21 16:29 367 客戶端2.py 2017/08/21 19:25 966 服務端2.py 2017/08/21 19:05 413 模塊subprocess.py 4 個文件 2,079 字節 2 個目錄 266,249,355,264 可用字節 >>:ipconfig /all Windows IP 配置 主機名 . . . . . . . . . . . . . : DESKTOP-0QR7V9H 主 DNS 後綴 . . . . . . . . . . . : 節點類型 . . . . . . . . . . . . : 混合 IP 路由已啟用 . . . . . . . . . . : 否 WINS 代理已啟用 . . . . . . . . . : 否 以太網適配器 以太網: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開連接 連接特定的 DNS 後綴 . . . . . . . : 描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller 物理地址. . . . . . . . . . . . . : 80-FA-5B-3C-8F-54 DHCP 已啟用 . . . . . . . . . . . : 是 自動配置已啟用. . . . . . . . . . : 是 無線局域網適配器 本地連接* 1: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開連接 連接特定的 DNS 後綴 . . . . . . . : 描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter 物理地址. . . . . . . . . . . . . : 70-1C-E7-32-BC-D5 DHCP 已啟用 . . . . . . . . . . . : 是 自動配置已啟用. . . . . . . . . . : 是 無線局域網適配器 WLAN: 連接特定的 DNS 後綴 . . . . . . . : 描述. . . . . . . . . . . . . . . : Intel(R) Dual Band Wireless- >>:輸出結果
5.socket 解決粘包問題。
import struct import subprocess from socket import * phone = socket(AF_INET,SOCK_STREAM) phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) print(‘ready go !!‘) while True: conn,client.addr=phone.accept() print(conn,client_addr) while True: try: cmd = conn.recv(1024) if not cmd : break res = subprocess.Popen(cmd.decode(‘utf-8‘),shell = True, stdout = stdout.subprocess.PIPE, stderr = stderr.subprocess.PIPE,) stdout = res.stdout.read() stderr = res.stderr.read() header = struct.pack(‘i‘,len(stdout)+len(stderr)) conn.send(header) conn.send(stdout) conn.send(stderr) except Exception: break conn.close() phone.close()服務端
import struct from socket import * phone = socket(AF_INET,SOCK_STREAM) phone.connect((‘127.0.0.1‘,8080)) while True: cmd = input(‘>>:‘).strip() if not cmd : continue phone.sent(cmd.encode(‘utf-8‘)) header_struct = phone.recv(4) unpack_res = struct.unpack(‘i‘,header_struct) total_size = unpack_res[0] total_data = b‘‘ recv_size = 0 while recv_size < total_size: recv_data = phone.recv(1024) recv_size += len(recv_data) total_data += recv_data print(total_data.decode(‘gbk‘)) phone.close()客戶端
python ==》 網絡編程