python進階之 網絡編程

otherhtml

互聯網協議按照功能不一樣分爲osi七層或tcp/ip五層或tcp/ip四層

  每一層的做用python

  每層運行常見的物理設備程序員

  socket五層通信流程算法

osi每層做用django

1.應用層:爲應用軟件提供接口,使應用程序可以使用網絡服務
常見的應用協議:http80 https443 dns53 ftp(20/21) smtp(25) pop3(110) telnet(23)

2.表示層:數據的解碼和編碼 加密和解密 壓縮和解壓縮
圖片:jpg,gif
音頻:MP3,wma,aac
視頻:MP4,avi

3.會話層:負責創建管理和終止表示層實體之間的會話連接
在設備或節點之間提供會話控制,協調通訊過程。
並提供三種不一樣的方式(單工,半雙工,全雙工)來組織他們之間的通訊

4.傳輸層(tcp/udp):負責創建端到端的鏈接,保證報文在端到端之間進行傳輸,負責數據重傳,冗餘校驗等
服務點編址,分段與重組,鏈接控制,流量控制,差錯控制

5.網絡層:爲網絡設備邏輯尋址,進行路由選擇,路由表維護等等
負責將分組數據從源端傳輸到目的端,
表明設備:路由器和三層交換機

6.數據鏈路層:在不可靠的物理鏈路上提供可靠的數據傳輸服務,把封裝好數據幀從(一跳)節點移動到另外一節點(另外一跳)
組幀,物理編址,流量控制,差錯控制,接入控制
表明設備:交換機

7.物理層:負責把逐個的比特從一跳(結點)移動到另外一跳(結點)。
定義接口和媒體的物理特性(線序、電壓、電流)
定義比特的表示、數據傳輸速率、信號的傳輸模式
定義網絡物理拓撲(網狀、星型、環型、總線型等拓撲)
表明:集線器
View Code

 

1.tcp和udp協議的區別編程

tcp/ip協議簡介
  TCP/IP協議(傳輸控制協議/互聯網協議)不是簡單的一個協議,而是一組特別的協議,包括:TCP,IP,UDP,ARP等,這些被稱爲子協議。在這些協議中,最重要、最著名  的就是TCP和IP。所以,大部分網絡管理員稱整個協議族爲「TCP/IP」。

TCP協議   面向鏈接\可靠\慢\對傳遞的數據的長短沒有要求   兩臺機器之間要想傳遞信息必須先創建鏈接   以後在有了鏈接的基礎上,進行信息的傳遞   可靠 : 數據不會丟失 不會重複被接收   慢 : 每一次發送的數據還要等待結果   三次握手和四次揮手 UDP協議   無鏈接\不可靠\快\不能傳輸過長的數據0   機器之間傳遞信息不須要創建鏈接 直接發就行   不可靠 : 數據有可能丟失

TCP(Transmission Control Protocol)可靠的、面向鏈接的協議(eg:打電話)、傳輸效率低全雙工通訊(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;文件傳輸程序。json

UDP(User Datagram Protocol)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文(數據包),盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。flask






  import socket
  sk = socket.socket()
  sk.bind(('0.0.0.0',9090))
  sk.listen(5)
  conn,addr = sk.accept()設計模式

  accept 至關於和客戶端的connect 一塊兒完成了TCP的三次握手
  至於以前的sk, 它只起到一個大門的做用了, 意思是說,歡迎敲門, 進門以後我將爲你生成一個獨一無二的socket描述符sk!  瀏覽器

2.socket模塊

套接字簡介?
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的

什麼是socket?   創建網絡通訊鏈接至少要一對端口號(socket)。
  socket本質是編程接口(API),對TCP
/IP的封裝,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口;
  HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通訊的能力。
  socket又稱爲套接字,它是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,
  Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。

兩種套接字:基於文件和麪向網絡的
  基於文件的:AF_UNIX
    unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊
  基於網絡的:AF_INIT
    (還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址
    家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)

特殊意義的解釋socket:
  socekt又稱爲‘套接字’,用於描述IP和地址端口,是一個通訊鏈路的句柄,應用程序一般經過套接字向網絡發出請求或者應答網絡請求。
  socket起源於Unix,因此也聽從「一切皆文件」的基本哲學,對於文件,進行打開/讀取/關閉的操做模式。
  socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉)
  socket和file文件的區別:
    file模塊是針對指定文件進行打開、讀寫、關閉操做。
    socket模塊是針對服務器客戶端socket進行打開、讀寫、關閉操做。
