【python 第13日】網絡通訊socket socketserver

須要弄清的幾個問題:python

一、何時用tcp,何時用udp?linux

二、tcp快仍是udp快?nginx

三、視頻,直播用udp仍是tcp,效果差別有多少?安全性能有多少?有待完善web

四、若是解決粘包問題算法

五、一次傳輸多少,會形成緩衝區丟包apache

六、在linux和windows裏,對於空包,回撤,和直接斷開鏈接的處理不一樣編程

七、端口被佔用了怎麼辦?windows

八、能不能直接操做數據緩衝區,以防丟包緩存

九、測試一下用tcp和udp傳輸一個G的文件,多久能傳完,傳輸命令安全

本文結構

socket定義------->TCP------------>UDP--------------->多任務-------------------->socketserver

socket

socket 套接字

socket(簡稱 套接字) 是進程間通訊一個工具,它能實現把數據從一方傳輸到另一方,完成不一樣電腦上進程之間的通訊, 它比如數據的搬運工

基本流程:

簡單的服務端和客戶端

##############################服務器端
import socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()             #創建一個套接字,用來接收請求
sk.bind(ip_port)                 #綁定一個端口
sk.listen(2)                     #接聽,最大能候補接聽5個

conn, addr = sk.accept()         #conn是鏈接的套接字, addr是對方的ip_port
print(conn, addr)                #<socket.socket fd=616, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 56779)> ('127.0.0.1', 56779)

conn.sendall(bytes("hello world", encoding="utf8"))     #必定給conn發送數據,發送的是字節,
print("發送完畢")

############################客戶端
import  socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)                       #創建一個套接字,鏈接服務器端口

print(sk.recv(1024).decode("utf8"))       #等待接收

 socket經常使用函數以及屬性

基本參數:

Socket函數使用的格式爲:socket(family,type[,protocol])

  參數一:family 指定應用程序使用的通訊協議的協議族,對於TCP/IP協議族,該參數爲AF_INET,如下爲該參數的一些經常使用選項

  參數二:type 是要建立套接字的類型,如下爲該參數的一些經常使用選項
 

   參數三:protocol 指明所要接收的協議類型,一般爲0或者不填。

如下爲該參數的一些經常使用選項

使用例子:

   建立TCP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

   建立UDP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

經常使用函數:

 

sk.bind(address)
  s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog)
  開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量。
      backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5
      這個值不能無限大,由於要在內核中維護鏈接隊列
sk.setblocking(bool)
  是否阻塞(默認True),若是設置False,那麼accept和recv時一旦無數據,則報錯。
sk.accept()
  接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址。
  接收TCP 客戶的鏈接(阻塞式)等待鏈接的到來
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表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )
sk.getpeername()
  返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。
sk.getsockname()
  返回套接字本身的地址。一般是一個元組(ipaddr,port)
sk.fileno()
  套接字的文件描述符

 setsockopt()

python定義了setsockopt()和getsockopt(),一個是設置選項,一個是獲得設置。這裏主要使用setsockopt(),具體結構以下:

setsockopt(level,optname,value)

level定義了哪一個選項將被使用。一般狀況下是SOL_SOCKET,意思是正在使用的socket選項。它還能夠經過設置一個特殊協議號碼來設置協議選項,然而對於一個給定的操做系統,大多數協議選項都是明確的,因此爲了簡便,它們不多用於爲移動設備設計的應用程序。

optname參數提供使用的特殊選項。關於可用選項的設置,會由於操做系統的不一樣而有少量不一樣。若是level選定了SOL_SOCKET,那麼一些經常使用的選項見下表:

選項

意義

指望值

SO_BINDTODEVICE

可使socket只在某個特殊的網絡接口(網卡)有效。也許不能是移動便攜設備

一個字符串給出設備的名稱或者一個空字符串返回默認值

SO_BROADCAST

容許廣播地址發送和接收信息包。只對UDP有效。如何發送和接收廣播信息包

布爾型整數

SO_DONTROUTE

禁止經過路由器和網關往外發送信息包。這主要是爲了安全而用在以太網上UDP通訊的一種方法。無論目的地址使用什麼IP地址,均可以防止數據離開本地網絡

布爾型整數

SO_KEEPALIVE

可使TCP通訊的信息包保持連續性。這些信息包能夠在沒有信息傳輸的時候,使通訊的雙方肯定鏈接是保持的

布爾型整數

SO_OOBINLINE

能夠把收到的不正常數據當作是正常的數據,也就是說會經過一個標準的對recv()的調用來接收這些數據

布爾型整數

SO_REUSEADDR

當socket關閉後,本地端用於該socket的端口號馬上就能夠被重用。一般來講,只有通過系統定義一段時間後,才能被重用。

布爾型整數

 

本節在學習時,用到了SO_REUSEADDR選項,具體寫法是:

S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 這裏value設置爲1,表示將SO_REUSEADDR標記爲TRUE,操做系統會在服務器socket被關閉或服務器進程終止後立刻釋放該服務器的端口,不然操做系統會保留幾分鐘該端口。

setsockopt(level,optname,value)

level指定控制套接字的層次.能夠取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_IP:IP選項.
3)IPPROTO_TCP:TCP選項. 
optname指定控制的方式(選項的名稱),咱們下面詳細解釋 

optval得到或者是設置套接字選項.根據選項名稱的數據類型進行轉換 


選項名稱        說明                  數據類型
========================================================================
            SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST      容許發送廣播數據            int
SO_DEBUG        容許調試                int
SO_DONTROUTE      不查找路由               int
SO_ERROR        得到套接字錯誤             int
SO_KEEPALIVE      保持鏈接                int
SO_LINGER        延遲關閉鏈接              struct linger
SO_OOBINLINE      帶外數據放入正常數據流         int
SO_RCVBUF        接收緩衝區大小             int
SO_SNDBUF        發送緩衝區大小             int
SO_RCVLOWAT       接收緩衝區下限             int
SO_SNDLOWAT       發送緩衝區下限             int
SO_RCVTIMEO       接收超時                struct timeval
SO_SNDTIMEO       發送超時                struct timeval
SO_REUSERADDR      容許重用本地地址和端口         int
SO_TYPE         得到套接字類型             int
SO_BSDCOMPAT      與BSD系統兼容              int
========================================================================
            IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL       在數據包中包含IP首部          int
IP_OPTINOS       IP首部選項               int
IP_TOS         服務類型
IP_TTL         生存時間                int
========================================================================
            IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG       TCP最大數據段的大小           int
