網絡編程總結

socket, socket套接字分類, 基於tcp的socket, 常見錯誤

複製代碼

img

  ##爲何須要sockethtml

複製代碼

「」「
在標準的OIS模型中並無規定說必須有socket層,也就是說不使用socket也能完成通信,是的,的確如此!

那爲何須要socket呢?一個字 懶,程序員都是懶的!

咱們發現尚未開始實現應用程序邏輯,就須要花大把時間來實現各類協議,太特麼費事兒了,就有人專門把協議中一堆複雜的事情進行了封裝,因而socket就誕生了!

有了socket之後,無需本身編寫代碼實現三次握手,四次揮手,ARP請求,打包數據等等,socket已經封裝好了,只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。
」「」

複製代碼

  ##scoket套接字分類python

複製代碼

# 基於文件類型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊

# 基於網絡類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲大部通信都是網絡通信,因此大部分時候使用AF_INET)

複製代碼

  ##python中的socketmysql

複製代碼

#需明確:關於網絡協議 和socket相關概念,對於全部編程語言都是一致的,區別僅僅是各編程語言的函數名稱不一樣

# 1.導入socket模塊
import socket
# 2.建立socket對象 函數定義以下
socket.socket(socket_family,socket_type,protocal=0)
    #socket_family 能夠是 AF_UNIX 或 AF_INET。
    #socket_type 能夠是 SOCK_STREAM表示TCP協議 或 SOCK_DGRAM表示UDP協議。
    #protocol 通常不填,默認值爲 0。
    
# 2.1獲取TCP 套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 或者 後面的參數都有默認值,能夠不寫,默認建立的是TCP協議socket
tcpSock = socket.socket()

# 2.2獲取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)


#要明確一點:不管是客戶端服務器端都使用的都是socket對象
「」「
服務端套接字函數
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()      獲得阻塞套接字操做的超時時間
」「」

複製代碼

  ##基於TCP的socketlinux

#前言:已經明確socket是別人封裝好的接口,使用接口就變的簡單了

按照通信流程編寫代碼便可

  ##TCP通信流程程序員

img

  ##socket運行示例web

複製代碼

--------------------#socket服務器-------------------------------
import socket

# 做爲服務器必明確本身的ip和端口號    而且不該該變化
# 參數1指定 socket類型AF_INET 表示網絡類型
#參數2指定的傳輸協議爲 SOCK_STREAM表示TCP協議 SOCK_DGRAM UDP協議
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    # 1.買電話機

# 默認就是網絡類型   TCP協議
#server = socket.socket()

# 127.0.0.1這叫回送地址   表示當前電腦自己
# ip必定本機ip   本機可能會有多個IP (無線|有限)
# 注意:須要參數是一個元組   端口就是普通整數
server.bind(("127.0.0.1",1688))   # 2.插入手機卡

# 不管是服務器端仍是客戶端 都是socket 類型

# 開始監聽1688端口    盯着這個端口看之後沒有數據過來
server.listen()   # 3.手機開始待機

# 接收連接請求
# 第一個是表示客戶端的socket    第二個客戶端的地址信息
client,addr = server.accept()   # 4.接電話
# print(type(client))
# print(addr)


# 5.收發數據
data = client.recv(1024)
print(data)
client.send("copy!".encode("utf-8"))

server.close()  # 關機

----------------------#socket客戶端-----------------------------------
import socket

# 買個電話
client = socket.socket()

# 做爲客戶端 ip和端口能夠變化   全部系統會自動分配隨機端給客戶端
client.connect(("127.0.0.1",1688))

# 開始通話
# 發送數據   注意發送的內容只能是二進制  bytes
client.send("hello".encode("utf-8"))

# 接收數據  單位爲字節
data = client.recv(1024)
print(data)

client.close()----------------------------------------------------------------------#總結注意TCP中必須先啓動服務器再啓動客戶端,不然客戶端因爲沒法連接服務器,直接報錯!

複製代碼

  ##改版之後的socketsql

