Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。python
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。linux
socket本質就是套接字,項目通常在ISO七層模型中除了應用層其餘都會牽扯到socket,咱們只要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的,大大加強了開發者的效率!git
方法 | 用途 |
---|---|
s.bind() | 綁定(主機,端口號)到套接字 |
s.listen() | 開始TCP監聽 |
s.accept() | 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來 |
方法 | 用途 |
---|---|
s.connect() | 主動初始化TCP服務器鏈接 |
s.connect_ex() | connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常 |
方法 | 用途 |
---|---|
s.recv() | 接收TCP數據 |
s.send() | 發送TCP數據(send在待發送數據量大於己端緩存區剩餘空間時,數據丟失,不會發完) |
s.sendall() | 發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩餘空間時,數據不丟失,循環調用send直到發完) |
s.close() | 關閉套接字 |
import socket import time server = socket.socket() # 買手機 不傳參數默認用的就是TCP協議 server.bind(('127.0.0.1',8080)) # bind((host,port)) 插電話卡 綁定ip和端口 server.listen(5) # 開機 半鏈接池 conn, addr = server.accept() # 接聽電話 等着別人給你打電話 阻塞 # while True: # time.sleep(1) # # data = conn.recv(1024) # 聽別人說話 接收1024個字節數據 阻塞 # # print(data) for i in range(31): conn.send(b'hello baby~') # 給別人回話 conn.close() # 掛電話 server.close() # 關機
import socket client = socket.socket() # 拿電話 client.connect(('127.0.0.1',8080)) # 撥號 寫的是對方的ip和port for i in range(31): # client.send(b'hello world!') # 對別人說話 data = client.recv(1024) # 聽別人說話 print(data,'000') # client.close() # 掛電話import socket # socket.AF #一、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print(phone) #二、撥電話 phone.connect(('127.0.0.1', 8081)) # 指定服務端ip和端口 #三、通訊:發\收消息 phone.send('hello'.encode('utf-8')) # phone.send(bytes('hello',encoding='utf-8')) data = phone.recv(1024) print(data) # import time # time.sleep(500) #四、關閉 phone.close()
import socket """ 服務端 固定的ip和port 24小時不間斷提供服務 """ server = socket.socket() # 生成一個對象 server.bind(('127.0.0.1',8080)) # 綁定ip和port server.listen(5) # 半鏈接池 while True: conn, addr = server.accept() # 等到別人來 conn就相似因而雙向通道 print(addr) # ('127.0.0.1', 51323) 客戶端的地址 while True: try: data = conn.recv(1024) print(data) # b'' 針對mac與linux 客戶端異常退出以後 服務端不會報錯 只會一直收b'' if len(data) == 0:break conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close()
import socket client = socket.socket() # 拿電話 client.connect(('127.0.0.1',8080)) # 撥號 寫的是對方的ip和port for i in range(31): # client.send(b'hello world!') # 對別人說話 data = client.recv(1024) # 聽別人說話 print(data,'000') # client.close() # 掛電話
在重啓服務端時可能會遇到:編程
這個是因爲你的服務端仍然存在四次揮手的time_wait狀態在佔用地址(若是不懂,請深刻研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高併發狀況下會有大量的time_wait狀態的優化方法)json
# 加入一條socket配置,重用ip和端口 phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))
發現系統存在大量TIME_WAIT狀態的鏈接,經過調整linux內核參數解決, vi /etc/sysctl.conf 編輯文件,加入如下內容: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 而後執行 /sbin/sysctl -p 讓參數生效。 net.ipv4.tcp_syncookies = 1 表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉; net.ipv4.tcp_tw_reuse = 1 表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉; net.ipv4.tcp_tw_recycle = 1 表示開啓TCP鏈接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。 net.ipv4.tcp_fin_timeout 修改系統默認的 TIMEOUT 時間
8.1服務端設計模式
import socket import os import json import struct server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,addr = server.accept() while True: try: header_len = conn.recv(4) # 解析字典報頭 header_len = struct.unpack('i',header_len)[0] # 再接收字典數據 header_dic = conn.recv(header_len) real_dic = json.loads(header_dic.decode('utf-8')) # 獲取數據長度 total_size = real_dic.get('file_size') # 循環接收並寫入文件 recv_size = 0 with open(real_dic.get('file_name'),'wb') as f: while recv_size < total_size: data = conn.recv(1024) f.write(data) recv_size += len(data) print('上傳成功') except ConnectionResetError as e: print(e) break conn.close()
8.2客戶端緩存
import socket import json import os import struct client = socket.socket() client.connect(('127.0.0.1',8080)) while True: # 獲取電影列表 循環展現 MOVIE_DIR = r'D:\python脫產10期視頻\day25\視頻' movie_list = os.listdir(MOVIE_DIR) # print(movie_list) for i,movie in enumerate(movie_list,1): print(i,movie) # 用戶選擇 choice = input('please choice movie to upload>>>:') # 判斷是不是數字 if choice.isdigit(): # 將字符串數字轉爲int choice = int(choice) - 1 # 判斷用戶選擇在不在列表範圍內 if choice in range(0,len(movie_list)): # 獲取到用戶想上傳的文件路徑 path = movie_list[choice] # 拼接文件的絕對路徑 file_path = os.path.join(MOVIE_DIR,path) # 獲取文件大小 file_size = os.path.getsize(file_path) # 定義一個字典 res_d = { 'file_name':'性感荷官在線發牌.mp4', 'file_size':file_size, 'msg':'注意身體,多喝養分快線' } # 序列化字典 json_d = json.dumps(res_d) json_bytes = json_d.encode('utf-8') # 1.先製做字典格式的報頭 header = struct.pack('i',len(json_bytes)) # 2.發送字典的報頭 client.send(header) # 3.再發字典 client.send(json_bytes) # 4.再發文件數據(打開文件循環發送) with open(file_path,'rb') as f: for line in f: client.send(line) else: print('not in range') else: print('must be a number')