TCP_NODELAY       不使用Nagle算法             int
========================================================================

shutdown()

socket.makefile

socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None,newline=None), 返回一個文件對象,具體類型與參數相關,除了只能指定‘r’,‘w’,'b'模式外,與open()函數同樣.

前提條件是socket必須是阻塞模式,它能夠含有一個超時間(指socket),若是發生超時,內部緩衝區會在不一致的狀態中關閉。

關閉文件對象不會關閉socket,除非調用了socket.close()方法,或者全部其餘文件對象都關閉了。

注意:在win上,makefile建立的一個類文件對象在須要文件描述符的文件對象的狀況下是沒法使用的(意思大概是makefile建立的文件對象沒有文件描述符)


socket.dup(), 複製一個socket

socket.detach()

,將socket 對象設置爲關閉狀態,但底層的文件描述符並沒關閉,仍能夠進行操做,返回值爲文件描述符。

socket.get_inheritable()

測試socket是否能夠繼承,True或者False

socket.recvmsg(bufsize[, ancbufsize[, flags]])

socket.recvmsg(bufsize[, ancbufsize[, flags]]), 接收常規文件接收常規文件,附件等。ancbufsize設定附件接收緩衝區,相似於bufsize。但返回值是有四個元素的元組()。(data, ancdata, msg_flags, address). ancdata是一個含有四個0的列表或者是(cmsg_level, cmsg_type, cmsg_data)這樣的列表。

scoket.share()

share(process_id) -> bytes ,給另一個程序傳遞本身的二進制,別的程序能夠接收,經過socket.fromshare()變成一個套接字進行通訊

import socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()             #創建一個套接字,用來接收請求
sk.bind(ip_port)                 #綁定一個端口
sk.listen(2)                     #接聽,最大能接聽5個

coon, addr = sk.accept()
print(coon.getpeername())                              #('127.0.0.1', 64808)
print(coon.getsockname())                              #('127.0.0.1', 9999)
# print(socket.gethostbyaddr('112.80.248.76'))           #('www.baidu.com', [], ['112.80.248.76'])
print(socket.gethostbyname("www.baidu.com"))           #112.80.248.76
print(socket.gethostbyname_ex("www.baidu.com"))        #('www.baidu.com', [], ['112.80.248.75'])
print(socket.getservbyport(80))                        #http
print(sk.get_inheritable())                            #False   實例纔有這個屬性
sk2 = sk.dup()
print(sk2)                                             #<socket.socket fd=712, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>

TCP

 socket模式是TCP的,經常使用函數 send, sendall, recv

 當發送爲空的時候再也不接收卡住,不知道是3.7改進了仍是3.5的問題

服務端:socket()---------->綁定-------->監聽listen------------>等待連接accept ----------->接收數據

客戶端:socket()---------------->鏈接服務端connect------------>發送數據

sk.setblocking(bool)   設置True 阻塞,設置爲False, 遇到阻塞accept(), recv()就報錯

############服務端################
import socket
ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #創建一個套接字,用來接收請求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容許端口複用
sk.bind(ip_port)                 #綁定一個端口

sk.listen(2)                     #接聽,最大能接聽5個

while True:
    conn, addr = sk.accept()
    print("如下客戶端要鏈接主機:",conn, addr)
    conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)                   # 禁用nagle算法

    conn.sendall("你好,我是機器人1".encode("utf8"))
    conn.sendall("你好,我是機器人2".encode("utf8"))
    conn.sendall("你好,我是機器人3".encode("utf8"))              #第一次鏈接完不會粘包,第二三次會,多是時間緣由,若是對方睡眠一秒,則一次收到3個


    #進行某個信息處理
    while True:
        data_recv = conn.recv(BUFFERSIZE).decode("utf8")
        print("服務器收到數據:",data_recv)
        data_send = input("你要問的問題")
        conn.sendall(data_send.encode("utf8"))

    conn.close()

sk.shutdown()
sk.close()

############客戶端###########
import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8196

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(ip_port)                       #創建一個套接字,鏈接服務器端口

time.sleep(0.3)
data = sk.recv(BUFFERSIZE)
print("客戶端收到》》》》:",data.decode("utf8"))

while 1:
    data = input("請問有什麼問題")
    sk.sendall(data.encode("utf8"))
    data_recv = sk.recv(BUFFERSIZE)
    print("客戶端收到》》》》:", data_recv.decode("utf8"))

sk.close()
聊天機器人

遇到的問題:

  1. 怎麼關閉nagle, 用conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1),剛開始覺得沒弄成功,結果發現客戶端接收的時候是流式接收,因此仍是混在一塊兒,然並卵
  2. 怎麼讓端口複用sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    剛開始覺得是複製形式,後來不知道記錯了沒有,仍是這一個好用
#############服務器
import socket
ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #創建一個套接字,用來接收請求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容許端口複用
sk.bind(ip_port)                 #綁定一個端口

sk.listen(2)                     #接聽,最大能接聽5個


conn, addr = sk.accept()
print("如下客戶端要鏈接主機:",conn, addr)
size  = conn.recv(1024).decode("utf8")
print("接受到的數據大小爲:",size)

while True:
    conn.sendall("開始傳送".encode("utf8"))
    print("讓客戶端開始發送")
    has_size = 0
    with open("2.jpg", "wb") as f:
        while has_size < int(size) :
            data_recv = conn.recv(1024)
            f.write(data_recv)
            has_size += len(data_recv)
    print("接收完畢", has_size, size)
    if has_size == int(size):
        conn.sendall("接收完畢".encode("utf8"))
        break

conn.close()


###############客戶端
import  socket
import os
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)

#先發一個文件大小
size = os.stat("1.jpg").st_size
print("要發送的文件大小爲:",size)
sk.sendall(str(size).encode("utf8"))
print("發送文件大小完畢")

while True:
    data_recv = sk.recv(1024).decode("utf8")
    print("接收到服務器發送來的數據爲:",data_recv)
    if data_recv == "開始傳送":
        with open("1.jpg", "rb") as f :
            for line in f:
                sk.sendall(line)
    print("發送完畢")
    data_recv = sk.recv(1024).decode("utf8")
    print("等待接收是否收完:", data_recv)
    if data_recv =="接收完畢":
        break

sk.close()
發送照片

UDP

服務端:socket()------->綁定------------>等待接收數據,而後處理