複製代碼

#註解:一個最基本的TCP通信,可是創建是爲了傳輸數據,二傳輸數據不少時候並非一次性就傳輸完成了,須要屢次收發過程,因此須要給代碼加上循環
————————————socket服務器.py————————————————————
import socket
server = socket.socket()
server.bind(("127.0.0.1",8989))
server.listen()

while True:
    client_socket,client_addr = server.accept()
    buffer_size = 1024 #緩衝區  就是一個臨時的容器  
    # 緩衝區大小   不能隨便寫  太大會致使內存溢出    過小效率低    在內存可以承受的狀況下 大一些

    while True:
        try:
            data = client_socket.recv(1024)
            # 在linux中 對方若是強行下線了  服務器不會拋出異常 只會收到空消息
            # 得加上判斷 若是爲空則意味着 對方下線了 應該關閉連接  跳出循環
            # windows上正常關閉 也會收到空消息   if 必需要加
            if not data:
                client_socket.close()
                break

            print(data.decode("utf-8"))  # 解碼時必須保證雙方贊成編碼方式
            # 轉爲大寫在發回去
            client_socket.send(data.upper())
        except ConnectionResetError as e:
            print("%s %s" % (client_addr[0],client_addr[1]),e)
            # 若是對方下線了  那服務器也應該關閉對應的客戶端對象
            client_socket.close()
            break


# 一般服務器不會關閉
# server.close()
————————————socket客戶端.py————————————————————
import socket

client = socket.socket()
# 指定服務器的端口和ip    客戶端的端口系統自動分配的
client.connect(("127.0.0.1",8989)) #


# 添加循環 用來重複收發數據
while True:
    # 收發的順序  必須很對面相反  不然卡死了
    msg = input("輸入內容:")
    if not msg:continue
    client.send(msg.encode("utf-8"))
    print("sended!")
    data = client.recv(1024)
    print(data.decode("utf-8"))


client.close()

複製代碼

  ##socket常見錯誤shell

複製代碼

#一、端口占用 

在調試過程當中,可能會碰見如下錯誤:
問題發生緣由:

1》.多是因爲你已經啓動了服務器程序,卻又再次啓動了服務器程序,同一個端口不能被多個進程使用致使!

2》.三次握手或四次揮手時,發生了異常致使對方程序已經結束而服務器任然處於time_wait狀態致使!

3》.在高併發的場景下,因爲連接的客戶端太多,也會產生大量處於time_wait狀態的連接

解決的方案:

    第1種緣由,很簡單關閉以前運行的服務器便可

    第2,3中緣由致使的問題,有兩種解決方案:

1》.設置服務器重用端口
#加入一條socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,必須在bind前加
phone.bind(('127.0.0.1',8081))
2》.經過調整linux內核參數解決(瞭解)
發現系統存在大量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 時間

#示例
import socket
server = socket.socket()

server.bind(("127.0.0.1",9891))
server.listen()
server.accept()

server.close()


# 若是已經開啓了服務器  再次運行將會拋出 端口占用異常  把以前開啓的服務器關掉便可
"""
有些狀況   明明已經關閉了進程 可是 仍是端口占用  
多是進程正在後臺運行  能夠經過端口查出進程  進行關閉
windows下
netstat -ano  | findstr 9898
tasklist | findstr 進程id    拿到進程名稱
taskkill /f /t /im 進程名稱

大招: 重啓電腦
"""

複製代碼

 ## 半連接數, 粘包問題,操做系統緩存理解, 粘包解決方案,strutc模塊, 自定義報頭 數據庫

複製代碼

2.強行關閉連接
當客服端與服務器連接成功後,若是一方沒有執行close,而是直接強行終止程序(或是遇到異常被迫終止),都會致使另外一方發送問題,因此要分別在服務端和客戶端針對發送和接收代碼添加try exception進行異常判斷

#錯誤以下

img

複製代碼