python中socket模塊:
  地址簇:
    socket.AF_INET IPv4(默認)
    socket.AF_INET6 IPv6
    socket.AF_UNIX 只可以用於單一的Unix系統進程間通訊
  類型:
    socket.SOCK_STREAM  流式socket , for TCP (默認)
    socket.SOCK_DGRAM   數據報式socket , for UDP
tcp/ip和http的關係?
 tcp/ip協議是傳輸層協議,主要解決數據如何在網絡中傳輸,而HTTP協議是應用層協議,主要解決如何包裝數據。
  咱們在傳輸數據時,能夠只使用(傳輸層)TCP/IP協議,可是那樣的話,若是沒有應用層,便沒法識別數據內容。
 若是想要使傳輸的數據有意義,則必須使用到應用層協議。 應用層協議有不少,好比HTTP、FTP、TELNET等,也能夠本身定義應用層協議。 
 WEB使用HTTP協議做應用層協議,以封裝HTTP文本信息,而後使用TCP/IP作傳輸層協議將它發到網絡上。」

socket對象

Socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

參數一:地址簇
  參數    描述
  socket.AF_INET    IPv4(默認)
  socket.AF_INET6    IPv6
  ocket.AF_UNIX    只可以用於單一的Unix系統進程間通訊
參數二:類型
  參數    描述
  socket.SOCK_STREAM    流式socket , for TCP (默認)
  socket.SOCK_DGRAM    數據報式socket , 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類方法
  方法    描述
  s.bind(address)    將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
  sk.listen(backlog)    開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量。
  sk.setblocking(bool)    是否阻塞(默認True),若是設置False,那麼accept和recv時一旦無數據,則報錯。
  sk.accept()    接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址。
  sk.connect(address)    鏈接到address處的套接字。通常,address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。
  sk.connect_ex(address)    同上,只不過會有返回值,鏈接成功時返回 0 ,鏈接失敗時候返回編碼,例如:10061
  sk.close()    關閉套接字鏈接
  sk.recv(bufsize[,flag])    接受套接字的數據。數據以字符串形式返回,bufsize指定最多能夠接收的數量。flag提供有關消息的其餘信息,一般能夠忽略。
  sk.recvfrom(bufsize[.flag])    與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
  sk.send(string[,flag])    將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。
  sk.sendall(string[,flag])    將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。內部經過遞歸調用send,將全部內容發送出去。
  sk.sendto(string[,flag],address)    將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。
  sk.settimeout(timeout)    設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。
  sk.getpeername()    返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。
  sk.getsockname()    返回套接字本身的地址。一般是一個元組(ipaddr,port)
  sk.fileno()    套接字的文件描述符
socket對象

基於tcp協議(流式socket):是兩個進程之間的通訊,經過端口號來區分不一樣進程

import socket
sk = socket.socket()  #)返回的用於監聽和接受客戶端的鏈接請求的套接字
# sk.bind(('192.168.16.96',9090))
sk.bind(('0.0.0.0',9090)) #能接收到全部的ip的訪問
sk.listen(5)  #監聽連接,而且設置監聽個數,
conn,addr = sk.accept() 
# hold住,等待用戶連接,accept()接受一個客戶端的鏈接請求,並返回一個新的套接字,與客戶端通訊是經過這個新的套接字上發送和接收數據來完成的。
#每一個鏈接進來的客戶端,都會經過accept函數返回一個不一樣的客戶端的socket對象和屬於客戶端的套接字
    #bytes:是字節   b'kobe'
    #str:字符串類型  'kobe'
    #str----編碼(encode)---->bytes
    #bytes-----解碼(decode)--->str
    #英文字符串能夠直接加b轉成bytes
    #中文的必須的加上''.encode('utf-8')
#發送回覆信息,在網絡傳輸中的最小單位爲字節,因此,要將數據轉爲字節格式
conn.send('我接受到了'.encode('utf-8'))
ret = conn.recv(4096)
print(ret.decode('utf-8'))
conn.close()  #conn.close和cilent的sk.close()是四次揮手的過程
sk.close()  #關閉socket,不接受任何client請求