客服端:socket()------->直接發

UDP發包,要確保丟包率,搞策略確保重發

#服務端
import socket
ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE =8196


udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)             #創建一個套接字,用來接收請求
udp.bind(ip_port)                 #綁定一個端口

print("--------------")
#直接進入通訊
data, addr= udp.recvfrom(1024)
print(data.decode("utf8"), addr)
data, addr= udp.recvfrom(1024)
print(data.decode("utf8"), addr)
udp.close()

#客戶端

import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8196
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# udp.sendto(b'hello', ip_port)
udp.sendto("".encode("utf8"), ip_port)
udp.sendto(b"hello", ip_port)
udp.close()
UDP流程 

TCP和UDP包

最大包,最小包

報文封裝總體結構

報文總體結構

其中以太網(Ethernet)的數據幀在鏈路層   
IP包在網絡層   
TCP或UDP包在傳輸層   
TCP或UDP中的數據(Data)在應用層   
它們的關係是 數據幀{IP包{TCP或UDP包{Data}}}   

    不一樣的協議層對數據包有不一樣的稱謂,在傳輸層叫作段(segment),在網絡層叫作數據報(datagram),在鏈路層叫作幀(frame)。數據封裝成幀後發到傳輸介質上,到達目的主機後每層協議再剝掉相應的首部,最後將應用層數據交給應用程序處理。

 

在應用程序中咱們用到的Data的長度最大是多少,直接取決於底層的限制。   
咱們從下到上分析一下:   
1.在鏈路層,由以太網的物理特性決定了數據幀的長度爲(46+18)-(1500+18),其中的18是數據幀的頭和尾,也就是說數據幀的內容最大爲1500(不包括幀頭和幀尾),即MTU(Maximum Transmission Unit)爲1500;  
2.在網絡層,由於IP包的首部要佔用20字節,因此這的MTU爲1500-20=1480; 
3.在傳輸層,對於UDP包的首部要佔用8字節,因此這的MTU爲1480-8=1472;   
因此,在應用層,你的Data最大長度爲1472。當咱們的UDP包中的數據多於MTU(1472)時,發送方的IP層須要分片fragmentation進行傳輸,而在接收方IP層則須要進行數據報重組,因爲UDP是不可靠的傳輸協議,若是分片丟失致使重組失敗,將致使UDP數據包被丟棄。  
從上面的分析來看,在普通的局域網環境下,UDP的數據最大爲1472字節最好(避免分片重組)。   
但在網絡編程中,Internet中的路由器可能有設置成不一樣的值(小於默認值),Internet上的標準MTU值爲576,因此Internet的UDP編程時數據長度最好在576-20-8=548字節之內。
 

二、TCP、UDP數據包最大值的肯定     

        UDP和TCP協議利用端口號實現多項應用同時發送和接收數據。數據經過源端口發送出去,經過目標端口接收。有的網絡應用只能使用預留或註冊的靜態端口;而另一些網絡應用則可使用未被註冊的動態端口。由於UDP和TCP報頭使用兩個字節存放端口號,因此端口號的有效範圍是從0到65535。動態端口的範圍是從1024到65535。  

        MTU最大傳輸單元,這個最大傳輸單元實際上和鏈路層協議有着密切的關係,EthernetII幀的結構DMAC+SMAC+Type+Data+CRC因爲以太網傳輸電氣方面的限制,每一個以太網幀都有最小的大小64Bytes最大不能超過1518Bytes,對於小於或者大於這個限制的以太網幀咱們均可以視之爲錯誤的數據幀,通常的以太網轉發設備會丟棄這些數據幀。

        因爲以太網EthernetII最大的數據幀是1518Bytes這樣,刨去以太網幀的幀頭(DMAC目的MAC地址48bits=6Bytes+SMAC源MAC地址48bits=6Bytes+Type域2Bytes)14Bytes和幀尾CRC校驗部分4Bytes那麼剩下承載上層協議的地方也就是Data域最大就只能有1500Bytes這個值咱們就把它稱之爲MTU。

 

UDP 包的大小就應該是 1500 - IP頭(20) - UDP頭(8) = 1472(Bytes)
TCP 包的大小就應該是 1500 - IP頭(20) - TCP頭(20) = 1460 (Bytes)

 

注*PPPoE所謂PPPoE就是在以太網上面跑「PPP」。隨着寬帶接入(這種寬帶接入通常爲Cable Modem或者xDSL或者以太網的接入),由於以太網缺少認證計費機制而傳統運營商是經過PPP協議來對撥號等接入服務進行認證計費的,因此引入PPPoE。PPPoE致使MTU變小了以太網的MTU是1500,再減去PPP的包頭包尾的開銷(8Bytes),就變成1492。不過目前大多數的路由設備的MTU都爲1500。

 

        若是咱們定義的TCP和UDP包沒有超過範圍,那麼咱們的包在IP層就不用分包了,這樣傳輸過程當中就避免了在IP層組包發生的錯誤;若是超過範圍,既IP數據報大於1500字節,發送方IP層就須要將數據包分紅若干片,而接收方IP層就須要進行數據報的重組。更嚴重的是,若是使用UDP協議,當IP層組包發生錯誤,那麼包就會被丟棄。接收方沒法重組數據報,將致使丟棄整個IP數據報。UDP不保證可靠傳輸;可是TCP發生組包錯誤時,該包會被重傳,保證可靠傳輸。

        UDP數據報的長度是指包括報頭和數據部分在內的總字節數,其中報頭長度固定,數據部分可變。數據報的最大長度根據操做環境的不一樣而各異。從理論上說,包含報頭在內的數據報的最大長度爲65535字節(64K)。

       咱們在用Socket編程時,UDP協議要求包小於64K。TCP沒有限定,TCP包頭中就沒有「包長度」字段,而徹底依靠IP層去處理分幀。這就是爲何TCP經常被稱做一種「流協議」的緣由,開發者在使用TCP服務的時候,沒必要去關心數據包的大小,只需講SOCKET看做一條數據流的入口,往裏面放數據就是了,TCP協議自己會進行擁塞/流量控制。 

       不過鑑於Internet(非局域網)上的標準MTU值爲576字節,因此建議在進行Internet的UDP編程時,最好將UDP的數據長度控制在548字節 (576-8-20)之內。

 

