Python3之網絡編程

1、初識網絡編程

1.socket概念
Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,
它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。

 

 

 



2.兩種家族套接字:基於文件的和麪向網絡的
AF_UNIX 基於文件類型的套接字家族
AF_INET 基於網絡類型的套接字家族


3.兩種類型套接字鏈接
SOCK_STREAM: TCP面向鏈接的套接字
SOCK_DGRAM: UDP面向無鏈接的套接字


4.socket()模塊函數
要建立套接字,必須使用socket.socket()函數,它通常的語法以下。
socket(socket_family, socket_type, protocol=0)

其中,socket_family 是AF_UNIX 或AF_INET(如前所述),socket_type 是SOCK_STREAM
或SOCK_DGRAM(也如前所述)。protocol 一般省略,默認爲0。

建立TCP/IP 套接字
tc pSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

建立UDP/IP 套接字
ud pSock = socket.socket(socket.AF_INET, socket.SOCK_DGRA

5.套接字對象(內置)方法

服務器套接字方法
s.bind() 將地址(主機名、端口號對)綁定到套接字上
s.listen() 設置並啓動TCP 監聽器
s.accept() 被動接受TCP 客戶端鏈接,一直等待直到鏈接到達(阻塞)

客戶端套接字方法
s.connect() 主動發起TCP 服務器鏈接
s.connect_ex() connect()的擴展版本,此時會以錯誤碼的形式返回問題,而不是拋出一個異常

普通的套接字方法
s.recv() 接收TCP 消息
s.recv_into()   接收TCP 消息到指定的緩衝區
s.send()        發送TCP 消息
s.sendall()     完整地發送TCP 消息
s.recvfrom()    接收UDP 消息
s.recvfrom_into() 接收UDP 消息到指定的緩衝區
s.sendto()      發送UDP 消息
s.getpeername() 鏈接到套接字(TCP)的遠程地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回給定套接字選項的值
s.setsockopt() 設置給定套接字選項的值
s.shutdown()    關閉鏈接
s.close()       關閉套接字
s.detach()     在未關閉文件描述符的狀況下關閉套接字,返回文件描述符
s.ioctl()      控制套接字的模式(僅支持Windows)

面向阻塞的套接字方法
s.setblocking()     設置套接字的阻塞或非阻塞模式
s.settimeout()     設置阻塞套接字操做的超時時間
s.gettimeout()     獲取阻塞套接字操做的超時時間

面向文件的套接字方法
s.fileno()      套接字的文件描述符
s.makefile()    建立與套接字關聯的文件對象

數據屬性
s.family 套接字家族
s.type 套接字類型
s.proto 套接字協議

 

6.基於TCP的sockethtml

簡單版

TCP服務器端

import socket
sk = socket.socket()
# sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8898))  #把地址綁定到套接字
sk.listen()          #監聽連接
conn,addr = sk.accept() #接受客戶端連接
ret = conn.recv(1024)  #接收客戶端信息
print(ret)       #打印客戶端信息
conn.send(b'hi')        #向客戶端發送信息
conn.close()       #關閉客戶端套接字
sk.close()        #關閉服務器套接字(可選)

執行結果:
b'hello!'


client端

import socket
sk = socket.socket()           # 建立客戶套接字
sk.connect(('127.0.0.1',8898)) # 嘗試鏈接服務器
sk.send(b'hello!')
ret = sk.recv(1024)            # 對話(發送/接收)
print(ret)
sk.close()                     # 關閉客戶套接字

執行結果:
b'hi'

  

實例2:實現客戶端和服務端的簡單尬聊python

服務端

import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
while True:
    ret = conn.recv(1024)
    print(ret.decode('utf-8'))
    if ret == b'bye':
        conn.send(b'bye')
        break
    info = input('>>>')
    conn.send(bytes(info,encoding='utf-8'))
conn.close()
sk.close()

客戶端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    info = input('>>>')
    sk.send(bytes(info,encoding='utf-8'))
    ret = sk.recv(1024)
    if ret == b'bye':
        sk.send(b'bye')
        break
    print(ret.decode('utf-8'))

sk.close()

  

 7.基於UDP的socket算法

實例1:簡單版通訊shell

UDP服務端

import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
sk.bind(ip_port)
message,addr = sk.recvfrom(1024)
print(message.decode('utf-8'))
print(addr)
sk.sendto(b'hello udp client,Im Server!',addr)
sk.close()

UDP客戶端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
sk.sendto(b'hello udp Server,Im client1!',ip_port)
info,addr = sk.recvfrom(1024)
print(info.decode('utf-8'))
print(addr)

執行結果:
服務端
hello udp Server,Im client1!
('127.0.0.1', 62259)

客戶端
hello udp client,Im Server!
('127.0.0.1', 8080)
UDP的server 不須要進行監聽也不須要創建鏈接
在啓動服務後只能被動的等待客戶端發送消息過來
客戶端發送消息的同時還會自帶地址消息
消息回覆的時候 不只須要發送消息 還須要填上回復消息的地址

 

實例2:尬聊數據庫

服務端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
sk.bind(ip_port)
print(socket.gethostname())
while 1:
    info,addr = sk.recvfrom(1024)
    print(info.decode('utf-8'))
    print(addr)
    message = input('Server>>>').encode('utf-8')
    sk.sendto(message,addr)

sk.close()

客戶端1:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
while 1:
    message = input('Client1>>>').encode('utf-8')
    sk.sendto(message,ip_port)
    info,addr = sk.recvfrom(1024)
    print(info.decode('utf-8'))
    print(addr)

sk.close()

客戶端2:
import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)