import socket
sk =socket.socket()
sk.connect(('192.168.16.96',9090))  #只和server的accept對應
ret = sk.recv(1024)
print(ret.decode('utf-8'))
sk.send('你好啊'.encode('utf-8'))
sk.close()
socket服務端
import socket
sk =socket.socket()
sk.connect(('192.168.16.96',9090))  #只和server的accept對應
ret = sk.recv(1024)
print(ret.decode('utf-8'))
sk.send('你好啊'.encode('utf-8'))
sk.close()
socket客戶端

基於udp協議(報文式socket)

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  #SOCK_DGRAM指的是udp協議
sk.bind(('192.168.16.96',8081))
while True:
    msg,client_addr = sk.recvfrom(1024)
    #在udp協議中,recvfrom接收返回的時候能接收到客戶端的信息msg和客戶端的連接信息client_addr,
    #在tcp協議中,在等待鏈接的conn,addr = sk.accept()會接受到conn客戶端的socket和客戶端的連接信息
    print(str(client_addr)+":"+msg.decode('utf-8'))
    content = input('>>>')
    sk.sendto(content.encode('utf-8'),client_addr)
    #經過sendto和客戶端的鏈接信息發送消息
sk.close()
socket服務器端
import socket
client_addr= ('192.168.16.96',8081)
sk = socket.socket(type=socket.SOCK_DGRAM)
while True:
    connect = input(">>>>")
    if connect.upper() !='Q':
        sk.sendto(connect.encode('utf-8'),client_addr)
        msg = sk.recv(1024).decode('utf-8')
        if msg.upper() == 'Q': break
        print(str(client_addr)+":"+msg)
    else:
        break
socket客戶端
注意:
    1.tcp協議服務端是經過accept來創建連接,獲取客戶端的連接信息
         conn,addr = sk.accept()
      經過recv來獲取客戶端消息
         msg = sk.recv(1024).decode('utf-8')
    經過send發送信息
     conn.send('hello'.encode('utf-8'))

    客戶端須要鏈接服務端地址
     sk.connect(服務器地址和端口)
   客戶端發送信息
     sk.send('你好'.encode('utf-8'))
    客戶端接收信息
     msg = sk.recv(1024).deocde('1024')
2.udp協議服務器是經過recvfrom來獲取客戶端發送的信息和客戶端連接的信息
      msg,addr1 = sk.recvfrom(1024)
    經過sendto發送信息給客戶端,要指定客戶端信息
      sk.sendto('hello'.encode('utf-8'),addr1)

    udp客戶端不須要連接服務器端,是經過sendto發送信息
      sk.sendto('你好'.encode('utf-8'),服務器地址和端口)
    經過recv來獲取信息
      msg = sk.recv(1024).decode('utf-8')
 

 3.tcp黏包

什麼叫作黏包?

通常所謂的TCP粘包是在一次接收數據不能徹底地體現一個完整的消息數據。

爲何只有TCP通信存在粘包?

主要緣由是TCP是以流的方式來處理數據,而且能發送大量的數據,再加上網絡上MTU的每每小於在應用處理的消息數據,因此就會引起一次接收的數據沒法知足消息的須要,致使粘包的存在。

TCP協議拆包機制

當發送端緩衝區的長度大於網卡的(最大傳輸單元)時,tcp會將此次發送的數據拆成幾個數據包發送出去。 
MTU是Maximum Transmission Unit的縮寫。意思是(網卡)網絡上傳送的最大數據包。MTU的單位是字節。 大部分網絡設備的MTU都是1500。
若是本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生不少數據包碎片,增長丟包率,下降網絡速度。
同時執行多條命令以後,獲得的結果極可能只有一部分,在執行其餘命令時又會接到以前執行的另一部分結果,這種就是黏包。

 面向流的通訊特色和Nagle(優化)算法

TCPtransport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。 收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。 這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。 
對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。

可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,
一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

 黏包的兩種狀況

1,發送方的緩存機制:發送端須要等緩衝區滿才發送出去,形成黏包(發送數據時間間隔很短,數據很小,會合到一塊兒,產生黏包)
    連續send兩次且數據很小
2,接收方的緩存機制:接收不及時接收緩衝區的包,形成多個包接收(客戶端發送一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿走上次剩餘的數據,產生黏包。)
    連續recv兩次且第一個recv接收的數據小

 黏包處理