三、TCP、UDP數據包最小值的肯定

     在用UDP局域網通訊時,常常發生「Hello World」來進行測試,可是「Hello World」並不知足最小有效數據(64-46)的要求,爲何小於18個字節,對方仍然可用收到呢?由於在鏈路層的MAC子層中會進行數據補齊,不足18個字節的用0補齊。但當服務器在公網,客戶端在內網,發生小於18個字節的數據,就會出現接收端收不到數據的狀況。

       以太網EthernetII規定,以太網幀數據域部分最小爲46字節,也就是以太網幀最小是6+6+2+46+4=64。除去4個字節的FCS,所以,抓包時就是60字節。當數據字段的長度小於46字節時,MAC子層就會在數據字段的後面填充以知足數據幀長不小於64字節。因爲填充數據是由MAC子層負責,也就是設備驅動程序。不一樣的抓包程序和設備驅動程序所處的優先層次可能不一樣,抓包程序的優先級可能比設備驅動程序更高,也就是說,咱們的抓包程序可能在設備驅動程序尚未填充不到64字節的幀的時候,抓包程序已經捕獲了數據。所以不一樣的抓包工具抓到的數據幀的大小可能不一樣。下列是本人分別用wireshark和sniffer抓包的結果,對於TCP 的ACK確認幀的大小一個是54字節,一個是60字節,wireshark抓取時沒有填充數據段,sniffer抓取時有填充數據段。

       

四、實際應用

        用UDP協議發送時,用sendto函數最大能發送數據的長度爲:65535- IP頭(20) - UDP頭(8)=65507字節。用sendto函數發送數據時,若是發送數據長度大於該值,則函數會返回錯誤。  

        用TCP協議發送時,因爲TCP是數據流協議,所以不存在包大小的限制(暫不考慮緩衝區的大小),這是指在用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據並不必定會一次性發送出去,若是這段數據比較長,會被分段發送,若是比較短,可能會等待和下一次數據一塊兒發送。

ip報頭結構

ip頭部結構

版本號(Version):標明瞭IP 協議的版本號,目前的協議版本號爲4。下一代IP 協議的版本號爲6。

●報文長度:指 IP 包頭部長度,佔4 位。

●8 位的服務類型:包括一個3 位的優先權字段(COS,Class of Service),4 位TOS 字段和1 位未用位。4 位TOS 分別表明最小時延、最大吞吐量、最高可靠性和最小費用。

●總長度:是整個IP 數據報長度,包括數據部分。

●標識符:惟一地標識主機發送的每一份數據報。一般每發送一份報文它的值就會加1。

●生存時間:設置了數據包能夠通過的路由器數目。一旦通過一個路由器,TTL 值就會減1,當該字段值爲0 時,數據包將被丟棄。

●協議:肯定在數據包內傳送的上層協議,和端口號相似,IP 協議用協議號區分上層協議。TCP 協議的協議號爲6,UDP 協議的協議號爲17。

●報頭校驗和:計算IP 頭部的校驗和,檢查報文頭部的完整性。

●源IP 地址和目的IP 地址字段:標識數據包的源端設備和目的端設備。

●IP選項:通常格式爲1個字節的代碼,一個字節的長度,一個字節的指針,指針的值從1開始計數,指向IP選項的內容,通常其值爲4(跳過了前面的代碼&長度&指針的三個字節),長度包括前面3個字節在內的整個IP選項,最大值爲40。

TCP包

 這裏寫圖片描述 1.源端口和目的端口:各佔2個字節。

2.序號:佔4字節。序號範圍是0~2^32-1。TCP是面向字節流的,TCP鏈接中傳送的字節流中的每一個字節都按順序編號。整個要傳送的字節流的起始序號必需要在鏈接創建時設置。首部中的序號字段值指的是本報文段所發送的數據的第一個字節的序號。

3.確認號:4個字節,是指望收到對方下一個報文段的第一個數據字節的序號。 
若確認號=N,則代表:到序號N-1爲止的全部數據都已正確收到。 
4.數據偏移:4位。指出TCP報文段的數據起始處距離報文段的起始處有多遠。這個字段其實是指出TCP報文段的首部長度。因爲首部中還有長度不肯定的選項字段,所以數據偏移字段是必要的。單位是32位字,也就是4字節,4位二進制最大表示15,因此數據偏移也就是TCP首部最大60字節

5.保留:6位 
下面有6個控制位說明本報文段的性質

6.緊急URG:1位 
當URG=1時,代表緊急指針字段有效。它告訴系統此報文段中有緊急數據,應儘快傳送(至關於高優先級的數據),而不要按原來的排隊順序來傳送。例如,已經發送了很長的一個程序在遠地的主機上運行。但後來發現了一些問題,須要取消該程序的運行。所以用戶從鍵盤發出中斷命令(Control+c)。若是不使用緊急數據,那麼這兩個字符將存儲在接收TCP的緩存末尾。只有在全部的數據被處理完畢後這兩個字符才被交付接收方的應用進程。這樣作就浪費了許多時間。

當URG置爲1時,發送應用進程就告訴發送方的TCP有緊急數據要傳送。因而發送方TCP就把緊急數據插入到本報文段數據的最前面,而在緊急數據後面的數據仍時普通數據。這時要與首部中緊急指針字段配合使用。

7.確認ACK 
僅當ACK=1時確認號字段纔有效。當ACK=0時,確認號無效。TCP規定,在鏈接創建後全部的傳送的報文段都必須把ACK置1。

8.推送PSH 
當兩個應用進程進行交互式的通訊時,有時在一端的應用進程但願在鍵入一個命令後當即就能收到對方的響應。在這種狀況下,TCP就可使用推送操做。這時,發送方TCP把PSH置1,並當即建立一個報文段發送出去。接收方TCP收到PSH=1的報文段,就儘快地交付接收應用進程,而再也不等到整個緩存都填滿了後向上交付。

雖然應用程序能夠選擇推送操做,但推送還不多使用。

9.復位RST 
tcp鏈接出現嚴重差錯時釋放鏈接,而後從新創建鏈接。而能夠用來拒絕一個非法的報文段或拒絕打開一個鏈接。

當RST=1時,代表TCP鏈接中出現嚴重差錯(如因爲主機崩潰或其餘緣由),必須釋放鏈接,而後再從新創建運輸鏈接。RST置1還用來拒絕一個非法的報文段或拒絕打開一個鏈接。

10.同步SYN 
在鏈接創建時用來同步序號。當SYN=1而ACK=0時,代表這是一個鏈接請求報文段。對方若贊成創建鏈接,則應在相應的報文段中使用SYN=1和ACK=1。所以,SYN置爲1就表示這是一個鏈接請求或鏈接接受保溫。