while 1:
    message = input('Client2>>>').encode('utf-8')
    sk.sendto(message,ip_port)
    info,addr = sk.recvfrom(1024)
    print(info.decode('utf-8'))
    print(addr)
sk.close()


執行結果:
服務端
CCHNCQL008
你好 我是客戶端1
('127.0.0.1', 51851)
Server>>>你好 我是服務端
你好 我是客戶端2
('127.0.0.1', 51853)
Server>>>你好 我是服務端

客戶端1:
Client1>>>你好 我是客戶端1
你好 我是服務端
('127.0.0.1', 8080)
Client1>>>

客戶端2:
Client2>>>你好 我是客戶端2
你好 我是服務端
('127.0.0.1', 8080)
Client2>>>

  

實例3:簡單實現「時間同步」功能編程

服務端

import time
import socket

sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9000))
while True:
    msg,addr = sk.recvfrom(1024)
    # msg 客戶端發送給server端的時間格式 "%Y-%m-%d %H:%M-%S"
    time_format = msg.decode('utf-8')
    time_str = time.strftime(time_format)
    sk.sendto(time_str.encode('utf-8'),addr)
sk.close()

客戶端

import time
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.sendto('%Y/%m/%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9000))  #定製獲取時間的格式
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()


執行結果:

客戶端
2018/10/22 11:05:39

  

  TCP和UDP區別:json

  TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)協議屬於傳輸層協議。設計模式

  其中TCP提供IP環境下的數據可靠傳輸,它提供的服務包括數據流傳送、可靠性、有效流控、全雙工操做和多路複用。經過面向鏈接、端到端和可靠的數據包發送。通俗說,它是事先爲所發送的數據開闢出鏈接好的通道,而後再進行數據發送;緩存

  而UDP爲面向非鏈接協議,「面向非鏈接」就是在正式通訊前沒必要與對方先創建鏈接,無論對方狀態就直接發送。則不爲IP提供可靠性、流控或差錯恢復功能。服務器

  通常來講,TCP對應的是可靠性要求高的應用,而UDP對應的則是可靠性要求低、傳輸經濟的應用。TCP支持的應用協議主要有:Telnet、FTP、SMTP等;UDP支持的應用層協議主要有:NFS(網絡文件系統)、SNMP(簡單網絡管理協議)、DNS(主域名稱系統)、TFTP(通用文件傳輸協議)等。

 