#1.socket
    套接字,本質上是一個模塊,裏面封裝了一些網絡通信協議
    是處於傳輸層和應用層之間的一個抽象層,實際在OSI中並不存在
    也就是沒有socket也能可以通信 ,可是這樣一來 咱們必須徹底按照OSI規定的各類協議來編碼
    這是一個重複,複雜的過程,爲了提升開發效率,就出現了socket模塊,專門幫咱們封裝了傳輸層如下的一堆協議
    有socket模塊以後 當咱們須要編寫網絡通信的程序時,就不須要在關心具體的協議細節,直接使用socket提供的功能接口便可

「」「
門面設計模式,隱藏複雜的內部細節,僅提供最簡單的使用接口 本質就是封裝
」「」
#2.TCP通信
    網絡通信必定分爲兩端,服務器和客戶端
    服務器:
        1.建立socket對象   server = socket.socket()
        2.綁定一個固定的ip和端口  server.bind    ip必須是本機ip    端口1024-65535    不要使用常見的端口   web:80 / 8080  mysql 3306   ssh:22  ftp:21
        3.開始監聽客戶端的到來   server.listen
        4.接收客戶端的連接請求   server.accept    # 阻塞直到客戶連接到來  沒有新鏈接則不可能執行該函數
        5.收發數據   須要循環
             recv 收    收多少字節數
             send 發    只能發二進制數據
    客戶端:
        1.建立socket對象  client = socket.socket()
        2.連接服務器    client.connect((ip,port))
        3.收發數據   一般須要循環
            send 發    只能發二進制數據
            recv 收    收多少字節數
        4.斷開連接   client.close()
#3.常見的異常
    1.一方異常下線,另外一方還要收發數據  ,ConnectionResetError
    在send  recv的地方 加上try   客戶端也須要異常處理
    2.端口占用異常
    重複運行服務器
    以前的進程沒有正確關閉
    關閉和打開端口 都是操做系統負責  在一些極端狀況下   可能應用程序已經正確接收而且通知了操做系統要關閉端口
    可是操做系統 沒及時處理
        2.1 更換端口
        2.2 查後臺進程 殺掉它
        2.3 重啓服務器電腦
#4循環通信

複製代碼

  ##半鏈接數編程

複製代碼

「」「
三次握手沒有完成 稱之爲半鏈接  

緣由1   惡意客戶端沒有返回第三次握手信息

緣由2   服務器沒空及時處理你的請求 

socket中  listen(半鏈接最大數量)
」「」

#理解
import socket

server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen(5)
# backlog  # 瞭解4
# 最大半鏈接數 本質就是一個數組  未完成連接的socket 就會被加入到數組中  ,
# 每一次執行accept 就會挑一個來完成三次握手  ,若是達到最大限制  額外的客戶端將直接被拒絕
# 咱們能夠調整內核參數來修改 最大等待時長   若是超時  客戶仍是沒有回覆第三次握手信息 就直接刪除

複製代碼

img

  

  ##粘包問題(TCP)和操做系統緩存理解

複製代碼

TCP流式協議, 指的是數據與數據之間沒有明確的分界線,致使不能正確讀取數據, 就像水  一杯水和一杯牛奶倒在一塊兒了!

#要理解粘包問題,須要先了解TCP協議傳輸數據時的具體流程
「」「
應用程序沒法直接操做硬件,應用程序想要發送數據則必須將數據交給操做系統,而操做系統須要須要同時爲全部應用程序提供數據傳輸服務,也就意味着,操做系統不可能立馬就能將應用程序的數據發送出去,就須要爲應用程序提供一個緩衝區,用於臨時存放數據,,具體流程以下:
##### 發送方:

當應用程序調用send函數時,應用程序會將數據從應用程序拷貝到操做系統緩存,再由操做系統從緩衝區讀取數據併發送出去

##### 接收方:

對方計算機收到數據也是操做系統先收到,至於應用程序什麼時候處理這些數據,操做系統並不清楚,因此一樣須要將數據先存儲到操做系統的緩衝區中,當應用程序調用recv時,其實是從操做系統緩衝區中將數據拷貝到應用程序的過程