11.終止FIN 
用來釋放一個鏈接。當FIN=1時,代表此報文段的發送方的數據已發送完畢,並要求釋放運輸鏈接。

12窗口 佔2字節。窗口值是【0,2^16-1]之間的整數。窗口指的是發送本報文段的一方的接收窗口(而不是本身的發送窗口)。窗口值告訴對方: 從本報文段首部中的確認號算起,接收方目前容許對方發送的數據量。之因此要有這個限制,是由於接收方的數據緩存空間是有限的。總之,窗口值做爲接收方讓發送方設置其發送窗口的依據。而且窗口值是常常在動態變化着。

13.檢驗和:2字節。檢驗範圍包括首部和數據兩部分。和UDP用戶數據報同樣,在計算校驗和 時,要在TCP報文段加上12字節的僞首部。

14.緊急指針:2字節。緊急指針僅在URG=1時纔有意義,它指出本報文段中的緊急數據的字節數(緊急數據結束後就是普通數據)。所以,緊急指針指出了緊急數據的末尾在報文段中的位置。當全部緊急數據都處理完時,TCP就告訴應用程序恢復到正常操做。值得注意的是,即便窗口爲零時也可發送緊急數據。

15.選項:長度可變,最長可達40字節。當沒有使用「選項」時,TCP的首部長度是20字節。 
1)MSS 最大報文段長度 
MSS最大報文段長度(數據字段的最大長度,默認是536字節)。MSS不宜設的太大也不宜設的過小。若選擇過小,極端狀況下,TCP報文段只含有1字節數據,在IP層傳輸的數據報的開銷至少有40字節(包括TCP報文段的首部和IP數據報的首部)。這樣,網絡的利用率就不會超過1/41。若TCP報文段很是長,那麼在IP層傳輸時就有可能要分解成多個短數據報片。在終點要把收到的各個短數據報片裝配成原來的TCP報文段。當傳輸出錯時還要進行重傳,這些也都會使開銷增大。

所以MSS應儘量大,只要在IP層傳輸時不須要再分片就行。在鏈接創建過程當中,雙方都把本身可以支持的MSS接入這一字段,之後就按照這個數值傳送數據。 
2)窗口擴大 
窗口擴大選項是爲了擴大窗口。TCP首部中窗口字段長度是16位,所以最大窗口大小就是64k字節。對於包含衛星信道的網絡多是不夠用的。能夠在雙方初始創建TCP鏈接的時候就進行協商。 
3)時間戳(計算RTT,防止序號繞回) 
A. 用來計算往返時間RTT。發送方在發送報文段時把當前時鐘的時間值放入時間戳字段,接收方在確認該報文段時把時間戳字段值複製到時間戳回送回答字段。所以,發送方在收到確認報文後,能夠準確地計算RTT來。 
4)選擇確認選項 

UDP 包

這裏寫圖片描述 
內容:16位源端口 16位目標端口
16位的UDP包長度 UDP頭部和UDP數據的總長度字節
16位頭部校驗和 此字段是可選項
僅提供端口號—-0-65535 1-1023註明端口 1024-65535動態端口(高端口)
客戶端訪問服務器時,隨機在高端口中分配一個進程號;做爲數據包中的源端口—用於區分客戶端上的程序進程;使用註明端口來做爲目標端口,用於告知所要訪問的服務;

這裏寫圖片描述 
UDP協議分爲首部字段和數據字段,其中首部字段只佔用8個字節,分別是個佔用兩個字節的源端口、目的端口、長度和檢驗和。

偏函數,struct

偏函數,用於固定經常使用函數的某些經常使用參數

from functools import partial
def sum(arg1, arg2):
    return arg1+arg2
if __name__ == '__main__':
    sum1 = partial(sum, 10)       #固定參數
    sum2 = partial(sum, arg2 = 1)       #固定某個參數
    print(sum1(2))
    print(sum2(2))
偏函數

 

struct 

pack(fmt,v1,v2...)<-------------------->unpack(fmt,bytes)

import struct
import ctypes
if __name__ == '__main__':
    s = struct.Struct('ii')
    s_b = s.pack(1,2)
    print(s.unpack(s_b))       #(1, 2)

    str_buf = ctypes.create_string_buffer(s.size)       #造一個緩存
    s.pack_into(str_buf, 0 , 1,2)             #把1,2按照偏移量0,存入緩存str_buf
    print(s.unpack_from(str_buf, 0))  
struct pack pack_into 使用方法

 

1.格式化對照表

2.字節順序,大小和校準

 

socketserver

解決的問題:不管是udp仍是tcp,只要不用線程,多我的聊天的時候是不能有阻塞的

socketserver流程

 socketserver聊天

TCP線程

import logging
import socketserver
import threading

DATEFMT = "%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATEFMT)
ip_port = ("127.0.0.1", 9999)

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print(self.__dict__)
        # print(self.request)
        # print(self.client_address)
        # print(self.server)
        # self.request.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 2)
        print("已經進入到handle",self.request)
        tlist =[]
        t1 = threading.Thread(target=self.read)
        t2 = threading.Thread(target=self.write)            #不能加參數,默認是傳遞了一個self
        tlist.append(t1)
        tlist.append(t2)
        for i in tlist:
            i.start()
        for i in tlist:                                     #必須阻塞,不然這個線程會退出,形成self.request不存在,子線程錯誤
            i.join()

    def read(self):
        while True:
            # print("正在執行read",self, self.request)
            data_recv = self.request.recv(1024)
            print("客戶端端傳來數據:", data_recv.decode("utf8"))


    def write(self):
        while True:
            # print("正在執行write",self.request)
            # data_send = input("輸入您要說的話》》》》》")
            data_send = input()
            print("%40s" % data_send)
            self.request.sendall(data_send.encode("utf8"))

if __name__ == '__main__':
    socketserver.ThreadingTCPServer.allow_reuse_address = True
    s = socketserver.ThreadingTCPServer(ip_port, MyServer, True)   #第三個bool值,爲真時候表示開啓綁定和監聽, 爲假表示只是建立一個套接字,並不綁定和監聽
    # print(s.__dict__)
    s.serve_forever()
服務器端 用線程實現
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(ip_port)
time.sleep(3)