2、TCP鏈接的黏包

  1.粘包原因?

TCP:因爲TCP協議自己的機制(面向鏈接的可靠地協議-三次握手機制)客戶端與服務器會維持一個鏈接,數據在鏈接不斷開的狀況下,
能夠持續不斷地將多個數據包發往服務器,可是若是發送的網絡數據包過小,那麼他自己會啓用Nagle算法(可配置是否啓用)對較小的數據包進行
合併(基於此,TCP的網絡延遲要UDP的高些)而後再發送(超時或者包大小足夠)。那麼這樣的話,服務器在接收到消息(數據流)的時候就沒法
區分哪些數據包是客戶端本身分開發送的,這樣產生了粘包;服務器在接收到數據庫後,放到緩衝區中,若是消息沒有被及時從緩存區取走,下次
在取數據的時候可能就會出現一次取出多個數據包的狀況,形成粘包現象。

UDP:自己做爲無鏈接的不可靠的傳輸協議(適合頻繁發送較小的數據包),他不會對數據包進行合併發送(也就沒有Nagle算法之說了),
他直接是一端發送什麼數據,直接就發出去了,既然他不會對數據合併,每個數據包都是完整的(數據+UDP頭+IP頭等等發一次數據封裝一次)也就沒有粘包一說了。

分包產生的緣由就簡單的多:多是IP分片傳輸致使的,也多是傳輸過程當中丟失部分包致使出現的半包,還有可能就是一個包可能被分紅了兩次傳輸,
在取數據的時候,先取到了一部分(還可能與接收的緩衝區大小有關係),總之就是一個數據包被分紅了屢次接收。

實例1:TCP的長鏈接

# Server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()

info,addr = sk.accept()
ret = info.recv(1024)
print(ret)
info.send(b'Hi Client!the message from Server')

info.close()
sk.close()

# Clent1端
sk = socket.socket()
sk.connect(('127.0.0.1',8080))

sk.send(b'hello,Server,I am from clent1!')
info = sk.recv(1024)
print(info)
sk.close()


# Clent2端
sk = socket.socket()
sk.connect(('127.0.0.1',8080))

sk.send(b'hello,Server,I am from clent2!')
info = sk.recv(1024)
print(info)
sk.close()

當面向鏈接TCP通訊時,客戶端只有1個能與服務端通訊,當客戶端1與服務端鏈接時,說明他們之間已經保持鏈接的狀態,客戶端2就不能與服務端鏈接。只能中斷和客戶端1鏈接後,再與客戶端2鏈接,這就是TCP長鏈接的問題。

 

實例2:遠程執行命令返回結果,因不知道數據的長度,不能一次性完整的接收數據,待下次接受後,才能完整顯示,

# Server 下發命令給客戶端
import socket
ip_port = ('127.0.0.1',8080)
sk = socket.socket()
sk.bind((ip_port))
sk.listen()
conn,addr = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'Q' or cmd == 'q':
        break
    conn.send(cmd.encode('gbk'))
    res = conn.recv(2048)
    print(res.decode('gbk'))

conn.close()
sk.close()

#client接收命令 並執行命令 返回給Server

import socket
import subprocess

sk = socket.socket()

ip_port = ('127.0.0.1',8080)
sk.connect((ip_port))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q' or cmd == 'Q':
        break
    ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    ret_stdout = (ret.stdout.read())
    ret_stderr = (ret.stderr.read())
    print(cmd)
    print(ret_stdout.decode('gbk'))
    print(ret_stderr.decode('gbk'))
    sk.send(ret_stdout)
    sk.send(ret_stderr)

sk.close()

 

>>>dir
 驅動器 D 中的卷沒有標籤。
 卷的序列號是 8FA5-5F6B

 D:\Python_learn\Study\10.21\exp1_tcp黏包 的目錄

2018/10/22  11:21    <DIR>          .
2018/10/22  11:21    <DIR>          ..
2018/10/22  11:19             4,860 client.py
2018/10/22  11:21             3,938 server.py
2018/10/21  18:47                 0 __init__.py
               3 個文件          8,798 字節
               2 個目錄 43,630,231,552 可用字節