上述過程對於TCP與UDP都是相同的不一樣之處在於:

##### UDP:

UDP在收發數據時是基於數據包的,即一個包一個包的發送,包與包之間有着明確的分界,到達對方操做系統緩衝區後也是一個一個獨立的數據包,接收方從操做系統緩衝區中將數據包拷貝到應用程序
這種方式存在的問題:

1.發送方發送的數據長度每一個操做系統會有不一樣的限制,數據超過限制則沒法發送

2.接收方接收數據時若是應用程序的提供的緩存容量小於數據包的長度將形成數據丟失,而緩衝區大小不可能無限大
##### TCP:

當咱們須要傳輸較大的數據,或須要保證數據完整性時,最簡單的方式就是使用TCP協議了

與UDP不一樣的是,TCP增長了一套校驗規則來保證數據的完整性,會將超過TCP包最大長度的數據拆分爲多個TCP包 並在傳輸數據時爲每個TCP數據包指定一個順序號,接收方在收到TCP數據包後按照順序將數據包進行重組,重組後的數據全都是二進制數據,且每次收到的二進制數據之間沒有明顯的分界
」「」

######重點來了#######
「」「
粘包 僅發生在TCP協議中   

1. 發送端 發送的數據量小 而且間隔短 會粘
2. 接收端 一次性讀取了兩次數據的內容    會粘 
3. 接收端 沒有接收完整  剩餘的內容 和下次發送的粘在一塊兒

不管是那種狀況,其根本緣由在於  接收端不知道數據到底有多少 

解決方案就是 提早告知接收方 數據的長度  
」「」

複製代碼

  ##粘包解決方案

複製代碼

「」「
先發長度給對方  再發真實數據  



#發送端

1.使用struct 將真實數據的長度轉爲固定的字節數據

2.發送長度數據 

3.發送真實數據  

接收端

1.先收長度數據    字節數固定  

2.再收真實數據      真實可能很長 須要循環接收 

發送端和接收端必須都處理粘包 纔算真正的解決了 

案例: 遠程CMD程序 「」「需求: 基於TCP開發一款遠程CMD程序  客戶端鏈接服務器後 能夠向服務器發送命令  服務器收到命令後執行 不管執行成功仍是失敗 將執行結果返回給客戶端  執行系統指令使用subprocess模塊完成」「」
」「」

#案例 遠程CMD程序 
————————————服務器.py————————————————————
import socket
import subprocess
import struct
from 二_CMD程序 import  smallTool

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
# back

while True:
    # socket,addr一個元組 客戶端的ip和port
    client,addr = server.accept()
    print("客戶端連接成功!")
    # 循環收發數據
    while True:
        try:
            cmd = smallTool.recv_data(client)
            if not cmd:
                break
            print(cmd)

            p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 不要先讀err錯誤信息  它會卡主  緣由不詳  linux不會有問題  tasklist  netstat - ano啥的
            data = p.stdout.read()
            err_data = p.stderr.read()

            len_size = len(data) + len(err_data)
            print("服務器返回了: %s " %  len_size)

            len_bytes = struct.pack("q",len_size)

            # 在發送真實數據前先發送 長度
            client.send(len_bytes)

            # 返回的結果恰好就是二進制
            # 發送真實數據
            client.send(data + err_data)


        except ConnectionResetError as e:
            print("客戶端了掛了!",e)
            break
    client.close()

#server.close()
————————————客戶端.py————————————————————
"""
客戶端輸入指令
服務器接收指令並執行  最後返回執行結果
"""

import socket
from 二_CMD程序 import smallTool
import struct