# -*- coding: utf-8 -*- 
# @Time    : 2019/4/10 16:58 
# @Author  : p0st
# @Site    :  
# @File    : 傳遞大文件server.py
# @Software: PyCharm
import time
import json
import struct
import socket
start_time = time.time()
sk = socket.socket()
ip_addr = (('127.0.0.1',9999))
sk.bind(ip_addr)
sk.listen(5)
conn,addr = sk.accept()

num = conn.recv(4)
l_num = struct.unpack('i',num)[0]  #bytes類型的json的長度爲45,由於struct.unpack是元組類型的
l_dic = conn.recv(l_num).decode('utf-8')  #將bytes類型的json轉成字符串類型的json
dic = json.loads(l_dic)
filesize = dic['file_size']
with open(dic['file_name'],'wb') as info:
        while filesize>=1024:
            content = conn.recv(1024)
            info.write(content)
            filesize -=1024
        else:
            content = conn.recv(filesize)
            if content:
                info.write(content)
conn.close()
sk.close()
print(time.time()-start_time)
發送大文件服務端
# -*- coding: utf-8 -*- 
# @Time    : 2019/4/10 17:03 
# @Author  : p0st
# @Site    :  
# @File    : 傳遞大文件client.py
# @Software: PyCharm
import json
import struct
import socket
import os
sk = socket.socket()
ip_addr = (('127.0.0.1',9999))
sk.connect_ex(ip_addr)

file_path = input("請輸入文件路徑:>>>").strip()
file_name = os.path.basename(file_path) #獲取文件名字
file_size = os.path.getsize(file_path)  #獲取文件大小

s_dic = {'file_name':file_name,'file_size':file_size}
j_s_dic = json.dumps(s_dic)   #將字典序列化成json
b_s_dic = j_s_dic.encode('utf-8')  #將json轉成bytes類腦,在網上傳輸
l_s_dic = len(b_s_dic)   #常看bytes類型的長度

lalala = struct.pack('i',l_s_dic)  #將bytes類型的長度經過struct的pack方法變成4個字節
sk.send(lalala)
sk.send(b_s_dic)
with open(file_path,'rb') as info:
    while file_size >= 1024:
        content = info.read(1024)
        sk.send(content)
        file_size -= 1024
    else:
        content = info.read(file_size)
        if content:
            sk.send(content)

sk.close()






#1.將字典轉成json,再轉成bytes類型,查看其長度發送給服務器端
#2.將bytes類型的發送給服務器
發送大文件客戶端

發送大文件帶註釋版本

# -*- coding: utf-8 -*- 
# @Time    : 2019/7/18 16:50 
# @Author  : p0st
# @Site    :  
# @File    : server.py
# @Software: PyCharm
import struct
import socket
import hashlib
import json
import os
my_file_path = os.path.join(os.path.dirname(__file__),'file')

def socket_server():
    ip_port = ('127.0.0.1',8080)
    server = socket.socket()  # 建立socket對象
    server.bind(ip_port) # 綁定ip地址和端口
    server.listen(5)  # 設置監聽對象爲5個
    conn,addr = server.accept() # 設置等待鏈接


    # 1.接收4個字節的固定長度
    four_bytes = conn.recv(4)

    # 2.利用struct模塊進行反解
    head_len = struct.unpack('i',four_bytes)[0]

    # 3.接收bytes類型的數據
    head_dic_json_bytes = conn.recv(head_len)

    # 4.將bytes類型的數據轉成json類型的數據
    head_dic_json = head_dic_json_bytes.decode('utf-8')

    # 5.將json類型的數據轉成字典形式的數據
    head_dic = json.loads(head_dic_json)

    # 6.邊傳輸編計算MD5的值
    md5 = hashlib.md5()
    with open(os.path.join(my_file_path,head_dic['new_file_name']),mode='wb') as info:
        while 1:
            data = conn.recv(1024)
            if data:
                md5.update(data)
                info.write(data)
            else:
                info.close()
                if md5.hexdigest() == head_dic['md5']:
                    print('傳輸成功')
                    break
                else:
                    print('傳輸失敗')
                    break
if __name__ == '__main__':
    socket_server()