>>>ipconfig /all

Windows IP 配置

   主機名  . . . . . . . . . . . . . : CCHNCQL008
   主 DNS 後綴 . . . . . . . . . . . : 
   節點類型  . . . . . . . . . . . . : 混合
   IP 路由已啓用 . . . . . . . . . . : 否
   WINS 代理已啓用 . . . . . . . . . : 否

無線局域網適配器 無線網絡鏈接 2:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft Virtual WiFi Miniport Adapter
   物理地址. . . . . . . . . . . . . : AC-E0-10-55-39-D1
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是

以太網適配器 Bluetooth 網絡鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Bluetooth 設備(我的區域網)
   物理地址. . . . . . . . . . . . . : D0-53-49-D9-83-75
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是

以太網適配器 本地鏈接 2:

   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . .
>>>dir
 . . . . . . . . . . . : ASIX AX88772C USB2.0 to Fast Ethernet Adapter
   物理地址. . . . . . . . . . . . . : 00-0E-C6-A5-73-41
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是
   本地連接 IPv6 地址. . . . . . . . : fe80::c1fa:6a8a:172b:9bd%14(首選) 
   IPv4 地址 . . . . . . . . . . . . : 10.127.55.10(首選) 
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   得到租約的時間  . . . . . . . . . : 2018年10月22日 9:10:21
   租約過時的時間  . . . . . . . . . : 2018年10月23日 9:10:20
   默認網關. . . . . . . . . . . . . : 10.127.55.254
   DHCP 服務器 . . . . . . . . . . . : 10.127.55.253
   DHCPv6 IAID . . . . . . . . . . . : 436211398
   DHCPv6 客戶端 DUID  . . . . . . . : 00-01-00-01-21-40-0D-2B-AC-E0-10-55-39-D1
   DNS 服務器  . . . . . . . . . . . : 221.5.203.98
                                       221.7.92.98
   TCPIP 上的 NetBIOS  . . . . . . . : 已啓用

以太網適配器 本地鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴
>>>dir
 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   物理地址. . . . . . . . . . . . . : 30-8D-99-C4-0C-0D
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

無線局域網適配器 無線網絡鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Broadcom BCM943228HMB 802.11abgn 2x2 Wi-Fi Adapter
   物理地址. . . . . . . . . . . . . : AC-E0-10-55-39-D1
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 isatap.{45C597F0-B8B5-4F80-89CF-BD6E4D47D151}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 isatap.{FB4AFF6C-8A37-40A3-A7A0-65422AC3
>>>dir
5F32}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #2
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 Teredo Tunneling Pseudo-Interface:

   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是
   IPv6 地址 . . . . . . . . . . . . : 2001:0:ddcc:f429:3025:38e1:f580:c8f5(首選) 
   本地連接 IPv6 地址. . . . . . . . : fe80::3025:38e1:f580:c8f5%15(首選) 
   默認網關. . . . . . . . . . . . . : ::
   TCPIP 上的 NetBIOS  . . . . . . . : 已禁用

隧道適配器 isatap.{FEDA9CAA-E138-451C-BB19-2FD25E03B90F}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 .
>>>dir
 . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #3
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是
 驅動器 D 中的卷沒有標籤。
 卷的序列號是 8FA5-5F6B

 D:\Python_learn\Study\10.21\exp1_tcp黏包 的目錄

2018/10/22  11:21    <DIR>          .
2018/10/22  11:21    <DIR>          ..
2018/10/22  11:19             4,860 client.py
2018/10/22  11:21             3,938 server.py
2018/10/21  18:47                 0 __init__.py
               3 個文件          8,798 字節
               2 個目錄 43,630,231,552 可用字節
 驅動器 D 中的卷沒有標籤。
 卷的序列號是 8FA5-5F6B

 D:\Python_learn\Study\10.21\exp1_tcp黏包 的目錄

