BS架構 (騰訊通軟件:server+client)python
CS架構 (web網站)linux
C/S架構與socket的關係:web
咱們學習socket就是爲了完成C/S架構的開發shell
學習socket必定要先學習互聯網協議:編程
1.首先:本節課程的目標就是教會你如何基於socket編程,來開發一款本身的C/S架構軟件json
2.其次:C/S架構的軟件(軟件屬於應用層)是基於網絡進行通訊的windows
3.而後:網絡的核心即一堆協議,協議即標準,你想開發一款基於網絡通訊的軟件,就必須遵循這些標準。設計模式
4.最後:就讓咱們從這些標準開始研究,開啓咱們的socket編程之旅網絡
socket:就是位於 應用層和傳輸層 之間。socket幫咱們封裝了一系列協議,統一標準。架構
Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。
套接字家族的名字:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊
套接字家族的名字: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()
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()
首先,這裏加的連接循環是爲了防止,當咱們有多個客戶端時, 要關閉其中一個,而不致使整個程序出錯。 在沒作連接循環前,當咱們關閉了其中一個客戶端,服務端那裏是不能在運行的。 輸出結果: 服務端: 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.send(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()
6.json 解決粘包問題。
import socket import subprocess import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8082)) #綁定手機卡 phone.listen(5) #開機 print('starting...') while True: #連接循環 conn,client_addr=phone.accept() #等電話 (連接,客戶的的ip和端口組成的元組) print('-------->',conn,client_addr) #收,發消息 while True:#通訊循環 try: cmd=conn.recv(1024) 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() #製做報頭 header_dic = { 'total_size': len(stdout)+len(stderr), 'filename': None, 'md5': None} header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8') #發送階段 #先發報頭長度 conn.send(struct.pack('i',len(header_bytes))) #再發報頭 conn.send(header_bytes) #最後發送命令的結果 conn.send(stdout) conn.send(stderr) except Exception: break conn.close() #掛電話 phone.close() #關機
import socket import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 phone.connect(('127.0.0.1',8082)) #綁定手機卡 #發,收消息 while True: cmd=input('>>: ').strip() if not cmd:continue phone.send(cmd.encode('utf-8')) #先收報頭的長度 header_len=struct.unpack('i',phone.recv(4))[0] #再收報頭 header_bytes=phone.recv(header_len) header_json=header_bytes.decode('utf-8') header_dic=json.loads(header_json) total_size=header_dic['total_size'] #最後收數據 recv_size=0 #10241=10240+1 total_data=b'' 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()