client = socket.socket()
try:
    client.connect(("127.0.0.1",1688))
    print("連接成功!")
    while True:
        msg = input("請輸入要執行指令:").strip()
        if msg == "q": break
        if not msg: continue
        # 發送指令
        # 先發長度
        len_bytes = struct.pack("q",len(msg.encode("utf-8")))
        client.send(len_bytes)
        # 在發指令
        client.send(msg.encode("utf-8"))

        data = smallTool.recv_data(client)
        print(data.decode("GBK"))

    client.close()
except ConnectionRefusedError as e:
    print("連接服務器失敗了!",e)
except ConnectionResetError as e:
    print("服務器掛了!", e)
    client.close()
————————————smalltool.py——————————————————
「」「
說明:該模塊做用於服務器和客戶端讀長度,讀數據內容的公共代碼,抽調出來的模塊
返回的是:data 爲二進制數據
」「」
import struct

def recv_data(client):
    # 接收長度數據  固定位8個字節
    len_bytes = client.recv(8)
    # 轉換爲整型
    len_size = struct.unpack("q", len_bytes)[0]
    print("服務器返回了%s長度的數據" % len_size)

    # 再接收真實數據
    # 問題  若是數據量太大 則不能 一次收完  必須循環一次收一部分

    # 緩衝區大小
    buffer_size = 1024
    # 已接受大小
    recv_size = 0

    # 最終的數據
    data = b""
    while True:
        # 若是剩餘數據長度 大於緩存區大小 則緩衝區有多大就讀多大
        if len_size - recv_size >= buffer_size:
            temp = client.recv(buffer_size)
        else:
            #  剩餘數據長度 小於緩衝區大小   剩多少就收多少
            temp = client.recv(len_size - recv_size)
        recv_size += len(temp)
        data += temp
        # 當已接受大小等於數據總大小則跳出循環
        if recv_size == len_size:
            break

    # print(data.decode("gbk"))  # windows執行指令返回的結果默認爲GBK
    return data

複製代碼

  ## struct模塊

複製代碼

# struct 是一個內置模塊  做用是 將python中的類型 轉爲字節數據 而且長度固定

import struct

# 將整數 轉爲固定長度的二進制
data = struct.pack("q",100000000000000000)
print(data)
print(len(data))

# 將二進制轉換回整型  返回一個元組
res = struct.unpack("q",data)[0]
print(res)

複製代碼

  ##引出自定義報頭以前文件下載案例

複製代碼

-----------------------------服務器.py-------------------------------------------------
"""
#理解註釋
粘包問題解決了,可是存在一個問題
當文件過大時,不能直接發給send發給操做系統
"""
import socket
import os
import struct
"""
發文件大小-----發文件長度-----發數據
"""
server =socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
while True:
    client,addr = server.accept()
    print("客戶端鏈接成功")
    f=None
    try:
        #先發送文件大小
        path = r"D:\python8期\課堂內容\day32\視頻\2.半連接數.mp4"
        file_size=os.path.getsize(path)
        print(file_size)
        len_bytes=struct.pack("q",file_size)
        #發送長度
        client.send(len_bytes)
        #循環發送文件數據,每次發送2048
        f=open(path,'rb')
        while True:
            temp = f.read(2048)
            if not temp:
                break
            client.send(temp)
        print("文件發送完畢")
    except Exception as e:
        print("出問題了",e)
    finally:
        if f:
            f.close()
    client.close()
-----------------------------客戶端.py-------------------------------------------------
import socket
import struct
client=socket.socket()
try:
    client.connect(("127.0.0.1",1688))
    print("鏈接成功")

    #第一步 先收長度
    file_size=struct.unpack("q",client.recv(8))[0]
    #第二步 再收文件內容
    #已收大小
    recv_size=0
    #緩衝區
    buffer_size=2048
    f=open("new.mp4",'wb')

    while True:
        if file_size - recv_size >= buffer_size:
            temp=client.recv(buffer_size)
        else:
            temp=client.recv(file_size - recv_size)
        f.write(temp)
        recv_size += len(temp)
        print("已接收大小:%s%%"%(recv_size/file_size *100))
        if recv_size == file_size:
            break
    f.close()