2018/10/22  11:21    <DIR>          .
2018/10/22  11:21    <DIR>          ..
2018/10/22  11:19             4,860 client.py
2018/10/22  11:21             3,938 server.py
2018/10/21  18:47                 0 __init__.py
               3 個文件          8,79
執行結果
說明:
(1)執行第一次命令 dir時,數據長度小於等於1024,能完整的接收返回的結果。
(2)執行dir後,執行ipconfig /all命令,由於數據很長,接受1024個字節的長度並不能完整的接收數據。若服務端改成每次接收2048個字節數據也不能完整接收。由於並不知道接收的數據長度。
(3)再次執行dir命令後,接收到的數據爲:第二次執行的命令剩餘的部分數據加上第三次執行dir命令的數據。說明第二次和第三次命令返回的數據發送黏包現象

 實例3:

服務端
import socket
ip_port = ('127.0.0.1',8080)
sk = socket.socket()
sk.bind((ip_port))
sk.listen()
conn,addr = sk.accept()
ret1 = conn.recv(4).decode('utf-8')
ret2 = conn.recv(8).decode('utf-8')

print(ret1)
print(ret2)

conn.close()
sk.close()

客戶端
import socket
sk = socket.socket()
ip_port = ('127.0.0.1',8080)
sk.connect((ip_port))
sk.send(b'felix.wang')

sk.close()

執行結果:
服務端
feli
x.wang

結果表示:不知道對端發送的數據的長度,分別接受兩次也能接受所有數據

 

實例4:

服務端
import socket
ip_port = ('127.0.0.1',8080)
sk = socket.socket()
sk.bind((ip_port))
sk.listen()
conn,addr = sk.accept()
ret1 = conn.recv(12).decode('utf-8')
print(ret1)
conn.close()
sk.close()

客戶端
import socket
sk = socket.socket()
ip_port = ('127.0.0.1',8080)
sk.connect((ip_port))
sk.send(b'felix')
sk.send(b'wang')

sk.close()

執行結果:
服務端

執行屢次會發現收到的數據有可能爲 felix 或者 felixwang
執行結果爲 felixwang 表示數據內部作了優化算法 連續的小數據包會被合併
多個send小的數據連在一塊兒的時候,就有可能發生黏包現象,是TCP協議內部的優化算法形成的

  

2.解決粘包方法

(1)在接收數據以前,先接收數據的大小,再接收的時候按照數據大小接收

# Server
import socket
ip_port = ('127.0.0.1',8080)
sk = socket.socket()
sk.bind((ip_port))
sk.listen()
conn,addr = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'Q' or cmd == 'q':
        break
    conn.send(cmd.encode('gbk'))

    data_length  = conn.recv(1024)  #接收客戶端發來的數據長度
    print(data_length.decode('utf-8'))
    conn.send(b'ok')
    res = conn.recv(int(data_length))  #再按照數據長度大小接收
    print(res.decode('gbk'))

conn.close()
sk.close()

# client端

import socket
import subprocess

sk = socket.socket()

ip_port = ('127.0.0.1',8080)
sk.connect((ip_port))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q' or cmd == 'Q':
        break
    ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    ret_stdout = (ret.stdout.read())
    ret_stderr = (ret.stderr.read())
    data_length = len(ret_stdout) + len(ret_stderr)  #數據長度
    sk.send(str(data_length).encode('utf-8'))    #向server端發送數據長度大小
    print(cmd)
    cmd = sk.recv(1024)
    print(ret_stdout.decode('gbk'))
    print(ret_stderr.decode('gbk'))
    sk.send(ret_stdout)
    sk.send(ret_stderr)

sk.close()


        

  

說明:
此方法爲客戶端先計算數據的大小,再將數據大小發送給服務端,服務端接收數據的大小後,按照數據的大小接收,就能夠一次性完整的接收數據。
優勢:能完整接收數據,解決了粘包問題。
缺點:服務端和客戶端多了一次交互
   若數據太大,會致使消耗太多內存