服務端
# -*- coding: utf-8 -*- 
# @Time    : 2019/7/18 16:50 
# @Author  : p0st
# @Site    :  
# @File    : client.py
# @Software: PyCharm
import os
import json
import hashlib
import socket
import struct
my_file_path = os.path.join(os.path.dirname(__file__),'1.png')

def socket_client():
    ip_port = ('127.0.0.1',8080)
    client = socket.socket() # 建立socket對象
    client.connect(ip_port)

    # 1.製做字典了類型的數據
    head_dic = {
        'md5': md5(my_file_path),
        'file_name': os.path.basename(my_file_path),
        'file_size': os.path.getsize(my_file_path),
        'new_file_name':'11.png',
    }
    # 2.將字典轉換成json的數據類型
    head_dic_json = json.dumps(head_dic)

    # 3.獲取bytes類型的數據
    head_dic_json_baytes = head_dic_json.encode('utf-8')

    # 4.獲取bytes類型的字節數
    head_len = len(head_dic_json_baytes)

    # 5.將bytes類型的總字節數轉化成固定的4個字節
    four_bytes = struct.pack('i',head_len)

    # 6.發送4個固定字節
    client.send(four_bytes)

    # 7.發送head數據
    client.send(head_dic_json_baytes)

    # 8.發送總數據
    with open(my_file_path,mode='rb') as info:
        while 1:
            data = info.read(1024)
            if data:
                client.send(data)
            else:
                break
def md5(file_path):
    md5 = hashlib.md5()
    with open(file_path, mode='rb') as info:
        while 1:
            data = info.read(1024)
            if data:
                md5.update(data)
            else:
                break
        return md5.hexdigest() #31e03dc9b5a06c91f63dbfee470f2c58

if __name__ == '__main__':
    socket_client()
客戶端

使用struct解決黏包:該模塊能夠把一個類型,如數字,轉成固定長度的bytes

咱們知道長度數字能夠被轉換成一個標準大小的4字節數字。所以能夠利用這個特色來預先發送數據長度。

發送時                                接收時
  先發送struct轉換好的數據長度4字節        先接受4個字節使用struct轉換成數字來獲取要接收的數據長度
  再發送數據                            再按照長度接收數據

理解下struct模塊,經過該模塊能夠將一個類型,如數字,轉成固定長度的bytes類型

import struct
# 將一個數字轉化成等長度的bytes類型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))

# 經過unpack反解回來
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))


# 可是經過struct 處理不能處理太大
ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret))  # 報錯

 併發的socketserver

import time
import socketserver

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        for i in range(200):
            conn.send(('hello%s'%i).encode('utf-8'))
            print(conn.recv(1024))
            time.sleep(0.5)
            print(conn)


if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999
    # 設置allow_reuse_address容許服務器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 建立一個server, 將服務地址綁定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 讓server永遠運行下去,除非強制中止程序
    server.serve_forever()


#sk.setblocking(False)  #默認不阻塞,不阻塞模型,django和flask等等,setblocking作到和socketserver對tcp協議的非阻塞
# 非阻塞模型是一個突破,udp協議不用阻塞和不阻塞,udp協議能同時提供對多個客戶端進行鏈接
server
import socket

HOST, PORT = "127.0.0.1", 9999
data = "hello"

# 建立一個socket連接,SOCK_STREAM表明使用TCP協議
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))          # 連接到客戶端
    sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端發送數據
    received = str(sock.recv(1024), "utf-8")# 從服務端接收數據

print("Sent:     {}".format(data))
print("Received: {}".format(received))
client

數據報和數據流的區別

1.報文(message)
咱們將位於應用層的信息分組稱爲報文。報文是網絡中交換與傳輸的數據單元,也是網絡傳輸的單元。報文包含了將要發送的完整的數據信息,其長短不需一致。報文在傳輸過程當中會不斷地封裝成分組、包、幀來傳輸,封裝的方式就是添加一些控制信息組成的首部,那些就是報文頭。

2.報文段(segment)

一般是指起始點和目的地都是傳輸層的信息單元。

3.分組/包(packet)
分組是在網絡中傳輸的二進制格式的單元,爲了提供通訊性能和可靠性,每一個用戶發送的數據會被分紅多個更小的部分。在每一個部分的前面加上一些必要的控制信息組成的首部,有時也會加上尾部,就構成了一個分組。它的起始和目的地是網絡層。