except ConnectionRefusedError as e:
    print("服務器鏈接失敗")
    client.close()

複製代碼

  ##自定義報頭

複製代碼

「」「
當須要在傳輸數據時 傳呼一些額外參數時就須要自定義報頭 

報頭本質是一個json  數據   ,例如咱們要實現文件上傳下載,不光要傳輸文件數據,還須要傳輸文件名字,md5值等等,如何能實現呢?

具體過程以下: 

發送端

    1 發送報頭長度 ,定義的報頭包含所需的信息

    2  發送報頭數據    其中包含了文件長度  和其餘任意的額外信息  

    3  發送文件內容

   

接收端 

    1.接收報頭長度 

    2.接收報頭信息

    3.接收文件內容
」「」

#案例:優化上一個下載視頻文件案例
——————————————服務器.py——————————————————
import socket
import os
import struct
import json
"""
發文件大小-----發文件長度-----發數據
"""
server =socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
while True:
    client,addr = server.accept()
    print("客戶端鏈接成功")
    f=None
    try:

        path = r"D:\python8期\課堂內容\day32\視頻\2.半連接數.mp4"
        file_size = os.path.getsize(path)

        file_info={"file_name":"廣東雨神.mp4","file_size":file_size}
        json_str=json.dumps(file_info).encode("utf-8")
        #發送報頭長度
        client.send(struct.pack("q",len(json_str)))
        #發送報頭
        client.send(json_str)
        #發文件
        #循環發送文件數據,每次發送2048
        f=open(path,'rb')
        while True:
            temp = f.read(2048)
            if not temp:
                break
            client.send(temp)
        print("文件發送完畢")
    except Exception as e:
        print("出問題了",e)
    finally:
        if f:
            f.close()
    client.close()
——————————————客戶端.py——————————————————
import socket
import struct
import json
client=socket.socket()
try:
    client.connect(("127.0.0.1",1688))
    print("鏈接成功")

    #第一步 先收報頭長度
    head_size=struct.unpack("q",client.recv(8))[0]
    #第二步 再收報頭信息
    head_str = client.recv(head_size).decode("utf-8")
    file_info=json.loads(head_str)
    print("報頭數據:",file_info)
    file_size=file_info.get("file_size")
    file_name=file_info.get("file_name")
    #已收大小
    recv_size=0
    #緩衝區
    buffer_size=2048
    f=open(file_name,'wb')

    while True:
        if file_size - recv_size >= buffer_size:
            temp=client.recv(buffer_size)
        else:
            temp=client.recv(file_size - recv_size)
        f.write(temp)
        recv_size += len(temp)
        print("已接收大小:%s%%"%(recv_size/file_size *100))
        if recv_size == file_size:
            break
    f.close()
except ConnectionRefusedError as e:
    print("服務器鏈接失敗")
    client.close()

複製代碼

UDP協議, DNS服務器, 進程, 多道技術

1.  半鏈接數
    指的是沒有完成的連接
    1.1 客戶端惡意攻擊,只進行兩次握手 致使服務器處於等待狀態
    1.2 有大量的客戶端同時訪問  (高併發) 服務器處理不過來
    listen() 函數中能夠指定最大的半鏈接數量  超出這個數量將會被拒絕
    處於半鏈接狀態的連接  會在超時後被丟棄
2.  粘包問題 網絡編程重點
    粘包問題只出如今TCP協議中,由於TCP是流式協議,數據之間沒有明顯分隔
    之因此出現粘包
    1. 把時間間隔短的 數據量小的數據 一次性發送
    2. 數據已經到達 接收多了或少了 都會粘包
    之因此粘包本質緣由就是接收方不清楚數據長度
    解決方案就是提早告訴接收方數據長度

    發送方
    1.發送數據長度
        長度信息也會粘包  因此 必定要確保每一次發送的數據信息的長度 所佔的字節是固定的
        例如  8  或 4
        struct  模塊就能夠把一個python的數據類型 轉爲二進制 而且字節數固定
        q 轉爲8字節    i   轉爲4字節

    2.發送真實數據

    接收方
    1.先接受長度
    2.再接收真實數據