那麼如何解決一次接受性數據太多,致使消耗內存的問題呢?

 

 (2)仍是在接收數據以前,先接收數據的大小,再每次按照指定大小(1024)接收,若小於指定大小可一次性接收,若大於指定大小則每次接收指定大小,

數據大小每次減去指定大小長度(1024),直到數據大小爲0,則接收完整。

#Server
import socket
ip_port = ('127.0.0.1',8080)
sk = socket.socket()
sk.bind((ip_port))
sk.listen()
conn,addr = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'Q' or cmd == 'q':
        break
    conn.send(cmd.encode('gbk'))
    data_length  = conn.recv(1024).decode('utf-8')
    data_length = int(data_length)
    print('要接收數據大小:{} 字節'.format(data_length))
    conn.send(b'ok')

    while data_length:
        if data_length < 1024:
            res = conn.recv(1024)
            print(res.decode('gbk'))
            data_length = 0
        else:
            res = conn.recv(1024)
            data_length -= 1024
            print(res.decode('gbk'))
            # print(data_length)

conn.close()
sk.close()


#Client

ip_port = ('127.0.0.1',8080)
sk.connect((ip_port))
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q' or cmd == 'Q':
        break
    ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    ret_stdout = (ret.stdout.read())
    ret_stderr = (ret.stderr.read())
    data_length = len(ret_stdout) + len(ret_stderr)
    sk.send(str(data_length).encode('utf-8'))
    print(cmd)
    cmd = sk.recv(1024)
    print(ret_stdout.decode('gbk'))
    print(ret_stderr.decode('gbk'))
    sk.send(ret_stdout)
    sk.send(ret_stderr)
sk.close()

 

>>>dir
要接收數據大小:436 字節
 驅動器 D 中的卷沒有標籤。
 卷的序列號是 8FA5-5F6B

 D:\Python_learn\Study\10.21\exp2_解決TCP粘包問題方法2 的目錄

2018/10/22  14:12    <DIR>          .
2018/10/22  14:12    <DIR>          ..
2018/10/22  14:12               659 client.py
2018/10/22  14:01               768 server.py
2018/10/22  13:34                 0 __init__.py
               3 個文件          1,427 字節
               2 個目錄 43,510,009,856 可用字節

>>>ipconfig /all
要接收數據大小:4355 字節

Windows IP 配置

   主機名  . . . . . . . . . . . . . : CCHNCQL008
   主 DNS 後綴 . . . . . . . . . . . : 
   節點類型  . . . . . . . . . . . . : 混合
   IP 路由已啓用 . . . . . . . . . . : 否
   WINS 代理已啓用 . . . . . . . . . : 否

以太網適配器 Bluetooth 網絡鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Bluetooth 設備(我的區域網)
   物理地址. . . . . . . . . . . . . : D0-53-49-D9-83-75
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是

以太網適配器 本地鏈接 2:

   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : ASIX AX88772C USB2.0 to Fast Ethernet Adapter
   物理地址. . . . . . . . . . . . . : 00-0E-C6-A5-73-41
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是
   本地連接 IPv6 地址. . . . . . . . : fe80::c1fa:6a8a:172b:9bd%14(首選) 
   IPv4 地址 . . . . . . . . . . . . : 10.127.55.10(首選) 
   
子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   得到租約的時間  . . . . . . . . . : 2018年10月22日 13:31:18
   租約過時的時間  . . . . . . . . . : 2018年10月23日 13:31:18
   默認網關. . . . . . . . . . . . . : 10.127.55.254
   DHCP 服務器 . . . . . . . . . . . : 10.127.55.253
   DHCPv6 IAID . . . . . . . . . . . : 436211398
   DHCPv6 客戶端 DUID  . . . . . . . : 00-01-00-01-21-40-0D-2B-AC-E0-10-55-39-D1
   DNS 服務器  . . . . . . . . . . . : 221.5.203.98
                                       221.7.92.98
   TCPIP 上的 NetBIOS  . . . . . . . : 已啓用

以太網適配器 本地鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   物理地址. . . . . . . . . . . . . : 30-8D-99-C4-0C-0D
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

