C/S架構 C:clint(客戶端軟件)
S:server(服務端軟件)html
python程序員是應用開發程序員,開發的軟件都是應用軟件,應用軟件必須運行於操做系統之上,操做系統則運行於硬件上,應用軟件不能直接操做硬件,應用軟件對硬件的操做必須調用操做系統接口,由操做系統操做硬件。python
客戶端軟件基於網絡發送消息給服務端軟件流程:
一、客戶端軟件產生數據,存放於客戶端軟件的內存中,而後調用接口將本身內存中的數據發送/拷貝給操做系統內存
二、客戶端操做系統收到數據後,按照客戶端軟件指定的協議、調用網卡發送數據
三、網絡傳輸數據
四、服務端軟件調用系統接口,想要將數據從操做系統內存拷貝到本身的內存中
五、服務端操做系統受到4的指令後,使用與客戶端相同的協議,從網卡接收到數據,而後拷貝給服務端軟件linux
假設我如今要寫一個程序,給另外一臺計算機發數據,必須經過tcp/ip協議 ,但具體的實現過程是什麼呢?我應該怎麼操做才能把數據封裝成tcp/ip的包,又執行什麼指令才能把數據發到對端機器上呢?程序員
簡而言之,socket這個東東干的事情,就是幫你把tcp/ip協議層的各類數據封裝啦、數據發送、接收等經過代碼已經給你封裝好了,你只須要調用幾行代碼,就能夠給別的機器發消息了。算法
計算機之間的通訊首先要有物理連接介質,好比網線,交換機,路由器等網絡設備。 shell
通訊的線路建好以後,只是物理層面有了能夠承載數據的介質,要想通訊,還須要咱們按照某種規則組織咱們的數據,這樣對方在接收到數據後就能夠按照相同的規則去解析出數據。編程
按照功能不一樣,人們將互聯網協議分爲osi七層或tcp/ip五層或tcp/ip四層。json
http://www.cnblogs.com/linhaifeng/articles/5937962.html#_label5windows
TCP/IP五層:
一、應用層:規定應用程序的數據格式。
二、傳輸層:創建端口到端口的通訊設計模式
""" 端口範圍0-65535,0-1023爲系統佔用端口 傳輸層有兩種協議,TCP和UDP: tcp協議: 可靠傳輸,TCP數據包沒有長度限制,理論上能夠無限長,可是爲了保證網絡的效率,一般 TCP數據包的長度不會超過IP數據包的長度,以確保單個TCP數據包沒必要再分割。 udp協議: 不可靠傳輸,」報頭」部分一共只有8個字節,總長度不超過65,535字節,正好放進一個IP數據包。 """
三、網絡層:引入一套新的地址用來區分不一樣的廣播域/子網,這套地址即網絡地址
""" ip協議: 一、規定網絡地址的協議叫ip協議,它定義的地址稱之爲ip地址,普遍採用的v4版本即ipv4,它規定網絡地址由32位2進製表示 二、範圍0.0.0.0-255.255.255.255 三、一個ip地址一般寫成四段十進制數,例:172.16.10.1 子網掩碼: 所謂」子網掩碼」,就是表示子網絡特徵的一個參數。它在形式上等同於IP地址,也是一個32位二進制數字, 它的網絡部分所有爲1,主機部分所有爲0。好比,IP地址172.16.10.1,若是已知網絡部分是前24位, 主機部分是後8位,那麼子網絡掩碼就是11111111.11111111.11111111.00000000,寫成十進制就是255.255.255.0。 """
四、數據鏈路層:定義了電信號的分組方式,分組方式後來造成了統一的標準,即以太網協議ethernet
""" ethernet規定: 一、一組電信號構成一個數據包,叫作‘幀’ 二、每一數據幀分紅:報頭head和數據data兩部分 head包含:(固定18個字節) 發送者/源地址,6個字節 接收者/目標地址,6個字節 數據類型,6個字節 data包含:(最短46字節,最長1500字節) 數據包的具體內容 head長度+data長度=最短64字節,最長1518字節,超過最大限制就分片發送 mac地址:(標識子網內,一臺機器的位置) ethernet規定接入internet的設備都必須具有網卡,發送端和接收端的地址即是指網卡的地址,即mac地址 每塊網卡出廠時都被燒製上一個世界惟一的mac地址,長度爲48位2進制,一般由12位16進制數表示(前六位是廠商編號,後六位是流水線號) 廣播: 有了mac地址,同一網絡內的兩臺主機就能夠通訊了(一臺主機經過arp協議獲取另一臺主機的mac地址) ethernet採用最原始的方式,廣播的方式進行通訊,即計算機通訊基本靠吼 ## 以太網協議基於MAC地址的廣播,只能在局域網吼。## """
五、物理層:主要是基於電器特性發送高低電壓(電信號),高電壓對應數字1,低電壓對應數字0
OSI/RM模型(Open System Interconnection / Reference Model)的設計目的是成爲一個全部計算機廠商都能實現的開放網絡模型,來克服使用衆多私有網絡模型所帶來的困難和低效性。
一、應用層 二、表示層 三、會話層
四、傳輸層
五、網絡層
六、數據鏈路層
七、物理層
Socket是應用層與TCP/IP協議族(傳輸層)通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。
也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啓的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序。
而程序的pid是同一臺機器上不一樣進程或者線程的標識。
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。
# socket實例化方式 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) # proto=0 可忽略,特殊用途 # fileno=None 可忽略,特殊用途
套接字家族名:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊。
套接字家族名:AF_INET
全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET
socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp socket.SOCK_RAW #原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。 socket.SOCK_RDM #是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。 socket.SOCK_SEQPACKET #廢棄了
套接字工做流程圖以下:
流程解析:服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。
import socket socket.socket(socket_family,socket_type,protocal=0) socket_family 能夠是 AF_UNIX 或 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 module import *'語句。使用 'from socket import *',咱們就把 socket 模塊裏的全部屬性都帶到咱們的命名空間裏了,這樣能 大幅減短咱們的代碼。 例如tcpSock = socket(AF_INET, SOCK_STREAM)
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.recvfrom() 接收UDP數據
s.sendto() 發送UDP數據
s.getpeername() 鏈接到當前套接字的遠端的地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回指定套接字的參數
s.setsockopt() 設置指定套接字的參數
s.close() 關閉套接字
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操做的超時時間
s.gettimeout() 獲得阻塞套接字操做的超時時間
s.fileno() 套接字的文件描述符
s.makefile() 建立一個與該套接字相關的文件
1:用打電話的流程快速描述socket通訊 2:服務端和客戶端加上基於一次連接的循環通訊 3:客戶端發送空,卡主,證實是從哪一個位置卡的 服務端: from socket import * phone=socket(AF_INET,SOCK_STREAM) phone.bind(('127.0.0.1',8081)) phone.listen(5) conn,addr=phone.accept() while True: data=conn.recv(1024) print('server===>') print(data) conn.send(data.upper()) conn.close() phone.close() 客戶端: from socket import * phone=socket(AF_INET,SOCK_STREAM) phone.connect(('127.0.0.1',8081)) while True: msg=input('>>: ').strip() phone.send(msg.encode('utf-8')) print('client====>') data=phone.recv(1024) print(data) 說明卡的緣由:緩衝區爲空recv就卡住,引出原理圖 4.演示客戶端斷開連接,服務端的狀況,提供解決方法 5.演示服務端不能重複接受連接,而服務器都是正常運行不斷來接受客戶連接的 6:簡單演示udp 服務端 from socket import * phone=socket(AF_INET,SOCK_DGRAM) phone.bind(('127.0.0.1',8082)) while True: msg,addr=phone.recvfrom(1024) phone.sendto(msg.upper(),addr) 客戶端 from socket import * phone=socket(AF_INET,SOCK_DGRAM) while True: msg=input('>>: ') phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082)) msg,addr=phone.recvfrom(1024) print(msg) udp客戶端能夠併發演示 udp客戶端能夠輸入爲空演示,說出recvfrom與recv的區別,暫且不提tcp流和udp報的概念,留到粘包去說
tcp是基於連接的,必須先啓動服務端,而後再啓動客戶端去連接服務端
服務端:
import socket # 一、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 # print(phone) """ <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)> """ # 二、綁定手機卡 phone.bind(('127.0.0.1', 9001)) # 本地迴環地址, 端口0-65535(0-1024歸系統使用) # 三、開機 phone.listen(5) # 最大掛起的連接數 # 四、等電話連接 print('starting...') # res = phone.accept() # 程序卡在這一步 # print(res) # res包含連接對象 """ starting... ===》 (<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 55868)>, ('127.0.0.1', 55868)) """ # 改寫分拆兩個元組元素 conn, client_addr = phone.accept() print(conn) print(client_addr) print('got a new connection from %s' % (client_addr, )) """ <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 55925)> ('127.0.0.1', 65197) got a new connection from ('127.0.0.1', 65197) """ # 五、收發消息 data = conn.recv(1024) # 一、單位是bytes 二、1024表明接收數據的最大數是1024個bytes print('客戶端數據', data) conn.send(data.upper()) # 數據修改成大寫後發送 # 六、掛電話 conn.close() # 七、關機 phone.close() """ 客戶端數據 b'hello' """
客戶端:
import socket # 一、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 #print(phone) # 二、撥號 phone.connect(('127.0.0.1', 8080)) # 服務端先啓動後啓動客戶端 # 三、發、收消息 phone.send('hello'.encode('utf-8')) data = phone.recv(1024) print(data) # 四、關閉 phone.close() """ b'HELLO' """
上面是以打電話爲例實現了一個low版的套接字通訊,可是若是須要屢次發送,須要加上通訊循環。
import socket # 一、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 # 二、綁定手機卡 phone.bind(('127.0.0.1', 8080)) # 本地迴環地址, 端口0-65535(0-1024歸系統使用) # 三、開機 phone.listen(5) # 最大掛起的連接數(TCP協議須要) # 四、等電話連接 print('starting...') conn, client_addr = phone.accept() # accept是創建連接 print(client_addr) # 客戶端一啓動打印 ('127.0.0.1',56641) # 五、收發消息 while True: # 通訊循環 data = conn.recv(1024) # 一、單位是bytes 二、1024表明接收數據的最大數是1024個bytes print('客戶端數據', data) conn.send(data.upper()) # 數據修改成大寫後發送 # 六、掛電話 conn.close() # 七、關機 phone.close() """ starting... ('127.0.0.1', 56641) 客戶端數據 b'hello' 客戶端數據 b'xiugeng' """
import socket # 一、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 #print(phone) # 二、撥號 phone.connect(('127.0.0.1', 8080)) # 服務端先啓動後啓動客戶端 # 三、發、收消息 while True: # 通訊循環 msg = input('>>:').strip() phone.send(msg.encode('utf-8')) data = phone.recv(1024) # 收到服務端大寫以後的字符 print(data) # 四、關閉 phone.close() """ >>:hello b'HELLO' >>:xiugeng b'XIUGENG' """
解決重啓服務端時,服務端仍存在四次揮手的time_wait狀態佔用地址的狀況(深刻研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高併發狀況下會有大量的time_wait狀態的優化方法),沒法重啓服務端,修改代碼以下:
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 解決重啓服務端時,服務端仍存在四次揮手的time_wait狀態佔用地址的狀況: # 方法一:加入一條socket配置,重用ip和端口 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 在bind前,啓動關閉服務端時,系統端口沒有回收,能夠重用端口 # 方法二:調整linux內核參數 """ 在 /etc/sysctl.conf文件中添加: net.ipv4.tcp_syncookies = 1 # 表示開啓SYN Cookies,啓用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 = 30 # 修改系統默認的TIMEOUT時間 執行 /sbin/sysctl -p讓參數生效 """ phone.bind(('127.0.0.1', 8080)) phone.listen(5) # 最大掛起的連接數 print('starting...') conn, client_addr = phone.accept() print(client_addr) while True: # 通訊循環 try: data = conn.recv(1024) # 客戶端發出空消息,服務端沒有收到 if not data:break # # 適用與Linux操做系統 print('客戶端數據', data) conn.send(data.upper()) except ConnectionResetError: # 適用於windiws的操做系統 break conn.close() phone.close()
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 phone.connect(('127.0.0.1', 8080)) # 服務端先啓動後啓動客戶端 while True: # 通訊循環 msg = input('>>:').strip() # msg='' # 修改辦法 if not msg:continue # 輸入空,不發送消息 phone.send(msg.encode('utf-8')) # phone.send(b'') # print('has send') # 定位不能發空的緣故 data = phone.recv(1024) # 服務端沒有回消息,所以收不到消息 print(data.decode('utf-8')) phone.close()
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 時間
添加連接循環,當上一個連接結束後,等待下一個客戶端接入。
import socket """ 須要服務端一直提供服務, """ phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1', 8080)) phone.listen(5) # 最大掛起的連接數 print('starting...') """ 須要服務端一直提供服務,再添加一個循環。服務端的工做主要是:一、創建連接;二、通訊 須要注意的是:服務端能夠一直提供服務,可是不能併發。只有等前一個連接斷了後,下一個客戶端才能連接 """ while True: # 連接循環 # 建立連接 conn, client_addr = phone.accept() print(client_addr) while True: # 通訊循環 try: data = conn.recv(1024) if not data:break print('客戶端數據', data) conn.send(data.upper()) except ConnectionResetError: break conn.close() phone.close()
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基於網絡通信的,基於TCP協議的一個套接字 phone.connect(('127.0.0.1', 8080)) # 服務端先啓動後啓動客戶端 while True: # 通訊循環 msg = input('>>:').strip() # msg='' # 修改辦法 if not msg:continue # 輸入空,不發送消息 phone.send(msg.encode('utf-8')) # phone.send(b'') # print('has send') # 定位不能發空的緣故 data = phone.recv(1024) # 服務端沒有回消息,所以收不到消息 print(data.decode('utf-8')) phone.close()
以前學過os模塊來執行系統命令:
import os cmd = os.popen('df -h') cmd = cmd.read() print(cmd)
subprocess命令執行的結果就是bytes,無需轉變格式就能夠給客戶端、服務端使用。
import subprocess obj = subprocess.Popen('dxxxs', shell=True, stdout=subprocess.PIPE, # stdout:正確結果;管道 stderr=subprocess.PIPE) # stderr:錯誤結果;管道 print(obj) print('stdout 1---->: ', obj.stdout.read().decode('utf-8')) # 正確管道內容 print('stderr 1---->: ', obj.stderr.read().decode('utf-8')) # 錯誤管道內容 """ <subprocess.Popen object at 0x10401ada0> stdout 1---->: stderr 1---->: /bin/sh: dxxxs: command not found """
udp不須要通過3次握手和4次揮手,不須要提早創建連接,直接發送數據便可。
服務端:
# import socket from socket import * # 儘可能少用這種導入方式,會將全部名字加入名稱空間,容易致使重複 # server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 能夠看到須要引用的socket的模塊很是多。改用from socket import *導入 server = socket(AF_INET, SOCK_DGRAM) # SOCK_STREAM指的流式協議,SOCK_DGRAM指得是數據報協議(但凡發數據,就已是完整的數據報) server.bind(('127.0.0.1', 8080)) # server.listen(5) # 掛起的連接數,TCP協議須要,UDP不須要 # while True: # conn, addr = server.accept() # 用來創建連接,UDP不須要 while True: data, client_addr = server.recvfrom(1024) # 收消息 print(data) server.sendto(data.upper(), client_addr) # 發消息,取收消息的地址 server.close()
客戶端:
from socket import * client = socket(AF_INET, SOCK_DGRAM) while True: msg = input('>>: ').strip() client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # 發消息 data, server_addr = client.recvfrom(1024) # 收消息 print(data, server_addr) client.close()
以qq聊天爲例:
import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #買手機 udp_server_sock.bind(ip_port) while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回覆消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) qq_name_dic={ '狗哥alex':('127.0.0.1',8081), '瞎驢':('127.0.0.1',8081), '一棵樹':('127.0.0.1',8081), '武大郎':('127.0.0.1',8081), } while True: qq_name=input('請選擇聊天對象: ').strip() while True: msg=input('請輸入消息,回車發送: ').strip() if msg == 'quit':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) udp_client_socket.close()
import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) qq_name_dic={ '狗哥alex':('127.0.0.1',8081), '瞎驢':('127.0.0.1',8081), '一棵樹':('127.0.0.1',8081), '武大郎':('127.0.0.1',8081), } while True: qq_name=input('請選擇聊天對象: ').strip() while True: msg=input('請輸入消息,回車發送: ').strip() if msg == 'quit':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) udp_client_socket.close()
時間服務器
from socket import * from time import strftime ip_port=('127.0.0.1',9000) bufsize=1024 tcp_server=socket(AF_INET,SOCK_DGRAM) tcp_server.bind(ip_port) while True: msg,addr=tcp_server.recvfrom(bufsize) print('===>',msg) if not msg: time_fmt='%Y-%m-%d %X' else: time_fmt=msg.decode('utf-8') back_msg=strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'),addr) tcp_server.close()
from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize) print(data.decode('utf-8')) tcp_client.close()
模擬ssh遠程執行命令:
注意:
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼
且只能從管道里讀一次結果
注意:命令ls -l ; lllllll ; pwd 的結果是既有正確stdout結果,又有錯誤stderr結果
基於tcp的socket,ssh遠程運行時,會發生粘包的問題。致使下次執行命令時,會輸出上次服務器回傳的沒執行完的內容。
再基於udp製做一個遠程執行命令的程序:
import socket import subprocess ip_port = ('127.0.0.1', 9001) # 元組 bufsize = 1024 udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_server.bind(ip_port) while True: # 收消息 cmd, addr = udp_server.recvfrom(bufsize) print("用戶命令----->", cmd) # 邏輯處理 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdout = res.stdout.read() stderr = res.stderr.read() # 發消息 udp_server.sendto(stderr, addr) udp_server.sendto(stdout, addr) udp_server.close()
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: msg = input('>>: ').strip() client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # 發消息 data, server_addr = client.recvfrom(1024) # 收消息 print(data, server_addr) client.close()
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。
TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。
tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略
udp的recvfrom是阻塞的,一個recvfrom(x)必須對惟一一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠
tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。
兩種狀況下會發生粘包
一、發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據了很小,會合到一塊兒,產生粘包)
import time import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9002)) # 使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。 client.send('hello'.encode('utf-8')) client.send('world'.encode('utf-8'))
import time import socket import subprocess server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9002)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) conn, addr = server.accept() res1 = conn.recv(1024) # 第一次收 print('第一次', res1) res2 = conn.recv(1024) # 第二次收 print('第二次', res2) """ 第一次 b'helloworld' 第二次 b'' """ # 發生了粘包現象
二、接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
import time import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9002)) # 使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。 client.send('hello'.encode('utf-8')) # time.sleep(1) # 休息一秒看是否還粘包 time.sleep(5) client.send('world'.encode('utf-8'))
import time import socket import subprocess server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9002)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) conn, addr = server.accept() # 客戶端間隔五秒發送,服務端第一次僅接收一個字符,服務端發生粘包 res1 = conn.recv(1) # 第一次收 print('第一次', res1) res2 = conn.recv(1024) # 第二次收 print('第二次', res2) """ 第一次 b'h' 第二次 b'ello' """
補充問題一:爲什麼tcp是可靠傳輸,udp是不可靠傳輸
tcp的數據傳輸請參考文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在數據傳輸時,發送端先把數據發送到本身的緩存中,而後協議控制將緩存中的數據發往對端,對端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則從新發送數據,因此tcp是可靠的
而udp發送數據,對端是不會返回確認信息的,所以不可靠。
補充問題二:send(字節流)和recv(1024)及sendall
recv裏指定的1024意思是從緩存裏一次拿出1024個字節的數據
send的字節流是先放入己端緩存,而後由協議控制將緩存內容發往對端,若是待發送的字節流大小大於緩存剩餘空間,那麼數據丟失
sendall就會循環調用send,數據不會丟失
接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。
import time import socket import struct import json client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9003)) while True: """一、發命令""" cmd = input('>>: ').strip() if not cmd:continue client.send(cmd.encode('utf-8')) """二、拿結果""" # 第一步:先收報頭長度 obj= client.recv(4) # 接收四個bytes header_size = struct.unpack('i', obj)[0] # 第二步:再收報頭 header_bytes = client.recv(header_size) # 第三步:從報頭中解析出對真實數據的描述信息(數據長度) header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['total_size'] # 第四步:接收真實的數據 recv_size = 0 recv_data = b'' while recv_size < total_size: res = client.recv(1024) # 最大不能超過操做系統緩存大小,一次收不完,屢次收 recv_data += res recv_size += len(res) # 最後一次收的時候將不是1024,將來若是要查看進度的時候有問題 print(recv_data.decode('utf-8')) client.close()
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import subprocess import struct import json server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9003)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) print('starting...') while True: conn, client_addr = server.accept() print(client_addr) while True: # 通信循環 try: """一、收命令""" cmd = conn.recv(8096) if not cmd:break """二、拿到結果""" obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() """三、命令結果返回客戶端""" # 第一步:製做固定長度的報頭 header_dic = { 'filename': 'a.txt', 'md5': 'xxdxxx', 'total_size': len(stdout) + len(stderr) } header_json = json.dumps(header_dic) # dump和dumps的區別 header_bytes = header_json.encode('utf-8') # 第二步:先發送報頭的長度 conn.send(struct.pack('i', len(header_bytes))) # 第三步:將報頭髮給客戶端 conn.send(header_bytes) # conn.send(str(total_size).encode('utf-8')) # 整型轉換爲字符串,編碼後發出 # 第四步:發送真實數據 # conn.send(stdout+stderr) # 因爲粘包原理可優化改寫以下 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
這種方法雖然解決了粘包的問題,但實際上是比較差的解決方法。
程序的運行速度遠快於網絡傳輸速度,因此在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗。
爲字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,而後再取真實數據。
struct模塊
該模塊的功能就是把一個類型,例如數據,轉化爲固定長度的bytes,以下所示:
import struct # 格式:struct.pack(fmt, *args) # 設置格式後,把數字轉成固定長度的bytes類型 # fmt='i'指的是整型數字 res = struct.pack('i', 1280) # 打包 print(res, type(res), len(res)) """ b'\x00\x05\x00\x00' <class 'bytes'> 4 """ res1 = struct.pack('i', 1380) print(res1, type(res1), len(res1)) """ b'd\x05\x00\x00' <class 'bytes'> 4 """ obj = struct.unpack('i', res) # 解包 print(obj) # obj是元組,第一個值就是打包的值 print(obj[0]) """ (1280,) 1280 """
# res_i = struct.pack('i', 123000000000) # print(res_i, len(res_i)) """ struct.error: 'i' format requires -2147483648 <= number <= 2147483647 """ res_l = struct.pack('l', 123000000000) # l模式下,數字8個bytes,範圍更大。 print(res_l, len(res_l)) """ b'\x00\x0e_\xa3\x1c\x00\x00\x00' 8 """
header_dic = { 'filename': 'a.txt', 'md5': 'xxdxxx', 'total_size': 7678685767586786686876868687565756576655657565675657657657656757657565756757557575 } header_json = json.dumps(header_dic) # print(type(header_json)) # <class 'str'> header_bytes = header_json.encode('utf-8') #print(type(header_bytes)) # <class 'bytes'> struct.pack('i', len(header_bytes)) # 把報頭的長度打成固定長度
使用struct基本格式:
import json,struct #假設經過客戶端上傳1T:1073741824000的文件a.txt #爲避免粘包,必須自定製報頭 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數據,文件路徑和md5值 #爲了該報頭能傳送,須要序列化而且轉爲bytes head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化並轉成bytes,用於傳輸 #爲了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節 head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節裏只包含了一個數字,該數字是報頭的長度 #客戶端開始發送 conn.send(head_len_bytes) #先發報頭的長度,4個bytes conn.send(head_bytes) #再發報頭的字節格式 conn.sendall(文件內容) #而後發真實內容的字節格式 #服務端開始接收 head_len_bytes=s.recv(4) #先收報頭4個bytes,獲得報頭長度的字節格式 x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度 head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式 header=json.loads(json.dumps(header)) #提取報頭 #最後根據報頭的內容提取真實的數據,好比 real_data_len=s.recv(header['file_size']) s.recv(real_data_len)
如今運用struct模塊,能夠將ssh程序進一步優化:
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import subprocess import struct server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9001)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) print('starting...') while True: conn, client_addr = server.accept() print(client_addr) while True: # 通信循環 try: """一、收到客戶端的命令""" cmd = conn.recv(8096) if not cmd:break """二、執行命令,拿到結果""" obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() """三、命令結果返回客戶端""" # 第一步:製做固定長度的報頭 total_size = len(stdout) + len(stderr) # 整型 header = struct.pack('i', total_size) # 隱患:struct的i 的取值區間是-2147483648~2147483648 # 第二步:將報頭髮給客戶端 conn.send(header) # conn.send(str(total_size).encode('utf-8')) # 整型轉換爲字符串,編碼後發出 # 第三步:發送真實數據 # conn.send(stdout+stderr) # 因爲粘包原理可優化改寫以下 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import struct client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9001)) while True: """一、發命令""" cmd = input('>>: ').strip() if not cmd:continue client.send(cmd.encode('utf-8')) """二、拿結果""" # 第一步:拿到數據的長度——>即先收報頭 header = client.recv(4) # 第二步:從報頭中解析出對真實數據的描述信息(數據長度) total_size = struct.unpack('i', header)[0] # 解包以後爲元組,取第一項即打包的內容 # 第三步:接收真實的數據 recv_size = 0 recv_data = b'' while recv_size < total_size: res = client.recv(1024) # 最大不能超過操做系統緩存大小,一次收不完,屢次收 recv_data += res recv_size += len(res) # 最後一次收的時候將不是1024,將來若是要查看進度的時候有問題 print(recv_data.decode('utf-8')) client.close()
普通版本:
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import subprocess import struct import os import json share_dir = "/Users/hqs/PycharmProjects/startMyPython3.0/第六章-網絡編程/8_文件傳輸/server/share" server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9001)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) print('starting...') while True: conn, client_addr = server.accept() print(client_addr) while True: # 通信循環 try: """一、收到客戶端的命令""" res = conn.recv(8096) # b'get a.txt' if not res:break """二、解析命令,提取相應命令參數""" cmds = res.decode('utf-8').split() # ['get', 'a.txt'] filename = cmds[1] """三、以讀的方式打開文件,讀取文件內容發送給客戶端""" # with open(filename, 'rb') as f: 直接讀文件傳輸會有粘包問題 # 第一步:製做固定長度的報頭 header_dic = { 'filename': filename, # '' 'md5': 'xxdxxx', 'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename)) # 文件大小 } 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) # 第四步:發送真實數據 with open('%s/%s'% (share_dir, filename), 'rb') as f: # conn.send(f.read()) # 一下全讀取,文件有幾個T時會有問題 for line in f: conn.send(line) # 一行行發都會粘在一塊兒,且節省內存 except ConnectionResetError: break conn.close() server.close()
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import struct import json download_dir = '/Users/hqs/PycharmProjects/startMyPython3.0/第六章-網絡編程/8_文件傳輸/client/download' client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9001)) while True: """一、發命令""" cmd = input('>>: ').strip() # get a.txt軟件自定義命名標準 if not cmd:continue client.send(cmd.encode('utf-8')) """二、以寫的方式打開新文件,接收服務端發來的文件內容寫入新文件中""" # 第一步:先收報頭長度 header = client.recv(4) header_size = struct.unpack('i', header)[0] # 第二步:收取報頭 header_bytes = client.recv(header_size) # 第三步:從報頭中解析出對真實數據的描述信息(數據長度) header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['file_size'] filename = header_dic['filename'] # 第四步:接收真實的數據 with open('%s/%s' % (download_dir, filename), 'wb') as f: # 在同一臺機器上,相同的路徑會致使文件清空,須要指定目錄 recv_size = 0 # recv_data = b'' 拼接字符串不須要了 while recv_size < total_size: line = client.recv(1024) # 最大不能超過操做系統緩存大小,一次收不完,屢次收 f.write(line) # 一邊收一邊寫 recv_size += len(line) # 最後一次收的時候將不是1024,將來若是要查看進度的時候有問題 print('總大小: %s 已下載:%s' % (total_size, recv_size)) client.close()
函數版本:
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import subprocess import struct import os import json share_dir = "/Users/hqs/PycharmProjects/startMyPython3.0/第六章-網絡編程/9_文件傳輸函數優化版本/server/share" def get(conn, cmds): filename = cmds[1] """三、以讀的方式打開文件,讀取文件內容發送給客戶端""" # with open(filename, 'rb') as f: 直接讀文件傳輸會有粘包問題 # 第一步:製做固定長度的報頭 header_dic = { 'filename': filename, # '' 'md5': 'xxdxxx', 'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename)) # 文件大小 } 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) # 第四步:發送真實數據 with open('%s/%s' % (share_dir, filename), 'rb') as f: # conn.send(f.read()) # 一下全讀取,文件有幾個T時會有問題 for line in f: conn.send(line) # 一行行發都會粘在一塊兒,且節省內存 def put(conn, cmds):... def run(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 9001)) # bind()內爲元組,0-65535:0-1024供操做系統使用 server.listen(5) print('starting...') while True: conn, client_addr = server.accept() print(client_addr) while True: # 通信循環 try: """一、收到客戶端的命令""" res = conn.recv(8096) # b'get a.txt' if not res:break """二、解析命令,提取相應命令參數""" cmds = res.decode('utf-8').split() # ['get', 'a.txt'] if cmds[0] == 'get': get(conn, cmds) elif cmds[0] == 'put': put(conn, cmds) except ConnectionResetError: break conn.close() server.close() if __name__ == '__main__': run()
# -*- coding:utf-8 -*- __author__ = 'Qiushi Huang' import time import socket import struct import json download_dir = '/Users/hqs/PycharmProjects/startMyPython3.0/第六章-網絡編程/9_文件傳輸函數優化版本/client/download' def get(client, cmds): """二、以寫的方式打開新文件,接收服務端發來的文件內容寫入新文件中""" # 第一步:先收報頭長度 header = client.recv(4) header_size = struct.unpack('i', header)[0] # 第二步:收取報頭 header_bytes = client.recv(header_size) # 第三步:從報頭中解析出對真實數據的描述信息(數據長度) header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['file_size'] filename = header_dic['filename'] # 第四步:接收真實的數據 with open('%s/%s' % (download_dir, filename), 'wb') as f: # 在同一臺機器上,相同的路徑會致使文件清空,須要指定目錄 recv_size = 0 # recv_data = b'' 拼接字符串不須要了 while recv_size < total_size: line = client.recv(1024) # 最大不能超過操做系統緩存大小,一次收不完,屢次收 f.write(line) # 一邊收一邊寫 recv_size += len(line) # 最後一次收的時候將不是1024,將來若是要查看進度的時候有問題 print('總大小: %s 已下載:%s' % (total_size, recv_size)) def put(client, cmds):... def run(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9001)) while True: """一、發命令""" inp = input('>>: ').strip() # get a.txt軟件自定義命名標準 if not inp:continue client.send(inp.encode('utf-8')) cmds = inp.split() # ['get', 'a.txt'] if cmds[0] == 'get': get(client, cmds) elif cmds[0] == 'put': put(client, cmds) client.close() if __name__ == '__main__': run()
面向對象版本:
import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 server_dir='file_upload' def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print('from client ',self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack('i', head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={'cmd':'put','filename':'a.txt','filesize':123123} cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args['filename'] )) filesize=args['filesize'] recv_size=0 print('----->',file_path) with open(file_path,'wb') as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print('recvsize:%s filesize:%s' %(recv_size,filesize)) tcpserver1=MYTCPServer(('127.0.0.1',8080)) tcpserver1.run()
import socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()