3. 自定義報頭
    不只要傳輸數據過去  可能還要額外傳輸一些信息  例如文件名稱  大小 md5等等........
    報頭就是傳輸數據前 先傳輸的一個信息   一般用json
    本質就是  多傳輸了一個json數據
    發送
    1.先發json數據的長度
    2.在發送json(報頭)數據
    3.在發送真實數  也會粘包 因此要把長度放入報頭中

                    json也能夠傳輸二進制   可是json本質是字符串 因此必須把二進制轉爲字符串  接收方吧字符串轉二進制
                    BASE64 編碼
    接收:
    1.先收報頭長度
    2.收報頭
    3.真實數據

複製代碼

  ##UDP協議

複製代碼

#什麼是UDP協議
    用戶數據包協議
    OSI模型中 屬於傳輸層的協議, 僅用於不要求可靠性,不要求分組順序且數據量較小的簡單傳輸,力求快

#如何使用
    通信流程相似對講機    只管發送無論對方是否接受到 甚至不關心對方在不在
    1.買對講機
    2.固定頻道
    3.收發數據

    1.買個對講機
    2.指定發送的頻道
    3.收發數據

與TCP的區別 *****
不可靠傳輸
不須要創建鏈接
不會粘包
單次數據包不能太大

 

代碼 :
服務器端
服務器不須要監聽 listen
不須要接收請求 accept
收數據 recvfrom(緩衝區大小)
發數據 sendto(數據,地址)

客戶端:
不須要創建鏈接
收數據 recvfrom(緩衝區大小)
發數據 sendto(數據,地址)

#示例1
—————————————服務器.py——————————————————————
from socket import *

# 建立基於UDP的scoket  必須手動指定
server = socket(AF_INET,SOCK_DGRAM)

server.bind(("127.0.0.1",1688))

while True:
    data,addr = server.recvfrom(1024)
    print("收到來自%s的消息 : %s"  % (addr,data))
    server.sendto(data.upper(),addr)

# server.close()
—————————————客戶端.py——————————————————————
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#  UDP 不須要創建連接

# 發送數據時  要指定接受方地址
client.sendto("hello".encode("utf-8"),("127.0.0.1",1688))

data,addr = client.recvfrom(1024)
print("收到來自%s的消息 : %s"  % (addr,data))


client.close()


#示例2:在接受的時候 緩衝區大小必須大於數據的長度
—————————————服務器.py——————————————————————
from socket import *

# 建立基於UDP的scoket  必須手動指定
server = socket(AF_INET,SOCK_DGRAM)

server.bind(("127.0.0.1",1688))


# 在接受的時候 緩衝區大小必須大於數據的長度
data,addr = server.recvfrom(1)

print(data)
—————————————客戶端.py——————————————————————
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#  UDP 不須要創建連接

# 發送數據時  要指定接受方地址
client.sendto("hello".encode("utf-8"),("127.0.0.1",1688))

# client.sendto("world".encode("utf-8"),("127.0.0.1",1688))

client.close()

#示例3:UDP發不了大數據
—————————————服務器.py——————————————————————
from socket import  *

server = socket(AF_INET,SOCK_DGRAM)

server.bind(("127.0.0.1",1688))

while True:
    data,addr = server.recvfrom(1024*1024)
    print(data)

—————————————客戶端.py——————————————————————
"""UDP單次數據包不能太大"""

from socket import  *

client = socket(AF_INET,SOCK_DGRAM)

data = b""
for i in range(1024 * 60):
    data += b"1"

client.sendto(data,("127.0.0.1",1688))

#示例4:多個客戶端同時處理
—————————————服務器.py——————————————————————
from socket import  *
import time

server = socket(AF_INET,SOCK_DGRAM)
server.bind(("192.168.13.93",1688))