無線局域網適配器 無線網絡鏈接:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . 
. . . . . : 
   描述. . . . . . . . . . . . . . . : Broadcom BCM943228HMB 802.11abgn 2x2 Wi-Fi Adapter
   物理地址. . . . . . . . . . . . . : AC-E0-10-55-39-D1
   DHCP 已啓用 . . . . . . . . . . . : 是
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 isatap.{ED5225D2-304E-4857-AB9F-C0D3EDA125CA}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #2
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 Teredo Tunneling Pseudo-Interface:

   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是
   IPv6 地址 . . . . . . . . . . . . : 2001:0:ddcc:f429:300c:353c:f580:c8f5(首選) 
  
 本地連接 IPv6 地址. . . . . . . . : fe80::300c:353c:f580:c8f5%15(首選) 
   默認網關. . . . . . . . . . . . . : ::
   TCPIP 上的 NetBIOS  . . . . . . . : 已禁用

隧道適配器 isatap.{FEDA9CAA-E138-451C-BB19-2FD25E03B90F}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #3
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 isatap.{4EF1428D-080C-44A6-B8B2-9FAEFD1DB5A8}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開
   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #4
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

隧道適配器 isatap.{FB4AFF6C-8A37-40A3-A7A0-65422AC35F32}:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開

   鏈接特定的 DNS 後綴 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #5
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已啓用 . . . . . . . . . . . : 否
   自動配置已啓用. . . . . . . . . . : 是

>>>
執行結果

 

3.解決粘包的進階方法(struct模塊

 

 1 #Server
 2 import json
 3 import struct
 4 import socket
 5 
 6 sk = socket.socket()
 7 sk.bind(('127.0.0.1',8081))
 8 sk.listen()
 9 
10 conn,addr = sk.accept()
11 dic_len = conn.recv(4)  # 4個字節 數字的大小
12 print(dic_len)
13 dic_len = struct.unpack('i',dic_len)[0]
14 print(dic_len)
15 content = conn.recv(dic_len).decode('utf-8')  # 70
16 print(content)
17 content_dic = json.loads(content)
18 print(content_dic)
19 if content_dic['operate'] == 'upload':
20     with open(content_dic['filename'],'wb') as f:
21         while content_dic['filesize']:
22             file = conn.recv(1024)
23             f.write(file)
24             content_dic['filesize'] -= len(file)
25 conn.close()
26 sk.close()
27 
28 #client
29 import os
30 import json
31 import struct
32 import socket
33 
34 sk = socket.socket()
35 sk.connect(('127.0.0.1',8081))
36 
37 def get_filename(file_path):
38     filename = os.path.basename(file_path)
39     return filename
40 
41 #選擇 操做
42 operate = ['upload','download']
43 for num,opt in enumerate(operate,1):
44     print(num,opt)
45 num = int(input('請輸入您要作的操做序號 : '))
46 if num == 1:
47     '''上傳操做'''
48     file_path = input('請輸入要上傳的文件路徑 : ')
49     file_size = os.path.getsize(file_path)  # 獲取文件大小
50     file_name = get_filename(file_path)
51     dic = {'operate': 'upload', 'filename': file_name,'filesize':file_size}
52     str_dic = json.dumps(dic).encode('utf-8')
53     print(str_dic)
54     ret = struct.pack('i', len(str_dic))  # 將字典的大小轉換成一個定長(4)的bytes
55     print(ret)
56     sk.send(ret + str_dic)
57     print(ret+str_dic)
58     with open(file_path,'rb') as  f:
59         sum = 0
60         count = 0
61         s = file_size
62         while file_size:
63             count += 1
64             content = f.read(1024)
65             sk.send(content)
66             file_size -= len(content)
67             sum += 1024
68             baifenshu = sum/s
69             print('*',end='')
70             if count%150 == 0:
71                 print()
72             # print('已上傳%.2f%%'%(baifenshu*100),end='')
73 
74 elif num == 2:
75     '''下載操做'''
76 sk.close()
文件上傳
相關文章
相關標籤/搜索