def read(sock):
    while True:
        # print("正在執行read",sock)
        data_recv = sock.recv(1024)
        print("服務器端傳來數據:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在執行write",sock)
        # data_send = input("輸入您要說的話》》》》》")
        data_send = input()
        print("%40s" % data_send)
        sock.sendall(data_send.encode("utf8"))


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
客戶端 線程實現

注意:

  原本想用select實現,可是select對sys.stdin不支持,若是再寫一個套接字,又麻煩,因此用線程實現

  暫時不支持多人聊天,主機是發送消息,一次發給客戶端1,下一次是客戶端2, 能夠修改以下:在主線程開寫程序,服務端經過「0    發送消息」來提示給第幾個發送消息,須要從新修改T和readingTCPServer,在於怎麼獲取有幾個鏈接管理

import socketserver
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(ip_port)
time.sleep(3)


def read(sock):
    while True:
        # print("正在執行read",sock)
        data_recv = sock.recv(1024)
        print("服務器端傳來數據:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在執行write",sock)
        # data_send = input("輸入您要說的話》》》》》")
        data_send = input(">>>")
        print("%40s" % data_send)
        sock.sendall(data_send.encode("utf8"))


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
TCPserver修訂版 基本實現互撩的功能

 

UDP線程

#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周廣露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: stcp測試
@Created_Time: 2019/1/23 21:14
"""
import logging
import socketserver
import threading

DATEFMT = "%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATEFMT)
ip_port = ("127.0.0.1", 9999)

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print(self.client_address)
        # print(self.server)
        # print(self.__dict__)
        # self.request.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 2)
        print("已經進入到handle",self.request)

        #添加讀取和寫模塊
        tlist =[]
        t1 = threading.Thread(target=self.read)

        t2 = threading.Thread(target=self.write)            #不能加參數,默認是傳遞了一個self
        t2.setDaemon(True)                                 #讓寫變成守護線程和主程序死,讀程序退出便可

        tlist.append(t1)
        tlist.append(t2)
        for i in tlist:
            i.start()

        for i in tlist:                                     #必須阻塞,不然這個線程會退出,形成self.request不存在,子線程錯誤
            i.join()

    def read(self):
        num = 1
        try :
            while True:
                if num == 1:
                    print("客戶端端傳來第%d數據:"% num,self.request[0].decode("utf8"))
                else:
                    data_recv, addr = self.request[1].recvfrom(1024)
                    print(data_recv, addr)
                    print("客戶端端傳來第%d數據:" % num, data_recv.decode("utf8"))
                num = num + 1
        except  ConnectionResetError as e:
            print(e)

    def write(self):
        while True:
            # print("正在執行write",self.request)
            # data_send = input("輸入您要說的話》》》》》")
            data_send = input(">>>")
            print("%40s" % data_send)
            self.request[1].sendto(data_send.encode("utf8"), self.client_address)

if __name__ == '__main__':
    s = socketserver.ThreadingUDPServer(ip_port, MyServer)   #第三個bool值,爲真時候表示開啓綁定和監聽, 爲假表示只是建立一個套接字,並不綁定和監聽
    # print(s.__dict__)
    s.serve_forever()
UDP server
#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周廣露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: stcp測試
@Created_Time: 2019/1/23 21:14
"""
import socketserver
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.connect(ip_port)
time.sleep(3)


s.sendto(b'123', ip_port)

def read(sock):
    while True:
        # print("正在執行read",sock)
        data_recv = sock.recv(1024)
        print("服務器端傳來數據:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在執行write",sock)
        # data_send = input("輸入您要說的話》》》》》")
        data_send = input(">>>")
        print("%40s" % data_send)
        sock.sendto(data_send.encode("utf8"), ip_port)


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
UDP client

注意:

   不支持多人聊天,多人鏈接時候,都鏈接到一個handle裏面去了,說明UDP鏈接時候就開了一個鏈接

TCP進程和UDP進程

ForkingTCPServer在windows不存在,只有linux纔有,os.fork()

問題解決

怎麼解決粘包問題

在發送每一個包以前加上一個struct.pack('i',文件大小),接收端每次接受一些,直到接受完,而後再decode

發送的時候14M的字符串發送須要1秒,接受第一次很快不到0.015秒,第二次1秒多

import  socket
import struct
import time

ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE = 1024


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #創建一個套接字,用來接收請求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容許端口複用
sk.bind(ip_port)                 #綁定一個端口
sk.listen(2)                     #接聽,最大能接聽5個

conn, addr = sk.accept()
start = time.process_time()
count = 1
while True:
    data = conn.recv(8)
    size = struct.unpack('q', data)[0]
    print(size)
    has_length = 0
    data = b''
    if size:
        while size > has_length:
            data += conn.recv(size - has_length)
            has_length = len(data)
            print("收到數據長度",has_length)
        end = time.process_time()
        # print("第%d次發送帶來的是:  %s"%(count, data.decode("utf8")))
        print("用時間爲:", end - start)
        count += 1
TCP服務端 屢次發送 小包粘包 大包一塊兒發均可以解決
import socket
import struct
import time


def send_one(sock, data:str):
    start = time.process_time()
    data = data.encode("utf8")
    data_length = len(data)
    data_length_bin = struct.pack('q', data_length)
    sock.sendall(data_length_bin)
    sock.sendall(data)
    end = time.process_time()
    print("發送完畢",data_length)
    print("用時間爲:",end - start)


def send_more_times(data:str):
    pass


ip_port = ("127.0.0.1", 9999)
BUFFERSIZE = 1024

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(ip_port)  # 創建一個套接字,鏈接服務器端口

while True:
    send_one(sk, "吃飯了麼")
    send_one(sk, "吃飯了麼2")

    time.sleep(100000)
TCP客戶端 屢次發送 小包粘包 大包一塊兒發均可以解決

tcp快仍是udp快?

#########服務器端
import socket
import time

ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #創建一個套接字,用來接收請求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容許端口複用
sk.bind(ip_port)                 #綁定一個端口

sk.listen(2)                     #接聽,最大能接聽5個


conn, addr = sk.accept()
tc = time.process_time()
print("如下客戶端要鏈接主機:",conn, addr)
size  = conn.recv(1024).decode("utf8")
print("接受到的數據大小爲:",size)

while True:
    conn.sendall("開始傳送".encode("utf8"))
    print("讓客戶端開始發送")
    has_size = 0
    with open("2", "wb") as f:
        while has_size < int(size) :
            data_recv = conn.recv(8192)
            f.write(data_recv)
            has_size += len(data_recv)
    print("接收完畢", has_size, size)
    if has_size == int(size):
        conn.sendall("接收完畢".encode("utf8"))
        break