# UDP 能夠處理多個客戶端 可是並非真正的同時處理 而是按順序處理 速度很是快  感受像是同時處理  叫併發
# 並行 真正同時處理     想要真正的同時運行  必須由多個執行單位


# 模擬一下聊天室
# 客戶端發消息來  服務器就把客戶端地址存起來
# 再有消息來 服務器循環給每個人都發一份

# 客戶端列表
clients = {}


while True:
    try:
        data,addr = server.recvfrom(1472)
        if addr[0] not in clients:
            clients[addr[0]] = addr
        try:
            print("%s說:%s" % (addr[0],data.decode("utf-8")))
        except:
            print("編碼有問題啊 .....")
        # 遍歷全部客戶端  轉發消息給他們
        for k,v in clients.items():
            server.sendto(data,v)
    except Exception as e:
        # print(e)
        pass

    # 若是要限制發消息不能太頻繁 思路以下:
    # 收到數據後 把你的地址 和發送數據的時間記錄下來
    # 遍歷出全部數據 1888888888  188888888.1   10
    # 以當前時間爲起始  100   取出消息的時間  若是時間範圍爲98-100

—————————————客戶端.py——————————————————————
「」「
多個客戶端問題:能夠在一個客戶端.py模塊執行多個窗口來測試
pycharm默認只能開一個問題:界面code下面,有個start標記----選擇裏面的Edit Configurations---------而後在彈出的窗口右上方打勾---就能夠執行多個客戶端
」「」
from socket import  *

client = socket(AF_INET,SOCK_DGRAM)
while True:
    # msg = input("msg:").strip()
    client.sendto("client2".encode("utf-8"),("127.0.0.1",1688))
    data,addr = client.recvfrom(1472)
    print(data)

複製代碼

  ##DNS服務器

複製代碼

DNS Domain Name System 全稱 :域名解析服務器
    DNS 是幹什麼的 :
        將域名轉換爲IP地址     要鏈接服務器 必定的知道IP  \
    爲何須要DNS
        單獨ip不方便記憶  因此咱們吧ip和一個域名綁定到一塊兒   域名一串有規律的字符串  www.baidu.com

    DNS 是CS結構的server端
    DNS 使用的是UDP 協議 由於  傳輸的數據小 但對速度要求高  一個DNS要服務不少計算機

    http://     news.cctv.com    /2019/05/29/ARTIXRqlqFBp59eECweiXTUU190529.shtml
    協議名稱        域名                      文件路徑

    DNS 本質就是一個數據庫  裏面就存儲 域名和ip的對應關係
    news.cctv.com
    .com  頂級域名
    cctv  二級域名
    news  三級域名

複製代碼

  ##進程

複製代碼

#進程是什麼?
    正在運行的程序
進程來自於操做系統  沒有操做系統就沒有進程

操做系統是什麼?
    也是一套軟件  ,

    主要功能
        1.控制硬件,隱藏醜陋複雜的硬件細節
        2.將無序的硬件競爭變得有序

早些年 計算機同一時間只能運行一個程序,這時候是不可能併發的
要併發 固然須要不一樣的應用程序   ,如何使多個應用程序同時被運行
這就須要多道技術來支持

複製代碼

  ##多道技術

複製代碼

#多道技術:爲了提升計算機的利用率
1.空間複用  把內存分割爲不一樣區域 ,每一個區域裝入不一樣的程序
2.時間複用  當一個程序執行IO操做時,切換到另外一個程序來執行
    光切換還不行 必須在切換前保存當前的狀態    以便與恢復執行

當內存中有多個程序時,必須保證數據是安全的
每一個進程之間的內存區域是相互隔離的,並且是物理層面的隔離

有了多道技術
    計算機就能夠在同一時間處理多個任務(看着像 因爲計算遠比人塊 因此感受同時執行了)

注意:並非多道就必定提升了效率
    若是多個任務都是純計算 那麼切換反而下降了效率
    遇到IO操做才應該切換    這才能提升效率
相關文章
相關標籤/搜索