day31
網絡編程演變過程
單機架構:不需要聯網,如超級瑪麗、坦克大戰等。
C(client)/S(server)架構:客戶端直接和服務端交互,如QQ、大型網絡游戲等。
B(browser)/S(server)架構:客戶端嫁接在瀏覽器上,瀏覽器和服務器交互,如淘寶、京東等。
客戶端:用戶安裝的軟件。
服務端:統一管理數據庫的主機中的軟件叫做服務器,再后來服務端不只是管理數據外加處理業務邏輯。
服務端特性:固定IP且穩定運行,支持并發。
大白話OSI五層協議(應傳網數物)
物理層:電信號
數據鏈路層:
以太坊協議:將電信號進行分組,每組(數據報/數據幀)由報頭和數據組成。
MAC地址/物理地址:每塊網卡有唯一一個MAC地址,12位16進制數表示(前六位是廠商編號,后六位是流水線號),發送者地址和接收者地址就是MAC地址,可確定唯一計算機。
廣播:同一局域網通信,會產生廣播風暴。
網絡層:
IP地址:目前用IPv4由32位二進制表示,從0.0.0.0到255.255.255.255范圍內。
子網掩碼:判斷兩個IP是否處于同一網段。
ARP協議:廣播的方式發送數據包,獲取目標主機的MAC地址。
MAC地址學習:MAC地址和IP地址的映射表,第一次接收就會在IP/MAC映射表中添加一條數據{'172.16.10.1':ddsadfgegsdgsdg}
傳輸層:
端口號:端口范圍0-65535,0-1023為系統占用端口,找到獨一無二的應用程序。
半連接池:限制連接的最大數。
DOS攻擊——拒絕服務攻擊和DDoS攻擊——分布式拒絕服務攻擊。
UDP協議和TCP協議(后面詳細講)
應用層:應用程序均工作于應用層,數據交互。
上網流程分析
在瀏覽器輸入域名,隱藏端口。
通過dns服務器將域名解析成IP地址。
向IP+端口號這個地址發送請求,就會訪問到域名所在的服務器。
TCP協議和UDP協議相關
tcp協議(流式協議):通信需要建立連接,通信結束要釋放連接,不允許發空數據,所以是可靠傳輸,但增加確認、流量等開銷,對應的應用層協議如HTTP、FTP等。
基于TCP協議的套接字編程:
import socket
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象,默認tcp協議
server.bind(('127.0.0.1', 8001)) # 綁定IP和端口號
server.listen(5) # 半連接池大小
while True: # 連接循環
conn, addr = server.accept()
while True: # 通信循環
try:
data = conn.recv(1024) # 接收客戶端數據
print(data)
conn.send(data.upper()) # 發送數據
except Exception:
break
客戶端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象
client.connect(('127.0.0.1', 8001)) # 和客戶端建立連接
while True: # 通信循環
msg = input('>>>:')
if not msg: continue # 發送數據不能為空
client.send(bytes(msg, encoding='utf-8')) # 以bytes格式發送數據
data = client.recv(1024) # 接受服務端數據
print(data)
udp協議(數據報協議):通信不需要建立連接和確認,允許發空數據,所以是不可靠傳輸,省去很多開銷,對應的應用層協議如DNS、NFS等。
基于UDP協議的套接字編程:
服務端.py
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # 建立socket對象
server.bind(('127.0.0.1', 8000)) # 綁定IP和端口號
while True: # 通信循環
data, addr = server.recvfrom(1024) # 接受客戶端信息
print(data)
server.sendto(data.upper(), addr) # 發送服務端信息
客戶端.py
import socket
client = socket.socket(type=socket.SOCK_DGRAM) # 建立socket對象
while True: # 通信循環
msg = input('>>>:')
client.sendto(bytes(msg, encoding='utf-8'), ('127.0.0.1', 8000)) # 以bytes格式發送數據
data = client.recvfrom(1024) # 接受服務端數據
print(data)
套接字(socket):在應用層和傳輸層之間的一個抽象層,把TCP/IP層復雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信,即IP地址+端口號組成,但是套接字所寫的軟件均屬于應用層。
模擬ssh遠程執行命令
在客戶端模擬ssh發送指令,服務端通過subprocess執行該命令,然后返回命令的結果。
服務端.py
import socket
import subprocess
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象,默認tcp協議
server.bind(('127.0.0.1', 8001)) # 綁定IP和端口號
server.listen(5) # 半連接池大小
while True: # 連接循環
conn, addr = server.accept()
while True: # 通信循環
try:
cmd = conn.recv(1024) # 接收客戶端數據
print(cmd)
# 執行cmd命令,然后把執行結果保存到管道里
pipeline = subprocess.Popen(str(cmd, encoding='utf-8'), # 輸入的字符串形式的cmd命令
shell=True, # 通過shell來運行
stdout=subprocess.PIPE, # 把正確輸出放入管道,以便打印
stderr=subprocess.PIPE) # 把錯誤輸出放入管道,以便打印
stdout = pipeline.stdout.read() # 打印正確輸出
stderr = pipeline.stderr.read() # 打印錯誤輸出
conn.send(stdout) # 發送數據
conn.send(stderr) # 發送數據
except Exception:
break
客戶端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象
client.connect(('127.0.0.1', 8001)) # 和客戶端建立連接
while True: # 通信循環
cmd = input('>>>:')
if not cmd: continue # 發送數據不能為空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式發送數據
data = client.recv(1024) # 接受服務端數據
print(str(data, encoding='gbk'))
輸入dir命令,由于服務端發送字節少于1024字節,客戶端可以接受。
輸入tasklist命令,由于服務端發送字節多于1024字節,客戶端只接受部分數據,并且當你再次輸入dir命令的時候,客戶端會接收dir命令的結果,但是會打印上一次的剩余未發送完的數據,這就是粘包問題。
粘包問題
形成粘包問題的原因:兩個數據非常小且間隔時間短;數據太大,一次取不完,下一次還會取這個數據。
解決粘包問題的方案:為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然后一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然后再取真實數據。
服務端.py
import socket
import subprocess
import json
import struct
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象,默認tcp協議
server.bind(('127.0.0.1', 8001)) # 綁定IP和端口號
server.listen(5) # 半連接池大小
while True: # 連接循環
conn, addr = server.accept()
while True: # 通信循環
cmd = conn.recv(1024) # 接收客戶端數據
print(cmd)
# 執行cmd命令,然后把執行結果保存到管道里
pipeline = subprocess.Popen(str(cmd, encoding='utf-8'), # 輸入的字符串形式的cmd命令
shell=True, # 通過shell來運行
stdout=subprocess.PIPE, # 把正確輸出放入管道,以便打印
stderr=subprocess.PIPE) # 把錯誤輸出放入管道,以便打印
stdout = pipeline.stdout.read() # 打印正確輸出
stderr = pipeline.stderr.read() # 打印錯誤輸出
# 制作報頭
header_dic = {
'filename': 'a.txt',
'total_size': len(stdout) + len(stderr),
'hash': 'asdfpoi79032'
}
# 將報頭序列化成字符串報頭
header_json = json.dumps(header_dic)
# 將字符串報頭轉換成bytes格式
header_bytes = bytes(header_json, encoding='utf-8')
# 1.先將報頭的長度len(header_bytes)打包成4個bytes,然后發送
conn.send(struct.pack('i', len(header_bytes)))
# 2.再發送報頭
conn.send(header_bytes)
# 3.最后發送真實數據
conn.send(stdout)
conn.send(stderr)
conn.close()
server.close()
客戶端.py
import socket
import struct
import json
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象
client.connect(('127.0.0.1', 8001)) # 和客戶端建立連接
while True: # 通信循環
cmd = input('>>>:')
if not cmd: continue # 發送數據不能為空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式發送數據
# 1.先收4個字節,這4個字節包含報頭的長度
header_len = struct.unpack('i', client.recv(4))[0]
# 2.再接收報頭
header_bytes = client.recv(header_len)
# 3.從包中解析出想要的數據
header_json = str(header_bytes, encoding='utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
# 4.最后接收真實的數據
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(str(res, encoding='gbk'))
client.close()
基于socketserver實現并發的socket套接字編程
讓服務端同時和多個客戶端進行連接。
服務端.py
import socketserver
自己定義一個類,必須繼承BaseRequestHandler
class MyTcp(socketserver.BaseRequestHandler):
# 必須重寫handle方法
def handle(self):
try:
while True: # 通信循環
# 給客戶端回消息
# conn對象就是request
data = self.request.recv(1024) # 接收數據
print(self)#<main.MyTcp object at 0x0000015941D1ECC0>
print(self.client_address)#('127.0.0.1', 11515)
print(data)
if len(data) == 0:
return
self.request.send(data.upper()) # 發送數據
except Exception:
pass
if name == 'main':
# 實例化得到一個tcp連接的對象,Threading是說,只要來了請求,自動的開線程來處理連接跟交互數據
# 第一個參數是綁定的地址,第二個參數傳一個類
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), MyTcp)
# 一直在監聽,只要來一個請求,就起一個線程做交互
server.serve_forever()
客戶端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket對象
client.connect(('127.0.0.1', 8009)) # 和客戶端建立連接
while True: # 通信循環
cmd = input('>>>:')
if not cmd: continue # 發送數據不能為空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式發送數據
data = client.recv(1024) # 接受服務端數據
print(str(data, encoding='gbk'))
轉載于:https://www.cnblogs.com/bjlxxbj/p/11515047.html
總結
- 上一篇: 从球衣了解“红魔”曼联历史
- 下一篇: 详细介绍XTF文件数据格式