tc2 = time.process_time()
print("接受完畢時間爲:",tc2 - tc, has_size)
conn.close()

#########客戶端
import  socket
import os
import  time
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)

tc = time.process_time()
#先發一個文件大小
size = os.stat("1").st_size
print("要發送的文件大小爲:",size,"發送時間爲:",tc)
sk.sendall(str(size).encode("utf8"))
print("發送文件大小完畢")

i = 1
while True:
    data_recv = sk.recv(1024).decode("utf8")
    print("接收到服務器發送來的數據爲:",data_recv)
    if data_recv == "開始傳送":
        with open("1", "rb") as f :
            while True:
                s = f.read(8000)
                if not s:
                    break
                sk.sendall(s)
            # for line in f:
            #     if len(line) > 10000:
            #         print("接受到的長度:", len(line))
            #     sk.sendall(line)              #tcp長度超過1500的能夠斷開發,可是udp不行
    print("發送完畢")
    data_recv = sk.recv(1024).decode("utf8")
    print("等待接收是否收完:", data_recv)
    if data_recv =="接收完畢":
        print("第%d次傳送完畢", i)
        break
    i += 1
tc2 = time.process_time()
print("發送完畢時間:",tc2- tc )
sk.close()
tcp發送文件,100M大約1秒,可是不用確保是否傳錯,若是用for line in f 大約18秒,因此用讀取read更快
#######udp服務器
import socket
import time

ip_port = ("127.0.0.1", 9999)                                 #當再虛擬機上運行的時候,不能夠寫127.0.0.1, 必須寫局域網地址
BUFFERSIZE =8192

udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.bind(ip_port)  # 綁定一個端口


tc = time.process_time()
size, addr = udp.recvfrom(1024)
print("接受到的數據大小爲:", size.decode("utf8"))

while True:
    udp.sendto("開始傳送".encode("utf8"), addr)
    print("讓客戶端開始發送")
    tc = time.process_time()
    has_size = 0
    with open("2", "wb") as f:
        num = 0
        while has_size < int(size) * 0.93:
            data_recv, addr = udp.recvfrom(BUFFERSIZE)
            num += 1
            has_size += len(data_recv)
            # print("接受到部分數據:", data_recv[0:3], len(data_recv), has_size, num )
            f.write(data_recv)

    print("接收完畢", has_size, size)
    if has_size >= int(size) * 0.93 :
        udp.sendto("接收完畢".encode("utf8"), addr)
        break
tc2 = time.process_time()
print("接受完畢時間爲:", tc2 - tc, has_size)

udp.close()