4.數據報(datagram)
面向無鏈接的數據傳輸,其工做過程相似於報文交換。採用數據報方式傳輸時,被傳輸的分組稱爲數據報。一般是指起始點和目的地都使用無鏈接網絡服務的的網絡層的信息單元。

5.幀(frame)
幀是數據鏈路層的傳輸單元。它將上層傳入的數據添加一個頭部和尾部,組成了幀。它的起始點和目的點都是數據鏈路層。

6.數據單元(data unit)

指許多信息單元。經常使用的數據單元有服務數據單元(SDU)、協議數據單元(PDU)。

SDU是在同一機器上的兩層之間傳送信息。PDU是發送機器上每層的信息發送到接收機器上的相應層(同等層間交流用的)。

 

應用層——消息

傳輸層——數據段/報文段(segment) (注:TCP叫TCP報文段,UDP叫UDP數據報,也有人叫UDP段)

網絡層——分組、數據包(packet)

鏈路層——幀(frame)

物理層——P-PDU(bit)



其實,segment,datagram,packet,frame是存在於同條記錄中的,是基於所在協議層不一樣而取了不一樣的名字。咱們能夠用一個形象的例子對數據包的概念加以說明:咱們在郵局郵寄產品時,雖然產品自己帶有本身的包裝盒,可是在郵寄的時候只用產品原包裝盒來包裝顯然是不行的。必須把內裝產品的包裝盒放到一個郵局指定的專用紙箱裏,這樣纔可以郵寄。這裏,產品包裝盒至關於數據包,裏面放着的產品至關於可用的數據,而專用紙箱就至關於幀,且一個幀中一般只有一個數據包。
 

7.TCP數據流(TCP stream)

Wireshark中是這麼定義的:相同四元組(源地址,源端口,目的地址,目的端口)的包就爲一條TCP流,即一條流有不少個包。
數據報和數據流

緩衝區

每一個 socket 被建立後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區。

write()/send() 並不當即向網絡中傳輸數據,而是先將數據寫入緩衝區中,再由TCP協議將數據從緩衝區發送到目標機器。一旦將數據寫入到緩衝區,函數就能夠成功返回,無論它們有沒有到達目標機器,也無論它們什麼時候被髮送到網絡,這些都是TCP協議負責的事情。

TCP協議獨立於 write()/send() 函數,數據有可能剛被寫入緩衝區就發送到網絡,也可能在緩衝區中不斷積壓,屢次寫入的數據被一次性發送到網絡,這取決於當時的網絡狀況、當前線程是否空閒等諸多因素,不禁程序員控制。

read()/recv() 函數也是如此,也從輸入緩衝區中讀取數據,而不是直接從網絡中讀取。

這些I/O緩衝區特性可整理以下:

1.I/O緩衝區在每一個TCP套接字中單獨存在;
2.I/O緩衝區在建立套接字時自動生成;
3.即便關閉套接字也會繼續傳送輸出緩衝區中遺留的數據;
4.關閉套接字將丟失輸入緩衝區中的數據。

輸入輸出緩衝區的默認大小通常都是 8K,能夠經過 getsockopt() 函數獲取:

1.unsigned optVal;
2.int optLen = sizeof(int);
3.getsockopt(servSock, SOL_SOCKET, SO_SNDBUF,(char*)&optVal, &optLen);
4.printf("Buffer length: %d\n", optVal);

socket緩衝區解釋
View Code

緩衝區的做用

先結束的進程能夠把結果放入緩衝區內,進行下面的工做,然後作完的進程能夠從緩衝區內取出原來的數據繼續工做。
緩衝區的做用是:在高速和低速設備之間起一個速度平滑做用;暫時存儲數據;常常訪問的數據能夠放進緩衝區,減小對慢速設備的訪問以提升系統的效率。提升上傳和下載的速度

緩衝(buffering)
利用存儲區緩解數據到達速度與離去速度不一致而採用的技術稱爲緩衝,此時同一數據只包含一個拷貝。例如:操做系統以緩衝方式實現設備的輸入和輸出操做主要是緩解處理機與設備之間速度不匹配的矛盾,從而提升資源利用律和系統效率。
View Code

 

返回系列

相關文章
相關標籤/搜索