########udp 客戶端
#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周廣露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: server
@Created_Time: 2019/1/21 22:10
"""
import os
import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8000

udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.connect(ip_port)

tc = time.process_time()
#先發一個文件大小
size = os.stat("1").st_size
print("要發送的文件大小爲:",size,"發送時間爲:",tc)
udp.sendto(str(size).encode("utf8"), ip_port)
print("發送文件大小完畢")

i = 1
while True:

    data_recv,addr  = udp.recvfrom(1024)
    tc = time.process_time()
    print("接收到服務器發送來的數據爲:",data_recv.decode("utf8"),addr)
    if data_recv.decode("utf8") == "開始傳送":
        num = 0
        max = 0
        with open("1", "rb") as f :
            while True:
                s = f.read(BUFFERSIZE)
                if s:
                    udp.send(s)
                    num = num + 1
                    # print("發送了:", num, num*BUFFERSIZE)
                else:
                    break
    #         has_length = 0
    #         for line in f:
    #             length = len(line)
    #             left_len = length
    #             has_len = 0
    #             if length > max:
    #                 max = length
    #             while left_len > 0 :
    #                 if left_len > BUFFERSIZE:
    #                     udp.sendall(line[has_len: has_len + BUFFERSIZE + 1])
    #                     has_length = has_length + BUFFERSIZE
    #                     # print("has_length1:",has_length)
    #                     num = num + 1
    #                     has_len = has_len + BUFFERSIZE
    #                 else:
    #                     udp.sendall(line[has_len:])
    #                     has_length = has_length + len(line[has_len:])
    #                     # print("has_length2:",has_length)
    #                     has_len = has_len + len(line[has_len:])
    #                     num = num + 1
    #                 left_len = left_len - BUFFERSIZE
    #                 has_len = has_len + BUFFERSIZE
    #                 print("發送長度:", len(line), has_length, num)
    print("發送完畢",max)
    data_recv, addr  = udp.recvfrom(BUFFERSIZE)
    print("等待接收是否收完:", data_recv.decode("utf8"))
    if data_recv.decode("utf8") =="接收完畢":
        print("第%d次傳送完畢", i)
        break
    i += 1
tc2 = time.process_time()
print("發送完畢時間:",tc2- tc )
udp.close()
udp 傳送文件100M大約0.6秒,可是須要確認丟包,只能保證接受95%而已

端口複用

socketserver中端口複用是設置

socketserver.ThreadingTCPServer.allow_reuse_address = True

 

設置緩衝區大小網絡

TCP有自動控制網絡擁塞,UDP沒有控制擁塞機制

 默認緩衝區,接受和發送的默認是65536

print(sk.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))      #65536
print(sk.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))       #65536
sk.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 100000)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 100000)
默認緩衝區大小 設置緩衝區大小

 

擴展怎麼本身填包,包頭

利用setsockopt來設置 

何時用tcp,何時用udp?

那麼如何選擇tcp仍是udp?

先看下人家怎麼選

1 HTTP,  http協議如今已經深刻影響到咱們的方方面面,重要性就不說了, 它採用的是tcp 協議,爲何使用tcp, 由於它傳輸的內容是不能夠出現丟失,亂序等各類錯誤的,其次它須要跨平臺實現,而tcp知足了這個要求,發展到今天,http享受了tcp帶來的簡潔高效和跨平臺,可是也承受了tcp的各類缺點,例如缺乏tcp keep alive機制[這個實際上是後來添加的支持,並不是廣泛實現], tcp協議棧的實現問題引起的難以支持海量用戶併發鏈接[只能經過dns等級別的集羣或者cdn來實現],協議太複雜致使很難模塊化處理[其實這個問題已經在nginx解決了,nginx經過模塊化和對協議的分段處理機制,並引入消息機制,避免了多進程[線程]的頻繁切換,相比apache等老牌web服務器軟件,在應對大量用戶上擁有極大的優點。 即便站在今天的角度看,http也確實應該選擇tcp.

2 FTP, 這個協議比http更加古老,它採用的也是tcp協議, 由於它的每個指令,或者文件傳輸的數據流,都須要保證可靠性,同時要求在各類平臺上普遍支持,那麼就只能選擇tcp, 和http不一樣,它採用了noop指令機制來處理tcp缺乏keep alive機制帶來的問題,也就是客戶端必須每過一段時間,若是沒有發送其餘指令,就必須發送一個noop指令給服務器,避免被服務器認爲是死鏈接。 Ftp的缺陷在哪裏呢?,其次它的文件傳輸是採用新的數據鏈接來執行,等於1個用戶須要2個鏈接,其次當一個文件正在傳輸的時候,你沒法進行其餘操做,例如列表,也許你能夠把它看成是一一對應的典範,由於這樣咱們能夠直接用命令行進行控制,可是不少用戶實際上是須要在下載的時候同時進行列表等操做的,爲了解決這個問題,不少客戶端只要開啓多個指令鏈接[和數據鏈接],這樣一來,無形中額外帶給了Ftp服務器不少壓力,而採用udp可靠傳輸就不存在這個問題,可是udp可靠傳輸是沒有跨平臺支持的,這樣是魚和熊掌不可兼得,對於這樣一個簡單的開放協議的實現,tcp是個好選擇。

3 POP3/SMTP, 常見的郵件協議,沒什麼好說的,反應--應答模式,跨平臺要求,所以tcp是個選擇,這個協議的悲劇在於,當初沒有考慮到郵件附件會愈來愈大的問題,所以它的實現中將附件文件採用了base64編碼格式,用文本模式進行發送,致使產生了大量的額外流量。

4 TFTP ,這是一個很是古老的用於內部傳輸小文件的協議,沒有FTP那麼多功能,採用的是udp協議,經過在包中加入包頭信息,用盡量簡單的代碼來實現小文件傳輸,注意是小文件,是一個值得參考的udp改造應用範例.

5 一般的voip,實時視頻流等,一般會採用udp協議,這是之內這些應用能夠容許丟包,不少人可能認爲是udp的高效率因此纔在這方面有普遍應用,這我不敢苟同,我我的認爲,之因此採用udp,是由於這些傳輸容許丟包,這是一個最大的前提

那麼如今來概括一下

[1] 若是數據要求完整,不容許任何錯誤發生

     [A] 應用層協議開放模式 [例如http ftp]

           建議選擇tcp,幾乎是惟一選擇.

    [B] 應用曾協議封閉模式 [例如遊戲]

         (1) 大量鏈接

               [a] 長鏈接  

                    [一] 少許數據傳輸

                           優先考慮可靠udp傳輸 , tcp建議在20000鏈接如下使用.

                   [二] 大流量數據傳輸

                          只有在10000鏈接如下能夠考慮tcp , 其餘狀況優先使用udp可靠傳輸    

               [b] 短鏈接

                  [一] 少許數據傳輸

                         建議使用udp標準模式, 加入序列號, 若是鏈接上限不超2萬,能夠考慮tcp

                 [二] 大流量數據傳輸

                       10000鏈接如下考慮tcp ,其餘狀況使用udp可靠傳輸

               在遇到海量鏈接的狀況下,建議優先考慮udp可靠傳輸,使用tcp,因爲tcp/ip棧的鏈表實現的影響,鏈接越多,性能降低越快,而udp能夠實現隊列,是一條平滑的直線,幾乎沒有性能影響.

         (2) 有限鏈接 [一般小於2000 , 通常每服務器爲幾百到1000左右]

                  [a] 長鏈接  

                        除非有數據的實時性要求,優先考慮tcp,不然使用udp可靠傳輸.        

                 

               [b] 短鏈接

                      優先考慮tcp.

              在有限鏈接的狀況下,使用tcp能夠減小代碼的複雜性,增長普遍的移植性,而且不須要考慮性能問題.  

[2] 容許丟包,甚至能夠亂序

           [a] 對實時性要求比較高,例如voip , 那麼udp是最優選擇.

           [b] 部分數據容許丟包,部分數據要求完整,部分有實時性要求,一般這樣的需求是出如今遊戲裏, 這時候, 基於udp協議改造的udp多路可靠傳輸[同時支持不可靠模式],基本是惟一能知足的,固然也可使用tcp來傳輸要求完整的數據,可是一般不建議,由於同時使用tcp和udp傳輸數據,會致使代碼臃腫複雜度增長,udp可靠傳輸徹底能夠替代tcp.

           [c]部分數據優先傳輸,部分可丟棄數據在規定時效內傳輸, 這一般是實時視頻流, 在有限鏈接模式下,能夠考慮tcp+udp , 可是一般, 可靠udp傳輸是最好的選擇.     

總結:

  要求不丟包的、兼容性強的、連接在10000如下的就用TCP,時效性高的、連接數高的用udp 

tcp快仍是udp快?

傳送100M的文件tcp發送大約用時間1秒,接受也差很少1秒,是按照每次8000接的,若是改用最大發,應該更快

視頻,直播用udp仍是tcp,效果差別有多少?安全性能有多少?有待完善

  總結找到的內容,應該說:

  1. 網頁上的視頻是基於HTTP/HTTPS,傳輸層是TCP

  2. QQ視頻聊天等是基於UDP

  3. 甚至有的應用使用p2p協議,傳輸層應該也是TCP

  4. 經過http進行流化視頻有不少種方法

  5. 傳輸視頻還有不少其餘的應用層協議

  一方面,在網頁上看視頻能夠忍受緩衝5s看到更清楚的視頻,因此用TCP問題不大,在網絡狀況較好的狀況下更是如此。視頻聊天時毫不能容忍等待5s才聽到對方的回話,因此用UDP更合適。使用TCP仍是UDP是在網絡條件有限的狀況下對「實時性」和「傳輸質量」之間的權衡,不是必須用TCP或者UDP。

一次傳輸多少,會形成緩衝區丟包

UDP纔有丟包,大約95%左右,一次緩衝區爲65536,能夠修改

在linux和windows裏,對於空包,回撤,和直接斷開鏈接的處理不一樣

對於直接回撤的空包,tcp和udp在python3.7都能直接接收

相關文章
相關標籤/搜索