Python3學(xué)習(xí)筆記_part_C

協(xié)程

協(xié)程,又稱微線程圆裕,纖程广鳍。英文名Coroutine荆几。

協(xié)程其實可以認(rèn)為是比線程更小的執(zhí)行單元。 為啥說他是一個執(zhí)行單元赊时,因為他自帶CPU上下文吨铸。這樣只要在合適的時機, 我們可以把一個協(xié)程 切換到另一個協(xié)程祖秒。 只要這個過程中保存或恢復(fù) CPU上下文那么程序還是可以運行的诞吱。

通俗的理解:在一個線程中的某個函數(shù),可以在任何地方保存當(dāng)前函數(shù)的一些臨時變量等信息竭缝,然后切換到另外一個函數(shù)中執(zhí)行房维,注意不是通過調(diào)用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定

協(xié)程和線程差異

線程切換從系統(tǒng)層面遠(yuǎn)不止保存和恢復(fù) CPU上下文這么簡單歌馍。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù)握巢,操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。 所以線程的切換非常耗性能松却。但是協(xié)程的切換只是單純的操作CPU的上下文暴浦,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住。

協(xié)程問題

系統(tǒng)并不感知晓锻,所以操作系統(tǒng)不會幫你做切換歌焦。 那么誰來幫你做切換?讓需要執(zhí)行的協(xié)程更多的獲得CPU時間才是問題的關(guān)鍵砚哆。

例子

目前的協(xié)程框架一般都是設(shè)計成 1:N 模式独撇。所謂 1:N 就是一個線程作為一個容器里面放置多個協(xié)程。 那么誰來適時的切換這些協(xié)程躁锁?答案是有協(xié)程自己主動讓出CPU纷铣,也就是每個協(xié)程池里面有一個調(diào)度器, 這個調(diào)度器是被動調(diào)度的战转。意思就是他不會主動調(diào)度搜立。而且當(dāng)一個協(xié)程發(fā)現(xiàn)自己執(zhí)行不下去了(比如異步等待網(wǎng)絡(luò)的數(shù)據(jù)回來,但是當(dāng)前還沒有數(shù)據(jù)到)槐秧, 這個時候就可以由這個協(xié)程通知調(diào)度器啄踊,這個時候執(zhí)行到調(diào)度器的代碼,調(diào)度器根據(jù)事先設(shè)計好的調(diào)度算法找到當(dāng)前最需要CPU的協(xié)程刁标。 切換這個協(xié)程的CPU上下文把CPU的運行權(quán)交個這個協(xié)程颠通,直到這個協(xié)程出現(xiàn)執(zhí)行不下去需要等等的情況,或者它調(diào)用主動讓出CPU的API之類膀懈,觸發(fā)下一次調(diào)度顿锰。

這個實現(xiàn)有一個問題:假設(shè)這個線程中有一個協(xié)程是CPU密集型的他沒有IO操作, 也就是自己不會主動觸發(fā)調(diào)度器調(diào)度的過程,那么就會出現(xiàn)其他協(xié)程得不到執(zhí)行的情況撵儿, 所以這種情況下需要程序員自己避免乘客。

# 協(xié)程簡單例子
from time import sleep

def A():
    a = 1
    while a<4:
        print('A a = {}'.format(a))
        yield a
        a += 1
        sleep(0.3)

def B(c):
    while True:
        try:
            print('B'.center(20, '*'))
            next(c)
            sleep(0.3)
        except StopIteration:
            break

if __name__ == '__main__':
    a = A()
    B(a)
*********B**********
A a = 1
*********B**********
A a = 2
*********B**********
A a = 3
*********B**********
# greenlet 版協(xié)程
# -*- coding: utf-8 -*-

from greenlet import greenlet
from time import sleep

def A():
    a = 1
    while a<4:
        print('A a = {}'.format(a).center(20, '*'))
        gr2.switch() # 切換至B()
        a += 1
        sleep(0.3)

def B():
    b = 1
    while True:
        print('B'.center(20, '*'))
        gr1.switch()
        sleep(0.3)
        
gr1 = greenlet(A)
gr2 = greenlet(B)

gr1.switch()
    
******A a = 1*******
*********B**********
******A a = 2*******
*********B**********
******A a = 3*******
*********B**********
'''grevent:當(dāng)遇到IO時,自動切換'''
# 使用例子
# -*- coding: utf-8 -*-

import gevent

def test(n):
    for i in range(n):
        print('this is "{}" at NO.{}'.format(gevent.getcurrent(), i))

g1 = gevent.spawn(test, 3)
g2 = gevent.spawn(test, 3)

g1.join()
g2.join()
this is "<Greenlet "Greenlet-0" at 0x1c4eb6f69d8: test(3)>" at NO.0
this is "<Greenlet "Greenlet-0" at 0x1c4eb6f69d8: test(3)>" at NO.1
this is "<Greenlet "Greenlet-0" at 0x1c4eb6f69d8: test(3)>" at NO.2
this is "<Greenlet "Greenlet-1" at 0x1c4ec265268: test(3)>" at NO.0
this is "<Greenlet "Greenlet-1" at 0x1c4ec265268: test(3)>" at NO.1
this is "<Greenlet "Greenlet-1" at 0x1c4ec265268: test(3)>" at NO.2
# gevent 切換執(zhí)行
# -*- coding: utf-8 -*-

import gevent

def test(n):
    for i in range(n):
        print('this is "{}" at NO.{}'.format(gevent.getcurrent(), i))
        gevent.sleep(0.3)

g1 = gevent.spawn(test, 3)
g2 = gevent.spawn(test, 3)

g1.join()
g2.join()        
this is "<Greenlet "Greenlet-0" at 0x1c4ec265378: test(3)>" at NO.0
this is "<Greenlet "Greenlet-1" at 0x1c4ec265488: test(3)>" at NO.0
this is "<Greenlet "Greenlet-0" at 0x1c4ec265378: test(3)>" at NO.1
this is "<Greenlet "Greenlet-1" at 0x1c4ec265488: test(3)>" at NO.1
this is "<Greenlet "Greenlet-0" at 0x1c4ec265378: test(3)>" at NO.2
this is "<Greenlet "Greenlet-1" at 0x1c4ec265488: test(3)>" at NO.2
asyncio淀歇、async/await易核、aiohttp

參考自廖雪峰的Python教程,不在此贅述浪默,下文給出參考網(wǎng)址

asyncio

async/await

aiohttp






網(wǎng)絡(luò)編程

使用網(wǎng)絡(luò)能夠把多方鏈接在一起牡直,然后可以進(jìn)行數(shù)據(jù)傳遞

所謂的網(wǎng)絡(luò)編程就是,讓在不同的電腦上的軟件能夠進(jìn)行數(shù)據(jù)傳遞纳决,即進(jìn)程之間的通信

Python Internet 模塊

協(xié)議 功能用處 端口號 Python模塊
HTTP 網(wǎng)頁訪問 80 httplib, urllib, xmlrpclib
NNTP 閱讀和張貼新聞文章碰逸,俗稱為"帖子" 119 nntplib
FTP 文件傳輸 20 fitlib, urllib
SMTP 發(fā)送郵件 25 smtplib
POP3 接收郵件 110 poplib
IMAP4 獲取郵件 143 imaplib
Telnet 命令行 23 telnetlib
Gopher 信息查找 70 gopherlib,urllib

TCP/IP協(xié)議(族)

應(yīng)用層:應(yīng)用層,表示層阔加,會話層

傳輸層:傳輸層

網(wǎng)絡(luò)層:網(wǎng)絡(luò)層

鏈路層:數(shù)據(jù)鏈路層饵史,物理層

TCP/IP協(xié)議族中各協(xié)議之間的關(guān)系

對上圖的說明:

網(wǎng)際層也稱為:網(wǎng)絡(luò)層;網(wǎng)絡(luò)接口層也稱為:鏈路層

端口

端口:可以認(rèn)為是設(shè)備與外界通訊交流的出口胜榔。

端口號是唯一的

端口范圍:0~65535

端口分類:

**公認(rèn)端口(WellKnownPorts):**從0到1023胳喷,它們緊密綁定(binding)于一些服務(wù)。

**注冊端口(RegisteredPorts):**從1024到49151夭织。它們松散地綁定于一些服務(wù)吭露。也就是說有許多服務(wù)綁定于這些端口,這些端口同樣用于許多其它目的尊惰。例如:許多系統(tǒng)處理動態(tài)端口從1024左右開始讲竿。

**動態(tài)和/或私有端口(Dynamicand/orPrivatePorts):**從49152到65535。理論上弄屡,不應(yīng)為服務(wù)分配這些端口题禀。實際上,機器通常從1024起分配動態(tài)端口膀捷。但也有例外:SUN的RPC端口從32768開始迈嘹。

查看端口:

1、netstat -an指令查看

2担孔、第三方掃描軟件

IP地址

ip地址:用來在網(wǎng)絡(luò)中標(biāo)記一臺電腦的一串?dāng)?shù)字江锨,比如192.168.1.1吃警;在本地局域網(wǎng)上是惟一的糕篇。

ip地址分類:

A類IP地址

一個A類IP地址由1字節(jié)的網(wǎng)絡(luò)地址和3字節(jié)主機地址組成,網(wǎng)絡(luò)地址的最高位必須是“0”酌心,地址范圍1.0.0.1-126.255.255.254二進(jìn)制表示為:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
可用的A類網(wǎng)絡(luò)有126個拌消,每個網(wǎng)絡(luò)能容納1677214個主機

B類IP地址

一個B類IP地址由2個字節(jié)的網(wǎng)絡(luò)地址和2個字節(jié)的主機地址組成,網(wǎng)絡(luò)地址的最高位必須是“10”,地址范圍128.1.0.1-191.255.255.254
二進(jìn)制表示為:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
可用的B類網(wǎng)絡(luò)有16384個墩崩,每個網(wǎng)絡(luò)能容納65534主機

C類IP地址

一個C類IP地址由3字節(jié)的網(wǎng)絡(luò)地址和1字節(jié)的主機地址組成氓英,網(wǎng)絡(luò)地址的最高位必須是“110”
范圍192.0.1.1-223.255.255.254
二進(jìn)制表示為: 11000000 00000000 00000001 00000001 - 11011111 11111111 11111110 11111110
C類網(wǎng)絡(luò)可達(dá)2097152個,每個網(wǎng)絡(luò)能容納254個主機

D類地址用于多點廣播

D類IP地址第一個字節(jié)以“1110”開始鹦筹,它是一個專門保留的地址铝阐。
它并不指向特定的網(wǎng)絡(luò),目前這一類地址被用在多點廣播(Multicast)中
多點廣播地址用來一次尋址一組計算機
地址范圍224.0.0.1-239.255.255.254

  • E類IP地址*

以“1111”開始铐拐,為將來使用保留
E類地址保留徘键,僅作實驗和開發(fā)用

  • 私有ip*

在這么多網(wǎng)絡(luò)IP中,國際規(guī)定有一部分IP地址是用于我們的局域網(wǎng)使用遍蟋,也就
是屬于私網(wǎng)IP吹害,不在公網(wǎng)中使用的,它們的范圍是:

10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255

注意

P地址127.0.0.1~127.255.255.255用于回路測試虚青,
如:127.0.0.1可以代表本機IP地址它呀,用[http://127.0.0.1]
就可以測試本機中配置的Web服務(wù)器。

子網(wǎng)掩碼

子網(wǎng)掩碼(subnet mask)又叫網(wǎng)絡(luò)掩碼棒厘、地址掩碼纵穿、子網(wǎng)絡(luò)遮罩,

它是一種用來指明一個IP地址的哪些位標(biāo)識的是主機所在的子網(wǎng)绊谭,

以及哪些位標(biāo)識的是主機的位掩碼政恍。

子網(wǎng)掩碼不能單獨存在,它必須結(jié)合IP地址一起使用达传。

子網(wǎng)掩碼只有一個作用:就是將某個IP地址劃分成網(wǎng)絡(luò)地址和主機地址兩部分篙耗。

與IP地址相同,子網(wǎng)掩碼的長度也是32位宪赶,

左邊是網(wǎng)絡(luò)位宗弯,用二進(jìn)制數(shù)字“1”表示;
右邊是主機位搂妻,用二進(jìn)制數(shù)字“0”表示蒙保。

假設(shè)IP地址為“192.168.1.1”子網(wǎng)掩碼為“255.255.255.0”。

其中欲主,“1”有24個邓厕,代表與此相對應(yīng)的IP地址左邊24位是網(wǎng)絡(luò)號;
“0”有8個扁瓢,代表與此相對應(yīng)的IP地址右邊8位是主機號详恼。
這樣,子網(wǎng)掩碼就確定了一個IP地址的32位二進(jìn)制數(shù)字中哪些是網(wǎng)絡(luò)號引几、哪些是主機號昧互。
這對于采用TCP/IP協(xié)議的網(wǎng)絡(luò)來說非常重要,只有通過子網(wǎng)掩碼,才能表明一臺主機所在的子網(wǎng)與其他子網(wǎng)的關(guān)系敞掘,使網(wǎng)絡(luò)正常工作叽掘。

子網(wǎng)掩碼是“255.255.255.0”的網(wǎng)絡(luò):

最后面一個數(shù)字可以在0~255范圍內(nèi)任意變化,因此可以提供256個IP地址玖雁。
但是實際可用的IP地址數(shù)量是256-2更扁,即254個,因為主機號不能全是“0”或全是“1”赫冬。

主機號全為0疯潭,表示網(wǎng)絡(luò)號
主機號全為1,表示網(wǎng)絡(luò)廣播

注意:

如果將子網(wǎng)掩碼設(shè)置過大面殖,也就是說子網(wǎng)范圍擴(kuò)大竖哩,根據(jù)子網(wǎng)尋徑規(guī)則,很可能發(fā)往和本地主機不在同一子網(wǎng)內(nèi)的目標(biāo)主機的數(shù)據(jù)脊僚,會因為錯誤的判斷而認(rèn)為目標(biāo)主機是在同一子網(wǎng)內(nèi)相叁,導(dǎo)致數(shù)據(jù)包將在本子網(wǎng)內(nèi)循環(huán),直到超時并拋棄辽幌,使數(shù)據(jù)不能正確到達(dá)目標(biāo)主機增淹,導(dǎo)致網(wǎng)絡(luò)傳輸錯誤;如果將子網(wǎng)掩碼設(shè)置得過小乌企,那么就會將本來屬于同一子網(wǎng)內(nèi)的機器之間的通信當(dāng)做是跨子網(wǎng)傳輸虑润,數(shù)據(jù)包都交給缺省網(wǎng)關(guān)處理,這樣勢必增加缺省網(wǎng)關(guān)的負(fù)擔(dān)加酵,造成網(wǎng)絡(luò)效率下降拳喻。因此,子網(wǎng)掩碼應(yīng)該根據(jù)網(wǎng)絡(luò)的規(guī)模進(jìn)行設(shè)置猪腕。如果一個網(wǎng)絡(luò)的規(guī)模不超過254臺電腦冗澈,采用“255.255.255.0”作為子網(wǎng)掩碼就可以了,現(xiàn)在大多數(shù)局域網(wǎng)都不會超過這個數(shù)字陋葡,因此“255.255.255.0”是最常用的IP地址子網(wǎng)掩碼亚亲;假如在一所大學(xué)具有1500多臺電腦,這種規(guī)模的局域網(wǎng)可以使用“255.255.0.0”腐缤。

socket

本地(一臺機器上)的進(jìn)程間通信(IPC)有很多種方式捌归,例如

隊列
同步(互斥鎖、條件變量等)

網(wǎng)絡(luò)上的兩個程序通過一個雙向的通信連接實現(xiàn)數(shù)據(jù)的交換岭粤,這個連接的一端稱為一個socket(簡稱:套接字)惜索。

Python提供了兩個級別訪問的網(wǎng)絡(luò)服務(wù):

低級別的網(wǎng)絡(luò)服務(wù)支持基本的 Socket,它提供了標(biāo)準(zhǔn)的 BSD Sockets API绍在,可以訪問底層操作系統(tǒng)Socket接口的全部方法门扇。

高級別的網(wǎng)絡(luò)服務(wù)模塊 SocketServer, 它提供了服務(wù)器中心類偿渡,可以簡化網(wǎng)絡(luò)服務(wù)器的開發(fā)臼寄。

Python創(chuàng)建socket:

import socket

socket.socket(family[, type[, proto]])

參數(shù)解釋:

family:套接字家族可以使AF_UNIX或者AF_INET

type:套接字類型可以根據(jù)是面向連接的還是非連接分為SOCK_STREAM或SOCK_DGRAM

protocol:一般不填默認(rèn)為0.

Socket 對象(內(nèi)建)方法

服務(wù)器端套接字

s.bind()

綁定地址(host, port)到socket溜宽, 在AF_INET下吉拳,以元組(host, port的形式表示地址。)

s.listen()

開始TCP監(jiān)聽适揉。backlog指定在拒絕連接之前,操作系統(tǒng)可以掛起的最大連接數(shù)量。該值至少為1笛质,大部分應(yīng)用程序設(shè)為5就可以了陆错。

s.accept()

被動接受TCP客戶端連接,(阻塞式)等待連接的到來

客戶端套接字

s.connect()

主動初始化TCP服務(wù)器連接,剪侮。一般address的格式為元組(hostname,port)拭宁,如果連接出錯,返回socket.error錯誤瓣俯。

s.connect_ex()

connect()函數(shù)的擴(kuò)展版本,出錯時返回出錯碼,而不是拋出異常

公共用途的套接字函數(shù)

s.recv()

接收TCP數(shù)據(jù)杰标,數(shù)據(jù)以字符串形式返回,bufsize指定要接收的最大數(shù)據(jù)量彩匕。flag提供有關(guān)消息的其他信息腔剂,通常可以忽略驼仪。

s.send()

發(fā)送TCP數(shù)據(jù)掸犬,將string中的數(shù)據(jù)發(fā)送到連接的套接字。返回值是要發(fā)送的字節(jié)數(shù)量绪爸,該數(shù)量可能小于string的字節(jié)大小登渣。

s.sendall()

完整發(fā)送TCP數(shù)據(jù)。將string中的數(shù)據(jù)發(fā)送到連接的套接字毡泻,但在返回之前會嘗試發(fā)送所有數(shù)據(jù)胜茧。成功返回None,失敗則拋出異常仇味。

s.recvfrom()

接收UDP數(shù)據(jù)呻顽,與recv()類似,但返回值是(data,address)丹墨。其中data是包含接收數(shù)據(jù)的字符串廊遍,address是發(fā)送數(shù)據(jù)的套接字地址。

s.sendto()

發(fā)送UDP數(shù)據(jù)贩挣,將數(shù)據(jù)發(fā)送到套接字喉前,address是形式為(ipaddr没酣,port)的元組,指定遠(yuǎn)程地址卵迂。返回值是發(fā)送的字節(jié)數(shù)裕便。

s.close()

關(guān)閉套接字

s.getpeername()  

返回連接套接字的遠(yuǎn)程地址。返回值通常是元組(ipaddr,port)见咒。

s.getsockname()

返回套接字自己的地址偿衰。通常是一個元組(ipaddr,port)

s.setsockopt(level, optname, value)

設(shè)置給定套接字選項的值。

s.getsocketopt(level, optname, buflen)

返回套接字選項的值改览。

s.settimeout(timeout)

設(shè)置套接字操作的超時期下翎,timeout是一個浮點數(shù),單位是秒宝当。值為None表示沒有超時期视事。一般,超時期應(yīng)該在剛創(chuàng)建套接字時設(shè)置庆揩,因為它們可能用于連接的操作(如connect())

s.gettimeout()

返回當(dāng)前超時期的值郑口,單位是秒,如果沒有設(shè)置超時期盾鳞,則返回None犬性。

s.gileno()

返回套接字的文件描述符。

s.setbolcking(flag)

如果flag為0腾仅,則將套接字設(shè)為非阻塞模式乒裆,否則將套接字設(shè)為阻塞模式(默認(rèn)值)。非阻塞模式下推励,如果調(diào)用recv()沒有發(fā)現(xiàn)任何數(shù)據(jù)鹤耍,或send()調(diào)用無法立即發(fā)送數(shù)據(jù),那么將引起socket.error異常

s.makefile()

創(chuàng)建一個與該套接字相關(guān)連的文件

# 創(chuàng)建socket 例子
import socket

# 創(chuàng)建一個tcp socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print('tcp socket created!')

# 創(chuàng)建一個udp socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print('udp socket created!')
tcp socket created!
udp socket created!
# socket 簡單服務(wù)器
# 導(dǎo)入socket验辞, sys 模塊
import socket
import sys

# 創(chuàng)建socket對象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 獲取本地主機名
host = socket.gethostname()
# 是指端口
port = 6666

#  綁定端口號
server_socket.bind((host, port))
# 設(shè)置最大連接數(shù)稿黄,超過后阻塞
server_socket.listen(5)

while True: # 死循環(huán),實際應(yīng)用不可用
    #  建立客戶端連接
    client_socket, addr = server_socket.accept()
    
    print('連接地址:addr = {}'.format(addr))
    
    msg = "這是一個socket 超簡易服務(wù)器跌造!\r\n"
    client_socket.send(msg.encode('utf-8'))
    client_socket.close()
    
# 因為是死循環(huán)杆怕,未設(shè)置終止,所以手動終止程序壳贪,會出現(xiàn)下方錯誤
---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

<ipython-input-1-9f6a2b82bcf7> in <module>
     19 while True: # 死循環(huán)陵珍,實際應(yīng)用不可用
     20     #  建立客戶端連接
---> 21     client_socket, addr = server_socket.accept()
     22 
     23     print('連接地址:addr = {}'.format(addr))


~/WorkStations/anaconda3/lib/python3.7/socket.py in accept(self)
    210         For IP sockets, the address info is a pair (hostaddr, port).
    211         """
--> 212         fd, addr = self._accept()
    213         sock = socket(self.family, self.type, self.proto, fileno=fd)
    214         # Issue #7995: if no default timeout is set and the listening


KeyboardInterrupt: 
# socket 客戶端例子

# 導(dǎo)入socket, sys 模塊
import socket
import sys

# 創(chuàng)建socket對象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 獲取本地主機名
host = socket.gethostname()
# 是指端口
port = 6666
# 連接服務(wù)违施,指定主機和端口
s.connect((host, port))

# 接收小于2048字節(jié)的數(shù)據(jù)
msg = s.recv(2048)

s.close()

print(msg.decode('utf-8'))

socket 服務(wù)器 客戶端 通信 結(jié)果圖

socket 服務(wù)器 客戶端 通信 結(jié)果圖

UDP

UDP --- 用戶數(shù)據(jù)報協(xié)議互纯,是一個無連接的簡單的面向數(shù)據(jù)報的運輸層協(xié)議。

udp是TCP/IP協(xié)議族中的一種協(xié)議能夠完成不同機器上的程序間的數(shù)據(jù)通信

udp只是把應(yīng)用層傳給ip層的數(shù)據(jù)報發(fā)出去磕蒲,不能保證到達(dá)留潦;udp在傳輸數(shù)據(jù)報前不用在客戶和服務(wù)器之間建立連接只盹,沒有超時重發(fā)等機制,傳輸速度快兔院。

UDP是一種面向無連接的協(xié)議殖卑,每個數(shù)據(jù)報都是一個獨立的信息,包括完整的源地址或目的地址秆乳,它在網(wǎng)絡(luò)上以任何可能的路徑傳往目的地,因此能否到達(dá)目的地钻哩,到達(dá)目的地的時間以及內(nèi)容的正確性都是不能被保證的屹堰。

UDP特點:

面向無連接的通訊協(xié)議,UDP數(shù)據(jù)包括目的端口號和源端口號信息街氢,由于通訊不需要連接扯键,所以可以實現(xiàn)廣播發(fā)送。

UDP傳輸數(shù)據(jù)有大小限制:每個被傳輸?shù)臄?shù)據(jù)報必須限定在64KB內(nèi)珊肃。

udp不可靠荣刑,發(fā)送方所發(fā)送的數(shù)據(jù)報并不一定以相同的次序到達(dá)接收方。

UDP一般用于多點通信和實時的數(shù)據(jù)業(yè)務(wù):

 語音廣播
 
視頻

QQ

TFTP(簡單文件傳送)

SNMP(簡單網(wǎng)絡(luò)管理協(xié)議)

RIP(路由信息協(xié)議伦乔,如報告股票市場厉亏,航空信息)

DNS(域名解釋)

UDP操作簡單,而且僅需要較少的監(jiān)護(hù)烈和,因此通常用于局域網(wǎng)高可靠性的分散系統(tǒng)中client/server應(yīng)用程序爱只。

udp端口號動態(tài)變化解釋:
每次重新運行網(wǎng)絡(luò)程序,對于未綁定端口號的程序招刹,系統(tǒng)默認(rèn)隨機分配一個端口號來唯一標(biāo)識這個程序恬试。如果需要向此程序發(fā)送信息,只需要向這個端口標(biāo)識的程序發(fā)送即可疯暑。

UDP網(wǎng)絡(luò)通信過程

UDP網(wǎng)絡(luò)通信過程

udp服務(wù)器训柴、客戶端

udp的服務(wù)器和客戶端的區(qū)分:往往是通過請求服務(wù)和提供服務(wù)來進(jìn)行區(qū)分

請求服務(wù)的一方稱為:客戶端

提供服務(wù)的一方稱為:服務(wù)器

udp 發(fā)送數(shù)據(jù):

創(chuàng)建一個udp客戶端程序步驟:

1、創(chuàng)建客戶端套接字

2妇拯、發(fā)送/接收數(shù)據(jù)

3幻馁、關(guān)閉套接字

UDP創(chuàng)建流程圖

UDP創(chuàng)建流程圖

# 創(chuàng)建udp 客戶端 例子udp
# -*- coding: utf-8 -*-
import socket

#1創(chuàng)建socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2 準(zhǔn)備接收方地址
sendAddr = ('192.168.1.3', 8080)

# 3 鍵入數(shù)據(jù)
send_data = input('input send data:')

# 4 發(fā)送到指定的地址
udp_socket.sendto(send_data.encode('utf-8'), sendAddr)

# 5 關(guān)閉socket
udp_socket.close()

# 結(jié)果:在1922.168.1.3機器上通過【網(wǎng)絡(luò)調(diào)試助手】軟件會收到發(fā)送的數(shù)據(jù)
input send data:asdf
# udp 發(fā)送、接收數(shù)據(jù) 例子
# -*- coding: utf-8 -*-
import socket

# 1越锈、創(chuàng)建socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2宣赔、準(zhǔn)備接收數(shù)據(jù)的機器地址
send_addr = ('192.168.1.3', 8080)
# 3、輸出要發(fā)送的數(shù)據(jù), 注意.encode('utf-8') 轉(zhuǎn)換二進(jìn)制
send_data = input('請輸入要發(fā)送的數(shù)據(jù):').encode('utf-8')
# 4瞪浸、發(fā)送數(shù)據(jù)到指定機器
udp_socket.sendto(send_data, send_addr)
# 5儒将、等待接收方發(fā)送數(shù)據(jù)
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接受的最大字節(jié)數(shù)
# 6、顯示接收到的數(shù)據(jù)
print(recv_data)
# 7对蒲、關(guān)閉socket
udp_socket.close()
請輸入要發(fā)送的數(shù)據(jù):asdf
udp綁定

一個udp網(wǎng)絡(luò)程序钩蚊,可以不綁定贡翘,此時操作系統(tǒng)會隨機進(jìn)行分配一個端口,

如果重新運行次程序端口可能會發(fā)生變化

一個udp網(wǎng)絡(luò)程序砰逻,也可以綁定信息(ip地址鸣驱,端口號),如果綁定成功蝠咆,

那么操作系統(tǒng)用這個端口號來進(jìn)行區(qū)別收到的網(wǎng)絡(luò)數(shù)據(jù)是否是此進(jìn)程的

# udp 接收方 端口綁定 例子
# -*-coding: utf-8 -*-
import socket

# 1踊东、創(chuàng)建socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2、綁定本地的相關(guān)信息刚操,若一個網(wǎng)絡(luò)程序不綁定闸翅,則系統(tǒng)會隨機分配端口號
bind_addr = ('',6666)# ip地址和端口號,ip一般不用寫菊霜,表示本機的任何一個ip
udp_socket.bind(bind_addr)
# 3坚冀、等待接收數(shù)據(jù)
recv_data = udp_socket.recvfrom(1024)# 1204為本次接收的最大字節(jié)數(shù)
# 4、顯示接收數(shù)據(jù)
# 若接收到的是bite格式鉴逞,則用decode('decode-type')解碼记某,str類型不必decode
print(recv_data.decode('utf-8'))
# 5、關(guān)閉socket
udp_socket.close()

# 因為例子是接收方的端口綁定构捡,所以單獨運行是沒有顯示的液南,故不運行
# udp echo 服務(wù)器 示例
# echo服務(wù)器:將收到的信息原封不動的返回
import socket

# 1、創(chuàng)建socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2勾徽、綁定本地相關(guān)信息
bind_addr = ('', 6666)# ip地址和端口號贺拣,ip一般不用寫,表示本機的任何一個ip
udp_socket.bind(bind_addr)

# 統(tǒng)計次數(shù)
num = 0

while True:
    # 3捂蕴、等待接收對方發(fā)送的數(shù)據(jù)
    recv_data = udp_socket.recvfrom(2048)# 最大接收字節(jié)數(shù)2048
    # 4譬涡、回傳接收到的信息
    udp_socket.sendto(recv_data[0], recv_data[1])
    # 5、統(tǒng)計信息
    num +=1
    print('已完成{}次數(shù)據(jù)收發(fā)'.format(num))
    
# 5啥辨、關(guān)閉socket
udp_socket.close()
udp廣播
# udp 廣播例子
# -*- coding: utf-8 -*-

import socket, sys

dest = ('<broadcast>', 6666)

# 創(chuàng)建socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 對socket進(jìn)行修改涡匀,以發(fā)送廣播
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

# 以廣播的形式發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)的所有電腦中
s.sendto('hello, world!', dest)
print('等待回復(fù),Ctrl+c退出')

while True:
    (buf, address) = s.recvfrom(2048)
    print("received from {}:{}".format(address, buf))

TCP

tcp通信模型中溉知,在通信開始之前陨瘩,一定要先建立相關(guān)的鏈接,才能發(fā)送數(shù)據(jù)级乍,類似于生活中"打電話"

TCP三次握手和四次揮手部分參考自下面的網(wǎng)址:

參考網(wǎng)址 作者:小書go

背景描述:

網(wǎng)絡(luò)層舌劳,可以實現(xiàn)兩個主機之間的通信。但是這并不具體玫荣,因為甚淡,真正進(jìn)行通信的實體是在主機中的進(jìn)程,是一個主機中的一個進(jìn)程與另外一個主機中的一個進(jìn)程在交換數(shù)據(jù)捅厂。IP協(xié)議雖然能把數(shù)據(jù)報文送到目的主機贯卦,但是并沒有交付給主機的具體應(yīng)用進(jìn)程资柔。而端到端的通信才應(yīng)該是應(yīng)用進(jìn)程之間的通信。

UDP撵割,在傳送數(shù)據(jù)前不需要先建立連接贿堰,遠(yuǎn)地的主機在收到UDP報文后也不需要給出任何確認(rèn)。雖然UDP不提供可靠交付啡彬,但是正是因為這樣羹与,省去和很多的開銷,使得它的速度比較快庶灿,比如一些對實時性要求較高的服務(wù)纵搁,就常常使用的是UDP。對應(yīng)的應(yīng)用層的協(xié)議主要有 DNS,TFTP,DHCP,SNMP,NFS 等跳仿。

TCP诡渴,提供面向連接的服務(wù)捐晶,在傳送數(shù)據(jù)之前必須先建立連接菲语,數(shù)據(jù)傳送完成后要釋放連接。因此TCP是一種可靠的的運輸服務(wù)惑灵,但是正因為這樣山上,不可避免的增加了許多的開銷,比如確認(rèn)英支,流量控制等佩憾。對應(yīng)的應(yīng)用層的協(xié)議主要有 SMTP,TELNET,HTTP,FTP 等。

TCP報文首部

  1. 源端口和目的端口干花,各占2個字節(jié)妄帘,分別寫入源端口和目的端口;

  2. 序號池凄,占4個字節(jié)抡驼,TCP連接中傳送的字節(jié)流中的每個字節(jié)都按順序編號。例如肿仑,一段報文的序號字段值是 301 致盟,而攜帶的數(shù)據(jù)共有100字段,顯然下一個報文段(如果還有的話)的數(shù)據(jù)序號應(yīng)該從401開始尤慰;

  3. 確認(rèn)號馏锡,占4個字節(jié),是期望收到對方下一個報文的第一個數(shù)據(jù)字節(jié)的序號伟端。例如杯道,B收到了A發(fā)送過來的報文,其序列號字段是501责蝠,而數(shù)據(jù)長度是200字節(jié)蕉饼,這表明B正確的收到了A發(fā)送的到序號700為止的數(shù)據(jù)虐杯。因此,B期望收到A的下一個數(shù)據(jù)序號是701昧港,于是B在發(fā)送給A的確認(rèn)報文段中把確認(rèn)號置為701擎椰;

  4. 數(shù)據(jù)偏移,占4位创肥,它指出TCP報文的數(shù)據(jù)距離TCP報文段的起始處有多遠(yuǎn)达舒;

  5. 保留,占6位叹侄,保留今后使用巩搏,但目前應(yīng)該都位0;

  6. 緊急URG趾代,當(dāng)URG=1贯底,表明緊急指針字段有效。告訴系統(tǒng)此報文段中有緊急數(shù)據(jù)撒强;

  7. 確認(rèn)ACK禽捆,僅當(dāng)ACK=1時,確認(rèn)號字段才有效飘哨。TCP規(guī)定胚想,在連接建立后所有報文的傳輸都必須把ACK置1;

  8. 推送PSH芽隆,當(dāng)兩個應(yīng)用進(jìn)程進(jìn)行交互式通信時浊服,有時在一端的應(yīng)用進(jìn)程希望在鍵入一個命令后立即就能收到對方的響應(yīng),這時候就將PSH=1胚吁;

  9. 復(fù)位RST牙躺,當(dāng)RST=1,表明TCP連接中出現(xiàn)嚴(yán)重差錯腕扶,必須釋放連接孽拷,然后再重新建立連接;

  10. 同步SYN蕉毯,在連接建立時用來同步序號乓搬。當(dāng)SYN=1,ACK=0代虾,表明是連接請求報文进肯,若同意連接,則響應(yīng)報文中應(yīng)該使SYN=1棉磨,ACK=1江掩;

  11. 終止FIN,用來釋放連接。當(dāng)FIN=1环形,表明此報文的發(fā)送方的數(shù)據(jù)已經(jīng)發(fā)送完畢策泣,并且要求釋放;

  12. 窗口抬吟,占2字節(jié)萨咕,指的是通知接收方,發(fā)送本報文你需要有多大的空間來接受火本;

  13. 檢驗和危队,占2字節(jié),校驗首部和數(shù)據(jù)這兩部分钙畔;

  14. 緊急指針茫陆,占2字節(jié),指出本報文段中的緊急數(shù)據(jù)的字節(jié)數(shù)擎析;

  15. 選項簿盅,長度可變,定義一些其他的可選的參數(shù)揍魂。

TCP通信過程

TCP通信過程
TCP三次握手
TCP三次握手

syn>>>> syn+ack<<<<<<<<<<<< ack>>>>>>>>>>>>>>>

*tcp傳輸前會發(fā)送ack包確認(rèn)桨醋,但是udp不會進(jìn)行確認(rèn),所以有tcp比udp穩(wěn)定

client close()方法愉烙,會通知到server讨盒,server會回復(fù)通知信息解取,然后client回復(fù)步责,揮手完*

TCP三次握手圖解

最開始的時候客戶端和服務(wù)器都是處于CLOSED狀態(tài)。主動打開連接的為客戶端禀苦,被動打開連接的是服務(wù)器蔓肯。

  1. TCP服務(wù)器進(jìn)程先創(chuàng)建傳輸控制塊TCB,時刻準(zhǔn)備接受客戶進(jìn)程的連接請求振乏,此時服務(wù)器就進(jìn)入了LISTEN(監(jiān)聽)狀態(tài)蔗包;

  2. TCP客戶進(jìn)程也是先創(chuàng)建傳輸控制塊TCB,然后向服務(wù)器發(fā)出連接請求報文慧邮,這是報文首部中的同部位SYN=1调限,同時選擇一個初始序列號 seq=x ,此時误澳,TCP客戶端進(jìn)程進(jìn)入了 SYN-SENT(同步已發(fā)送狀態(tài))狀態(tài)耻矮。TCP規(guī)定,SYN報文段(SYN=1的報文段)不能攜帶數(shù)據(jù)忆谓,但需要消耗掉一個序號裆装。

  3. TCP服務(wù)器收到請求報文后,如果同意連接,則發(fā)出確認(rèn)報文哨免。確認(rèn)報文中應(yīng)該 ACK=1茎活,SYN=1,確認(rèn)號是ack=x+1琢唾,同時也要為自己初始化一個序列號 seq=y奔则,此時杜窄,TCP服務(wù)器進(jìn)程進(jìn)入了SYN-RCVD(同步收到)狀態(tài)。這個報文也不能攜帶數(shù)據(jù),但是同樣要消耗一個序號蔽挠。

  4. TCP客戶進(jìn)程收到確認(rèn)后,還要向服務(wù)器給出確認(rèn)栅迄。確認(rèn)報文的ACK=1私杜,ack=y+1,自己的序列號seq=x+1泌豆,此時定庵,TCP連接建立,客戶端進(jìn)入ESTABLISHED(已建立連接)狀態(tài)踪危。TCP規(guī)定蔬浙,ACK報文段可以攜帶數(shù)據(jù),但是如果不攜帶數(shù)據(jù)則不消耗序號贞远。

為什么TCP客戶端最后還要發(fā)送一次確認(rèn)

一句話畴博,主要防止已經(jīng)失效的連接請求報文突然又傳送到了服務(wù)器,從而產(chǎn)生錯誤蓝仲。
如果使用兩次握手建立連接俱病,假設(shè)有這樣一種場景,客戶端發(fā)送了第一個請求連接并且沒有丟失袱结,只是因為在網(wǎng)絡(luò)結(jié)點中滯留的時間太長了亮隙,由于TCP的客戶端遲遲沒有收到確認(rèn)報文,以為服務(wù)器沒有收到垢夹,此時重新向服務(wù)器發(fā)送這條報文溢吻,此后客戶端和服務(wù)器經(jīng)過兩次握手完成連接,傳輸數(shù)據(jù)果元,然后關(guān)閉連接促王。此時此前滯留的那一次請求連接,網(wǎng)絡(luò)通暢了到達(dá)了服務(wù)器而晒,這個報文本該是失效的蝇狼,但是,兩次握手的機制將會讓客戶端和服務(wù)器再次建立連接欣硼,這將導(dǎo)致不必要的錯誤和資源的浪費题翰。
如果采用的是三次握手恶阴,就算是那一次失效的報文傳送過來了,服務(wù)端接受到了那條失效報文并且回復(fù)了確認(rèn)報文豹障,但是客戶端不會再次發(fā)出確認(rèn)冯事。由于服務(wù)器收不到確認(rèn),就知道客戶端并沒有請求連接血公。

tcp四次揮手
tcp四次揮手
tcp四次揮手圖解過程

數(shù)據(jù)傳輸完畢后昵仅,雙方都可釋放連接。最開始的時候累魔,客戶端和服務(wù)器都是處于ESTABLISHED狀態(tài)摔笤,然后客戶端主動關(guān)閉,服務(wù)器被動關(guān)閉垦写。

  1. 客戶端進(jìn)程發(fā)出連接釋放報文吕世,并且停止發(fā)送數(shù)據(jù)。釋放數(shù)據(jù)報文首部梯投,F(xiàn)IN=1命辖,其序列號為seq=u(等于前面已經(jīng)傳送過來的數(shù)據(jù)的最后一個字節(jié)的序號加1),此時分蓖,客戶端進(jìn)入FIN-WAIT-1(終止等待1)狀態(tài)尔艇。 TCP規(guī)定,F(xiàn)IN報文段即使不攜帶數(shù)據(jù)么鹤,也要消耗一個序號终娃。

  2. 服務(wù)器收到連接釋放報文,發(fā)出確認(rèn)報文蒸甜,ACK=1棠耕,ack=u+1,并且?guī)献约旱男蛄刑杝eq=v迅皇,此時昧辽,服務(wù)端就進(jìn)入了CLOSE-WAIT(關(guān)閉等待)狀態(tài)衙熔。TCP服務(wù)器通知高層的應(yīng)用進(jìn)程登颓,客戶端向服務(wù)器的方向就釋放了,這時候處于半關(guān)閉狀態(tài)红氯,即客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了框咙,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶端依然要接受痢甘。這個狀態(tài)還要持續(xù)一段時間喇嘱,也就是整個CLOSE-WAIT狀態(tài)持續(xù)的時間。

  3. 客戶端收到服務(wù)器的確認(rèn)請求后塞栅,此時者铜,客戶端就進(jìn)入FIN-WAIT-2(終止等待2)狀態(tài),等待服務(wù)器發(fā)送連接釋放報文(在這之前還需要接受服務(wù)器發(fā)送的最后的數(shù)據(jù))。

  4. 服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后作烟,就向客戶端發(fā)送連接釋放報文愉粤,F(xiàn)IN=1,ack=u+1拿撩,由于在半關(guān)閉狀態(tài)衣厘,服務(wù)器很可能又發(fā)送了一些數(shù)據(jù),假定此時的序列號為seq=w压恒,此時影暴,服務(wù)器就進(jìn)入了LAST-ACK(最后確認(rèn))狀態(tài),等待客戶端的確認(rèn)探赫。

  5. 客戶端收到服務(wù)器的連接釋放報文后型宙,必須發(fā)出確認(rèn),ACK=1伦吠,ack=w+1早歇,而自己的序列號是seq=u+1,此時讨勤,客戶端就進(jìn)入了TIME-WAIT(時間等待)狀態(tài)箭跳。注意此時TCP連接還沒有釋放,必須經(jīng)過2??MSL(最長報文段壽命)的時間后潭千,當(dāng)客戶端撤銷相應(yīng)的TCB后谱姓,才進(jìn)入CLOSED狀態(tài)。

  6. 服務(wù)器只要收到了客戶端發(fā)出的確認(rèn)刨晴,立即進(jìn)入CLOSED狀態(tài)屉来。同樣,撤銷TCB后狈癞,就結(jié)束了這次的TCP連接茄靠。可以看到蝶桶,服務(wù)器結(jié)束TCP連接的時間要比客戶端早一些慨绳。

tcp十種狀態(tài)

TCP十種狀態(tài)

當(dāng)一端收到一個FIN,內(nèi)核讓read返回0來通知應(yīng)用層另一端已經(jīng)終止了向本端的數(shù)據(jù)傳送

發(fā)送FIN通常是應(yīng)用層對socket進(jìn)行關(guān)閉的結(jié)果


TCP十種狀態(tài)
TCP的2MSL問題
TCP2MSL問題

MSL(Maximum Segment Lifetime)真竖,TCP允許不同的實現(xiàn)可以設(shè)置不同的MSL值脐雪。

第一,保證客戶端發(fā)送的最后一個ACK報文能夠到達(dá)服務(wù)器恢共,因為這個ACK報文可能丟失战秋,站在服務(wù)器的角度看來,我已經(jīng)發(fā)送了FIN+ACK報文請求斷開了讨韭,客戶端還沒有給我回應(yīng)脂信,應(yīng)該是我發(fā)送的請求斷開報文它沒有收到癣蟋,于是服務(wù)器又會重新發(fā)送一次,而客戶端就能在這個2MSL時間段內(nèi)收到這個重傳的報文狰闪,接著給出回應(yīng)報文梢薪,并且會重啟2MSL計時器。

第二尝哆,防止類似與“三次握手”中提到了的“已經(jīng)失效的連接請求報文段”出現(xiàn)在本連接中秉撇。客戶端發(fā)送完最后一個確認(rèn)報文后秋泄,在這個2MSL時間中琐馆,就可以使本連接持續(xù)的時間內(nèi)所產(chǎn)生的所有報文段都從網(wǎng)絡(luò)中消失。這樣新的連接中不會出現(xiàn)舊連接的請求報文恒序。

為什么建立連接是三次握手瘦麸,關(guān)閉連接確是四次揮手呢?

建立連接的時候歧胁, 服務(wù)器在LISTEN狀態(tài)下滋饲,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發(fā)送給客戶端喊巍。
而關(guān)閉連接時屠缭,服務(wù)器收到對方的FIN報文時,僅僅表示對方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù)崭参,而自己也未必全部數(shù)據(jù)都發(fā)送給對方了呵曹,所以己方可以立即關(guān)閉,也可以發(fā)送一些數(shù)據(jù)給對方后何暮,再發(fā)送FIN報文給對方來表示同意現(xiàn)在關(guān)閉連接奄喂,因此,己方ACK和FIN一般都會分開發(fā)送海洼,從而導(dǎo)致多了一次跨新。

如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦坏逢?

TCP還設(shè)有一個庇蛘剩活計時器,顯然词疼,客戶端如果出現(xiàn)故障俯树,服務(wù)器不能一直等下去,白白浪費資源贰盗。服務(wù)器每收到一次客戶端的請求后都會重新復(fù)位這個計時器,時間通常是設(shè)置為2小時阳欲,若兩小時還沒有收到客戶端的任何數(shù)據(jù)舵盈,服務(wù)器就會發(fā)送一個探測報文段陋率,以后每隔75分鐘發(fā)送一次。若一連發(fā)送10個探測報文仍然沒反應(yīng)秽晚,服務(wù)器就認(rèn)為客戶端出了故障瓦糟,接著就關(guān)閉連接。

TCP長連接和短連接

TCP在真正的讀寫操作之前赴蝇,server與client之間必須建立一個連接菩浙,
當(dāng)讀寫操作完成后,雙方不再需要這個連接時它們可以釋放這個連接句伶,
連接的建立通過三次握手劲蜻,釋放則需要四次握手,
所以說每個連接的建立都是需要資源消耗和時間消耗的考余。

TCP短連接

模擬一種TCP短連接的情況:

  1. client 向 server 發(fā)起連接請求

  2. server 接到請求先嬉,雙方建立連接

  3. client 向 server 發(fā)送消息

  4. server 回應(yīng) client

  5. 一次讀寫完成,此時雙方任何一個都可以發(fā)起 close 操作

在第 步驟5中楚堤,一般都是 client 先發(fā)起 close 操作疫蔓。當(dāng)然也不排除有特殊的情況。
從上面的描述看身冬,短連接一般只會在 client/server 間傳遞一次讀寫操作衅胀!

TCP長連接

模擬一種TCP長連接的情況:

  1. client 向 server 發(fā)起連接請求

  2. server 接到請求,雙方建立連接

  3. client 向 server 發(fā)送消息

  4. server 回應(yīng) client

  5. 一次讀寫完成酥筝,連接不關(guān)閉

  6. 后續(xù)讀寫操作...

  7. 長時間操作之后client發(fā)起關(guān)閉請求

TCP長/短連接操作過程

短連接:

建立連接——數(shù)據(jù)傳輸——關(guān)閉連接...建立連接——數(shù)據(jù)傳輸——關(guān)閉連接

短連接操作過程

長連接:

建立連接——數(shù)據(jù)傳輸...(保持連接)...數(shù)據(jù)傳輸——關(guān)閉連接

長連接操作過程

長短連接各自優(yōu)缺點:

長連接可以省去較多的TCP建立和關(guān)閉的操作拗小,減少浪費,節(jié)約時間樱哼。
對于頻繁請求資源的客戶來說哀九,較適用長連接。

client與server之間的連接如果一直不關(guān)閉的話搅幅,會存在一個問題阅束,
隨著客戶端連接越來越多,server早晚有扛不住的時候茄唐,這時候server端需要采取一些策略息裸,
如關(guān)閉一些長時間沒有讀寫事件發(fā)生的連接,這樣可以避免一些惡意連接導(dǎo)致server端服務(wù)受損沪编;
如果條件再允許就可以以客戶端機器為顆粒度呼盆,限制每個客戶端的最大長連接數(shù),
這樣可以完全避免某個蛋疼的客戶端連累后端服務(wù)蚁廓。

短連接對于服務(wù)器來說管理較為簡單访圃,存在的連接都是有用的連接,不需要額外的控制手段相嵌。

但如果客戶請求頻繁腿时,將在TCP的建立和關(guān)閉操作上浪費時間和帶寬况脆。

TCP長/短連接的應(yīng)用場景:

長連接多用于操作頻繁,點對點的通訊批糟,而且連接數(shù)不能太多情況格了。

而像WEB網(wǎng)站的http服務(wù)一般都用短鏈接,因為長連接對于服務(wù)端來說會耗費一定的資源徽鼎,

常見網(wǎng)絡(luò)攻擊案例

tcp半鏈接攻擊

tcp半鏈接攻擊也稱為:SYN Flood (SYN洪水)
是種典型的DoS (Denial of Service盛末,拒絕服務(wù)) 攻擊
效果就是服務(wù)器TCP連接資源耗盡,停止響應(yīng)正常的TCP連接請求

dns攻擊

dns服務(wù)器被劫持

域名服務(wù)器對其區(qū)域內(nèi)的用戶解析請求負(fù)責(zé)否淤,但是并沒有一個機制去監(jiān)督它有沒有真地負(fù)責(zé)悄但。
將用戶引向一個錯誤的目標(biāo)地址。這就叫作 DNS 劫持叹括,主要用來阻止用戶訪問某些特定的網(wǎng)站算墨,或者是將用戶引導(dǎo)到廣告頁面。

dns欺騙

DNS 欺騙簡單來說就是用一個假的 DNS 應(yīng)答來欺騙用戶計算機汁雷,讓其相信這個假的地址净嘀,并且拋棄真正的 DNS 應(yīng)答。

arp攻擊

arp攻擊

TCP服務(wù)器

創(chuàng)建TCP服務(wù)器過程:

1侠讯、創(chuàng)建socket

2挖藏、bind綁定ip和port

3、listen使套接字變?yōu)榭梢员粍渔溄?/p>

4accept等待客戶端的鏈接

5厢漩、recv/send接收發(fā)送數(shù)據(jù)

TCP服務(wù)器創(chuàng)建流程:socket(), bind(), listen(), accept(), read(), write(), close()

# tcp服務(wù)器 示例代碼
# -*- coding: utf -8 -*-

import socket

# 創(chuàng)建socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 綁定本地信息
address = ("", 6666)
tcp_server_socket.bind(address)

# 使用socket創(chuàng)建的套接字默認(rèn)的屬性是主動的膜眠,使用listen變?yōu)楸粍?# 變?yōu)楸粍雍螅涂梢越邮談e人的鏈接了
tcp_server_socket.listen(5)

# 有行的客戶端鏈接服務(wù)器溜嗜,就為這個客戶端生成一個新的套接字
# new_socket用來為這個客戶端服務(wù)
# tcp_server_socket可以省下等待其他新的客戶端鏈接
new_socket, client_addr = tcp_server_socket.accept()

# 接收對方發(fā)送的數(shù)據(jù)宵膨,最大為1024字節(jié)
recv_data = new_socket.recv(1024)
print('接收到的數(shù)據(jù)為:{}'.format(recv_data))

# 發(fā)送數(shù)據(jù)到客戶端
new_socket.send('send from server!')

# 關(guān)閉為此客戶端創(chuàng)建的socket
# 關(guān)閉后就不再服務(wù),要想繼續(xù)服務(wù)只能重連
new_socket.close()

# 關(guān)閉監(jiān)聽socket炸宵,此socket關(guān)閉表示不再接收客戶端鏈接
tcp_server_socket.close()
TCP客戶端
# tcp 客戶端 示例代碼
# -*- coding: utf-8 -*-
import socket

# 創(chuàng)建socket
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 鏈接服務(wù)器
server_addr = ('192.168.1.1', 6666)
tcp_client_socket.connect(server_addr)

# 提示輸入數(shù)據(jù)
send_data = input('請輸入要發(fā)送的數(shù)據(jù):')

# 接收服務(wù)器發(fā)送來的數(shù)據(jù)辟躏,最大接收1024字節(jié)
recv_data = tcp_client_socket.recv(1024)
print('接收到的數(shù)據(jù):{}'.format(recv_data))

# 關(guān)閉socket
tcp_client_socket.close()
'''tcp 模擬qq'''
# 客戶端代碼
# -*- coding: utf-8 -*-
import socket

# 創(chuàng)建secket
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 連接服務(wù)器
server_addr = ('192.168.1.1', 6666)
tcp_client_socket.connect(server_addr)

while True:
    # 提示用戶輸入數(shù)據(jù)
    send_data = input('send: ')
    if len(send_data)>0:
        tcp_client_socket.send(send_data)
    else:
        break
        
    # 接收對方發(fā)來的數(shù)據(jù),最大接收1024字節(jié)
    recv_data = tcp_client_socket.recv(1024)
    print('received:', recv_data)
    
# 關(guān)閉套接字
tcp_client_socket.close()

# 服務(wù)器 實例代碼
#-*- coding: utf-8 -*-
import socket

# create socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 綁定本地信息
address = ('', 6666)
tcp_server_socket.bind(address)

#用listen變socket為被動
tcp_server_socket.listen(5)

while True:
    # new socket
    new_socket, client_addr = tcp_server_socket.accept()
    
    while True:
        # 接收數(shù)據(jù)土全,最大接收1024字節(jié)
        recv_data = new_socket.recv(1024)
        
        # 接收到的數(shù)據(jù)長度為0捎琐,表示客戶端關(guān)閉鏈接
        if len(recv_data)>0:
            print('recv: ', recvData)
            
        else:
            break
        
        # 發(fā)送數(shù)據(jù)到客戶端
        send_data = input('send: ')
        new_socket.send(send_data)
        
        # 關(guān)閉此客戶端的套接字,不再為此客戶端服務(wù)
        new_socket.close()
        
        # 關(guān)閉監(jiān)聽socket
        tcp_server_socket.close()

網(wǎng)絡(luò)編程 應(yīng)用

單全雙工信息收發(fā)

單工:只能接收裹匙,半雙工:同一時刻只能接收或是發(fā)送瑞凑,全雙工:可接可發(fā)

socket(套接字)是全雙工的

編碼:encode("utf-8")

解碼:decode("utf-8")

# 簡單全雙工信息收發(fā) 例子
#-*-coding: utf-8 -*-

from threading import Thread

import socket

# 接收數(shù)據(jù),打印
def recv_data():
    while True:
        recv_msg = udp_socket.recvfrom(4096)
        print('收到>:{}:()'.str(recv_msg[1], recv_msg[0]))
        
# 發(fā)送數(shù)據(jù)
def send_data():
    while True:
        send_data = input('發(fā)送\r<:')
        print('>:')
        udp_socket.sendto(send_data.encode('utf-8'), (which_ip, which_port))
        
# socket, ip, port
udp_socket = None
which_ip = ''
which_port = 0

def main():
    global udp_socket
    global which_ip
    global which_port
    
    # 輸入ip:port
    which_ip = input('請輸入對方IP:\t')
    which_port = input("請輸入對方的port:\t")
    # create socket
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # bind local info
    udp_socket.bind('', 6666)
    
    # create threading
    tr = Thread(target=recv_data)
    ts = Thread(target=send_data)
    
    # start threading
    tr.start()
    ts.start()
    
    # blocking
    tr.join()
    ts.join()
    
if __name__=='__main__':
    main()
TFTP客戶端

TFTP 協(xié)議介紹

TFTP(Trivial File Transfer Protocol,簡單文件傳輸協(xié)議)是TCP/IP協(xié)議族中的一個用來在客戶端與服務(wù)器之間進(jìn)行簡單文件傳輸?shù)膮f(xié)議

TFTP特點:

簡單

占用資源小

適合傳遞小文件

適合在局域網(wǎng)進(jìn)行傳遞

端口號為69

基于UDP實現(xiàn)

TFTP下載過程

TFTP服務(wù)器默認(rèn)監(jiān)聽69號端口

當(dāng)客戶端發(fā)送“下載”請求(即讀請求)時概页,需要向服務(wù)器的69端口發(fā)送

服務(wù)器若批準(zhǔn)此請求,則使用一個新的籽御、臨時的端口進(jìn)行數(shù)據(jù)傳輸


TFTP下載過程

當(dāng)服務(wù)器找到需要現(xiàn)在的文件后,會立刻打開文件,把文件中的數(shù)據(jù)通過TFTP協(xié)議發(fā)送給客戶端

如果文件的總大小較大(比如3M)篱蝇,那么服務(wù)器分多次發(fā)送贺待,每次會從文件中讀取512個字節(jié)的數(shù)據(jù)發(fā)送過來

因為發(fā)送的次數(shù)有可能會很多徽曲,所以為了讓客戶端對接收到的數(shù)據(jù)進(jìn)行排序零截,所以在服務(wù)器發(fā)送那512個字節(jié)數(shù)據(jù)的時候,會多發(fā)2個字節(jié)的數(shù)據(jù)秃臣,用來存放序號涧衙,并且放在512個字節(jié)數(shù)據(jù)的前面,序號是從1開始的

因為需要從服務(wù)器上下載文件時奥此,文件可能不存在弧哎,那么此時服務(wù)器就會發(fā)送一個錯誤的信息過來,為了區(qū)分服務(wù)發(fā)送的是文件內(nèi)容還是錯誤的提示信息稚虎,所以又用了2個字節(jié) 來表示這個數(shù)據(jù)包的功能(稱為操作碼)撤嫩,并且在序號的前面

操作碼 功能
1 讀請求(下載)
2 寫請求(上傳)
3 表示數(shù)據(jù)包(DATA)
4 確認(rèn)碼(ACK)
5 錯誤

TFTP協(xié)議中規(guī)定,服務(wù)器確認(rèn)客戶端收到剛剛發(fā)送的數(shù)據(jù)包:當(dāng)客戶端接收到一個數(shù)據(jù)包的時候需要向服務(wù)器進(jìn)行發(fā)送確認(rèn)信息蠢终,這樣的包成為ACK(應(yīng)答包)

發(fā)送完:客戶端接收到的數(shù)據(jù)小于516(2字節(jié)操作碼+2字節(jié)序號+512字節(jié)數(shù)據(jù))

TFTP數(shù)據(jù)包的格式
'''
同一時刻只能為一個客戶進(jìn)行服務(wù)序攘,不能同時為多個客戶服務(wù)
類似于找一個“明星”簽字一樣,客戶需要耐心等待才可以獲取到服務(wù)
當(dāng)服務(wù)器為一個客戶端服務(wù)時寻拂,而另外的客戶端發(fā)起了connect程奠,
只要服務(wù)器listen的隊列有空閑的位置,就會為這個新客戶端進(jìn)行連接祭钉,
并且客戶端可以發(fā)送數(shù)據(jù)瞄沙,但當(dāng)服務(wù)器為這個新客戶端服務(wù)時,
可能一次性把所有數(shù)據(jù)接收完畢
當(dāng)recv接收數(shù)據(jù)時慌核,返回值為空距境,即沒有返回數(shù)據(jù),
那么意味著客戶端已經(jīng)調(diào)用了close關(guān)閉了垮卓;
因此服務(wù)器通過判斷recv接收數(shù)據(jù)是否為空 來判斷客戶端是否已經(jīng)下線
'''

# tftp 客戶端 示例代碼
# -*- coding:utf-8 -*-

import struct

from socket import *

import time

import os

def main():
    #0. 獲取要下載的文件名字:
    downloadFileName = raw_input("請輸入要下載的文件名:")
    #1.創(chuàng)建socket
    udpSocket = socket(AF_INET, SOCK_DGRAM)
    # 這里對下面的!H8sb5sb解釋:
    # !H占兩個垫桂,表示操作碼,8s是文件名的占位長度扒接,b是0的占位長度伪货,5sb同理
    requestFileData = struct.pack("!H%dsb5sb"%len(downloadFileName), 1, downloadFileName, 0, "octet", 0)
    #2. 發(fā)送下載文件的請求
    udpSocket.sendto(requestFileData, ("192.168.119.215", 69))
    flag = True #表示能夠下載數(shù)據(jù),即不擅長钾怔,如果是false那么就刪除
    num = 0
    f = open(downloadFileName, "w")

    while True:
        #3. 接收服務(wù)發(fā)送回來的應(yīng)答數(shù)據(jù)
        responseData = udpSocket.recvfrom(1024)
        # print(responseData)
        recvData, serverInfo = responseData
        opNum = struct.unpack("!H", recvData[:2])
        packetNum = struct.unpack("!H", recvData[2:4])
        print(packetNum[0])
        # print("opNum=%d"%opNum)
        # print(opNum)
        # if 如果服務(wù)器發(fā)送過來的是文件的內(nèi)容的話:
        if opNum[0] == 3: #因為opNum此時是一個元組(3,)碱呼,所以需要使用下標(biāo)來提取某個數(shù)據(jù)
            #計算出這次應(yīng)該接收到的文件的序號值,應(yīng)該是上一次接收到的值的基礎(chǔ)上+1
            num = num + 1
            # 如果一個下載的文件特別大宗侦,即接收到的數(shù)據(jù)包編號超過了2個字節(jié)的大小
            # 那么會從0繼續(xù)開始愚臀,所以這里需要判斷,如果超過了65535 那么就改為0
            if num==65536:
                num = 0
            # 判斷這次接收到的數(shù)據(jù)的包編號是否是 上一次的包編號的下一個
            # 如果是才會寫入到文件中矾利,否則不能寫入(因為會重復(fù))
            if num == packetNum[0]:
                # 把收到的數(shù)據(jù)寫入到文件中
                f.write(recvData[4:])
                num = packetNum[0]
                #整理ACK的數(shù)據(jù)包
                ackData = struct.pack("!HH", 4, packetNum[0])
                udpSocket.sendto(ackData, serverInfo)
        elif opNum[0] == 5:
            print("sorry姑裂,沒有這個文件....")
            flag = False
        # time.sleep(0.1)
        if len(recvData)<516:
            break
        if flag == True:
            f.close()
        else:
            os.unlink(downloadFileName)#如果沒有要下載的文件馋袜,那么就需要把剛剛創(chuàng)建的文件進(jìn)行刪除

if __name__ == '__main__':
    main()
單進(jìn)程 TCP服務(wù)器
# 單進(jìn)程 TCP服務(wù)器
from socket import *

server_socket = socket(AF_INET, SOCK_STREAM)

# 重用綁定的信息
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

local_addr = ('', 6666)

server_socket.bind(local_addr)

server_socket.listen(5)

while True:
    print('主進(jìn)程,等待新客戶端的到來')
    new_socket, dest_addr = server_socket.accept()
    print('主進(jìn)程舶斧, 接下來負(fù)責(zé)數(shù)據(jù)處理{}'.format(str_dest_addr))
    
    try:
        while True:
            recv_data = new_socket.recv(1024)
            if len(recv_data)>0:
                print('recv[{}]:{}'.format(str(dest_addr), recv_data))
            else:
                print('{}客戶端已經(jīng)關(guān)閉'.format(str(dest_addr)))
                
    finally:
        new_socket.close()

server_socket.close()
主進(jìn)程欣鳖,等待新客戶端的到來
多進(jìn)程服務(wù)器
# 多進(jìn)程服務(wù)器
'''
通過為每個客戶端創(chuàng)建一個進(jìn)程的方式,能夠同時為多個客戶端進(jìn)行服務(wù)
當(dāng)客戶端不是特別多的時候茴厉,這種方式還行泽台,如果有幾百上千個,就不可取了矾缓,
因為每次創(chuàng)建進(jìn)程等過程需要好較大的資源
'''
import socket

from multiprocessing import Process

from time import sleep

# 處理客戶端請求怀酷,并為其服務(wù)
def deal_with_client(new_socket, dest_addr):
    while True:
        recv_data = enw_socket.recv(1024)
        
        if len(recv_data)>0:
            print('recv[{}]: {}'.format(str(dest_addr), recv_data))
        else:
            print('[{}]客戶端已經(jīng)關(guān)閉'.format(str(dest_addr)))
            break
        new_socket.close()
        
def main():
    server_socket = socket(AF_INET, SOCK_STREAM)
    server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    local_addr = ('', 6666)
    server_socket.bind(local_addr)
    server_socket.listen(5)
    
    try:
        while True:
            print('主進(jìn)程,等待新客戶端的到來')
            new_socket, dest_addr = server_socket.accept()
            
            print('主進(jìn)程嗜闻, 接下來創(chuàng)建一個新的進(jìn)程負(fù)責(zé)數(shù)據(jù)處理[{}]'.format(str(dest_addr)))
            client = Process(target=deal_with_client, args=(new_socket, dest_addr))
            client.start()
            
            # 因為已經(jīng)向子進(jìn)程copy了一份(引用)蜕依,并且父進(jìn)程中這個套接字也沒有用處了
            # 所以關(guān)閉
            new_socket.close()
    finally:
        3 當(dāng)為所有的客戶端服務(wù)完后再關(guān)閉,表示不再接收新的客戶端的連接
        server_socket.close()
        
if __name__=='__main__':
    main()
多線程服務(wù)器
# 多線程服務(wù)器
# -*- coding: utf-8 -*-

from socket import *

from threading import Thread

from thime import sleep

# 處理客戶的請求并執(zhí)行事情
def deal_with_client(new_socket, dest_addr):
    while True:
        recv_data = new_socket.recv(1024)
        
        if len(recv_data)>0:
            print('recv[{}]: {}'.format(str(dest_addr), recv_data))
            
        else:
            print('[{}]客戶端已經(jīng)關(guān)閉'.format(dest_addr))
            break
        
        new_socket.close()
        
def main():
    server_socket = socket(AF_INET, SOCK_STREAM)
    server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    local_addr = ('',6666)
    server_socket.bind(local_addr)
    server_socket.listen(5)
    
    try:
        while True:
            print('-----主進(jìn)程琉雳,样眠,等待新客戶端的到來------')
            new_socket, dest_addr = server_socket.accept()
            
            print('主進(jìn)程,接下來創(chuàng)捷一個新的進(jìn)程負(fù)責(zé)數(shù)據(jù)處理[{}]'.format(
                str(dest_addr)
            ))
            client = Thread(target=deal_with_client, args=(new_socket, dest_addr))
            client.start()
            
            # 線程中共享socket咐吼, 若關(guān)閉會導(dǎo)致套接字不可用
            # 但是此時在線程中這個socket可能還在收數(shù)據(jù)吹缔,因此不可關(guān)閉
            # new_socket.close()
            
    finally:
        server_socket.close()

if __name__ =='__main__':
    main()
單進(jìn)程服務(wù)器,非阻塞模式
# 服務(wù)器
# -*- coding: utf-8 -*-

from socket import *
import time

# 存儲所有的新連接的socket
g_socket_list = []

def main():
    server_socket = socket(AF_INET, SOCK_STREAM)
    server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    local_addr = ('', 6666)
    server_socket.bind(local_addr)
    # 可以適當(dāng)修改listen中的值來看看不同的現(xiàn)象
    server_socket.listen(1000)
    # 設(shè)置套接字為非阻塞
    # 設(shè)置為非阻塞后锯茄,若accept時厢塘,無客戶端connect,accept會拋出一個異常肌幽,
    # 所以需要try處理異常
    server_socket.setblocking(False)
    
    while True:
        # 測試
        # time.sleep(0.5)
        
        try:
            new_client_info = server_socket.accept()
        except Exception as result:
            pass
        else:
            print('一個新的客戶端到來:{}'.format(str(new_client_info))
            new_client_infp[0].setblocking(False)
            g_socket_list.append(new_client_info)
        # 存儲需要刪除的客戶端信息
        need_del_client_info_list = []
        
        for client_socket, client_addr in g_socket_list:
            try:
                recv_data = client_socket.recv(1024)
                if len(recv_data)>0:
                    print('recv[{}]:{}'.format(str(client_addr), recv_data))
                else:
                    print('[{}]客戶端已經(jīng)關(guān)閉'.format(client_addr))
                    client_socket.close()
                    g_need_del_client_info_list.append(client_socket, client_addr)
            except Exception as result:
                  pass
                  
        for need_del_client_info in need_del_client_info_list:
            g_socket_list.remove(need_del_client_info)

if __name__=='__main__':
    main()
                  
# 客戶端
#coding=utf-8
from socket import *
import random
import time

serverIp = input("請輸入服務(wù)器的ip:")
connNum = input("請輸入要鏈接服務(wù)器的次數(shù)(例如1000):")
g_socketList = []
for i in range(int(connNum)):
    s = socket(AF_INET, SOCK_STREAM)
    s.connect((serverIp, 7788))
    g_socketList.append(s)
    print(i)

while True:
    for s in g_socketList:
        s.send(str(random.randint(0,100)))

    # 用來測試用
    #time.sleep(1)
select版 TCP服務(wù)器

select原理

多路復(fù)用的模型中捺僻,較常用的有select周霉,epoll模型育拨。這兩個都是系統(tǒng)接口温技,
由操作系統(tǒng)提供。當(dāng)然廊移,Python的select模塊進(jìn)行了更高級的封裝糕簿。

網(wǎng)絡(luò)通信被Unix系統(tǒng)抽象為文件的讀寫,通常是一個設(shè)備狡孔,由設(shè)備驅(qū)動程序提供懂诗,驅(qū)動可以知道自身的數(shù)據(jù)是否可用。支持阻塞操作的設(shè)備驅(qū)動通常會實現(xiàn)一組自身的等待隊列苗膝,如讀/寫等待隊列用于支持上層(用戶層)所需的block或non-block操作殃恒。設(shè)備的文件的資源如果可用(可讀或者可寫)則會通知進(jìn)程,反之則會讓進(jìn)程睡眠,等到數(shù)據(jù)到來可用的時候离唐,再喚醒進(jìn)程病附。
這些設(shè)備的文件描述符被放在一個數(shù)組中,然后select調(diào)用的時候遍歷這個數(shù)組亥鬓,如果對于的文件描述符可讀則會返回改文件描述符完沪。當(dāng)遍歷結(jié)束之后,如果仍然沒有一個可用設(shè)備文件描述符贮竟,select讓用戶進(jìn)程則會睡眠丽焊,直到等待資源可用的時候在喚醒较剃,遍歷之前那個監(jiān)視的數(shù)組咕别。每次遍歷都是依次進(jìn)行判斷的。

select 優(yōu)缺點

優(yōu)點

select目前幾乎在所有的平臺上支持写穴,其良好跨平臺支持也是它的一個優(yōu)點惰拱。

缺點

select的一個缺點在于單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024啊送,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制偿短,但是這樣也會造成效率的降低。
一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大馋没,具體數(shù)目可以cat /proc/sys/fs/file-max察看昔逗。32位機默認(rèn)是1024個。64位機默認(rèn)是2048.
對socket進(jìn)行掃描時是依次掃描的篷朵,即采用輪詢的方法勾怒,效率較低。
當(dāng)套接字比較多的時候声旺,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度笔链,不管哪個Socket是活躍的,都遍歷一遍腮猖。這會浪費很多CPU時間鉴扫。

# select 回顯服務(wù)器
import select
import socket
import sys

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 6660))
server.listen(5)

inputs = [server, sys.stdin]

running = True

while True:
    # 調(diào)用select函數(shù),阻塞等待
    readable, writeable, exceptional = select.select(inputs, [], [])
    
    # 數(shù)據(jù)到達(dá)澈缺,循環(huán)
    for sock in readable:
        # 監(jiān)聽到新的連接
        if sock == server:
            conn, addr = server.accept()
            # select 監(jiān)聽的socket
            inputs.append(conn)
            # 監(jiān)聽到鍵盤有輸入
        elif sock == sys.stdin:
            cmd = sys.stdin.readline()
            running = False
            break
            
        # 有數(shù)據(jù)到達(dá)
        else:
            # 讀取客戶端連接發(fā)送的數(shù)據(jù)
            data = sock.recv(1024)
            if data:
                sock.send(data)
            else:
                # 移除select監(jiān)聽的socket
                inputs.remove(sock)
                sock.close()
        # 如果檢測到用戶輸入敲擊鍵盤坪创,那么就退出
        if not running:
            break
            
server.close()
        
# 包含writeList服務(wù)器
# -*- coding: utf-8 -*-
import socket
import Queue
form select import select

server_ip = ('', 8888)

# 保存客戶端發(fā)過來的消息,存入消息隊列
message_queue = {}
input_list = []
output_list = []

if __name__ == '__main__':
    server = socket.socket()
    server.bind(server_ip)
    server.listen(10)
    # 設(shè)置為非阻塞
    server.setblocking(False)
    # 初始化將服務(wù)器加入監(jiān)聽列表
    input_list.append(server)
    
    while True:
        # 開始select監(jiān)聽姐赡,對input_list中的服務(wù)端server進(jìn)行監(jiān)聽
        stdinput, stdoutput, stderr = select(inut_list, out_list, input_list)
        
        # 循環(huán)判斷是否有客戶端連接進(jìn)來莱预,有客戶端連接時select將觸發(fā)
        for obj in stdinput:
            # 判斷當(dāng)前出發(fā)的是不是服務(wù)端對象,當(dāng)觸發(fā)的對象是服務(wù)端對象時雏吭,
            # 說明有新客戶連接進(jìn)來了
            if obj == server:
                # 接收客戶端的連接锁施, 獲取客戶端對象和客戶端地址信息
                
                conn, addr = server.accept()
                
                print('client {} connected!'.format(addr))
                # 將客戶端對象也加入到監(jiān)聽的列表中,當(dāng)客戶端發(fā)送消息時select將觸發(fā)
                input_list.append(conn)
                # 為連接的客戶端單獨創(chuàng)建一個消息隊列,用來保存客戶端發(fā)送的消息
                message_queue[conn] = Queue.Queue()
            else:
                # 將客戶端加入到了監(jiān)聽列表(input_list)悉抵,客戶端發(fā)送消息就觸發(fā)
                try:
                    recv_data = obj.recv(1024)
                    # 客戶端未斷開
                    print('received {} from client {} '.format(recv_data, str(addr)))
                    # 將收到的消息放入到各客戶端的消息隊列中
                    message_queue[obj].put(recv_data)
                    # 將回復(fù)操作放到output列表中肩狂,讓select監(jiān)聽
                    if obj not in output_list:
                        output_list.append(obj)
                
                except ConnectionResetError:
                    # 客戶端斷開連接了,將客戶端的監(jiān)聽中input列表中移除
                    input_list.remove(obj)
                    # 移除客戶端的消息隊列
                    del message_queue[obj]
                    print("\n[input] Client %s disconnected"%str(addr))
        # 如果現(xiàn)在沒有客戶端請求姥饰,也沒有客戶端發(fā)送消息時傻谁,
        # 開始對發(fā)送消息隊列進(jìn)行處理,是否需要發(fā)送消息
        for sendobj in output_list:
            try:
                # 如果消息隊列中有消息列粪,從消息隊列中獲取要發(fā)送的消息
                if not message_queue[sendobj].empty():
                    # 從該客戶端對象的消息隊列中獲取要發(fā)送的消息
                    send_data = message_queue[sendobj].get()
                    sendobj.send(send_data)
                else:
                    # 將監(jiān)聽移除等待下一次客戶端發(fā)送消息
                    output_list.remove(sendobj)
            except ConnectResetError:
                # 客戶端連接斷開
                del message_queue[sendobj]
                output_list.remove(sendobj)
                print("\n[output] Client  %s disconnected"%str(addr))
epoll版-TCP服務(wù)器

epoll的優(yōu)點

沒有最大并發(fā)連接的限制审磁,能打開的FD(指的是文件描述符,通俗的理解就是套接字對應(yīng)的數(shù)字編號)的上限遠(yuǎn)大于1024
效率提升岂座,不是輪詢的方式态蒂,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù)费什;即epoll最大的優(yōu)點就在于它只管你“活躍”的連接钾恢,而跟連接總數(shù)無關(guān),因此在實際的網(wǎng)絡(luò)環(huán)境中鸳址,epoll的效率就會遠(yuǎn)遠(yuǎn)高于select和poll瘩蚪。

epoll使用說明

EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)

epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認(rèn)模式稿黍,LT模式與ET模式的區(qū)別如下:

LT模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序疹瘦,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll時巡球,會再次響應(yīng)應(yīng)用程序并通知此事件言沐。

ET模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件辕漂。如果不處理呢灶,下次調(diào)用epoll時,不會再次響應(yīng)應(yīng)用程序并通知此事件钉嘹。

# epoll 參考代碼

import socket

import select

# 創(chuàng)建socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 設(shè)置可以重復(fù)使用綁定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 綁定本機信息
s.bind('', 1234)

# 綁定本機信息
s.bind(10)

# 創(chuàng)建一個epoll對象
# 測試鸯乃,用來打印套接字對應(yīng)的文件描述符
# print s.fileno()
# print select.EPOLLIN|select.EPOLLET

# 注冊事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已經(jīng)注冊過跋涣,則會發(fā)生異常
# 將創(chuàng)建的套接字添加到epoll的事件監(jiān)聽中
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)

connections = {}
addresses = {}

# 循環(huán)等待客戶端的到來或者對方發(fā)送數(shù)據(jù)
while True:
    # epoll 進(jìn)行fd掃描的地方 -- 未指定超時時間為阻塞等待
    epoll_list = epoll.poll()
    
    # 對事件進(jìn)行判斷
    for fd, events in epoll_list:
        # print fd
        # print event
        
        # 如果是socket創(chuàng)建的socket被激活
        if fd == s.fileno():
            conn, addr = s.accept()
            
            print('有新的客戶端到來{}'.format(str(addr)))
            
            # 將conn和addr信息分別保存起來
            connections[conn.fileno()] = conn
            addresses[conn.fileno()] = addr
            
            # 向epoll中注冊連接socket 的可讀事件
            epoll.register(conn.fileno(), select.EPOLLIN|select.EPOLLET)
            
        elif events == select.EPOLLIN:
            # 從激活fd上接收
            recv_data = connections[fd].recv(1024)
            
            if len(recv_data)>0:
                print('recv: {} '.format(recv_data))
                
            else:
                # 從epoll中移除該連接 fd
                epoll.unregister(fd)
                # server 側(cè)主動關(guān)閉該連接 fd
                connections[df].close()
                
                print('{} ---offline---'.format(str(addresses[fd])))
# gevent版TCP服務(wù)器

import sys

import time

import gevent

from gevent import socket, monkey

monkey.patch_all()

def handle_request(conn):
    data = conn.recv(1024)
    if not data:
        conn.close()
        break
        
    print('recv: {}'.format(data))
    conn.send(data)
    
def server(port):
    s = socket.socket()
    s.bind(('', port))
    s.listen(5)
    
    while True:
        cli, addr = s.accept()
        gevent,spawn(handle_request, cli)
        
if __name__ == '__main__':
    server(4567)
  File "<ipython-input-1-a04bdaa77df3>", line 17
    break
    ^
SyntaxError: 'break' outside loop



C:\MyPrograms\Anaconda3\lib\site-packages\gevent\hub.py:154: UserWarning: libuv only supports millisecond timer resolution; all times less will be set to 1 ms
  with loop.timer(seconds, ref=ref) as t:

網(wǎng)絡(luò)分析工具

wireshark

linux 下可能出現(xiàn)問題:打開wireshark提示權(quán)限不足

解決方法:
參考網(wǎng)址

添加組缨睡,wireshark,但是安裝軟件時已經(jīng)創(chuàng)建,這里可以省略

sudo groupadd wireshark

將自己添加到wireshark組

sudo usermod -a -G wireshark 'username'

newgrp wireshark

更改組別

sudo chgrp wireshark /usr/bin/dumpcap

添加權(quán)限(1-x, 2-w, 4-r)

sudo chmod 754 /usr/bin/dumpcap

這里原作者有兩個方法陈辱,我選擇一個簡單的

sudo setcap cap_net_raw,cap_netadmin=eip /usr/bin/dumpcap

sudo reboot now

至此奖年,問題解決

Packet Tracer
tcp-ip協(xié)議

hub(集線器)能夠完成多個電腦的鏈接
每個數(shù)據(jù)包的發(fā)送都是以廣播的形式進(jìn)行的,容易堵塞網(wǎng)絡(luò)

交換機能夠完成多個電腦的鏈接
每個數(shù)據(jù)包的發(fā)送都是以廣播的形式進(jìn)行的沛贪,容易堵塞網(wǎng)絡(luò)
如果PC不知目標(biāo)IP所對應(yīng)的的MAC陋守,那么可以看出震贵,pc會先發(fā)送arp廣播,得到對方的MAC然后水评,在進(jìn)行數(shù)據(jù)的傳送
當(dāng)switch第一次收到arp廣播數(shù)據(jù)猩系,會把arp廣播數(shù)據(jù)包轉(zhuǎn)發(fā)給所有端口(除來源端口);如果以后還有pc詢問此IP的MAC中燥,那么只是向目標(biāo)的端口進(jìn)行轉(zhuǎn)發(fā)數(shù)據(jù)

路由器(Router)又稱網(wǎng)關(guān)設(shè)備(Gateway)是用于連接多個邏輯上分開的網(wǎng)絡(luò)

在同一網(wǎng)段的pc寇甸,需要設(shè)置默認(rèn)網(wǎng)關(guān)才能把數(shù)據(jù)傳送過去 通常情況下,都會把路由器默認(rèn)網(wǎng)關(guān)
當(dāng)路由器收到一個其它網(wǎng)段的數(shù)據(jù)包時疗涉,會根據(jù)“路由表”來決定拿霉,把此數(shù)據(jù)包發(fā)送到哪個端口;路由表的設(shè)定有靜態(tài)和動態(tài)方法
每經(jīng)過一次路由器咱扣,那么TTL值就會減一

Ciso 的packet tracer

hub集線器現(xiàn)在基本已經(jīng)廢棄绽淘,現(xiàn)在一般用交換機

路由器:鏈接不同網(wǎng)段的網(wǎng)絡(luò),使不同網(wǎng)段的設(shè)備可以通訊

mac地址在通信傳輸中偏窝,是動態(tài)的收恢,在兩個設(shè)備之間會發(fā)生變話

IP地址在通信傳輸中,是靜態(tài)的祭往,在整個傳輸中不會發(fā)生變化


Django

虛擬環(huán)境

安裝虛擬環(huán)境:

Python3 :pip3 install python3-venv

python2 :pip install python-virtualenvs

創(chuàng)建虛擬環(huán)境:

python3 -m venv DirName(這個可以自定義名稱)

python -m virtualenv DirName

啟動虛擬環(huán)境:

cd /home/DirName/bin/;source activate

關(guān)閉虛擬環(huán)境:

deactivate

安裝django

pip install django==1.8 # 這里的==1.8表示的是要安裝的版本號,可以不寫

創(chuàng)建一個新的項目

django-admin startproject Demo # 這里的Demo是django的項目名

啟動項目

python3 manage.py startapp Demo

運行服務(wù)

python3 manage.py runserver 8080 # 這里的8080是端口號火窒,可以隨意更改(>1023)

設(shè)置遷移

python3 manage.py makemigrations

應(yīng)用遷移

python3 manage.py migrate

進(jìn)入shell

python3 manage.py shell

視圖view:接受請求硼补,邏輯處理,調(diào)用數(shù)據(jù)熏矿,輸出響應(yīng)

配置url:在自己的應(yīng)用中配置正則url(正則表達(dá)式已骇,視圖的名稱)

DIRS:定義目錄

APP_DIRS:在項目的根目錄創(chuàng)建模板目錄

建的項目不打算移植,可以用DIRS票编,希望以后移植褪储,用APP_DIRS更好一些。

django模板處理

1.加載模板內(nèi)容

2.根據(jù)模板內(nèi)容渲染

# 加載渲染的完整代碼
from django.tmplate import loader, RequestContext

from django.http import HttpResponse

def index(request):

    tmp = loader.get_tmplate("Demo/demo.html")

    context = RequestContext(request, {})

    return HttpResponse(tmp.render(context))
# 簡化代碼:
from django.shortcuts import render

def index(request):

    return render(request, "Demo/demo.html")

# 這里的簡化代碼中render_to_string("")返回一個字符串

# render(request, "模板", context)

DTL語言

'''
變量

{{ var }}:字典慧域,屬性或方法鲤竹,數(shù)字索引,空字符串昔榴,

注意:模板中調(diào)用方法不能傳遞參數(shù)辛藻,因為不能有()出現(xiàn)在模板中

標(biāo)簽

{% 代碼  %}

{%for%}

{{forloop.counter}} # 返回一個當(dāng)前是第幾次返回的數(shù)字

{%empty%} # 當(dāng)for in 的list不存在或空時,執(zhí)行這個標(biāo)簽

{%endfor%}

{%if%}

{%elif%}

{%else%}

{%endif%}

{%comment%}

這里是一個多行注釋

{%endcomment%}

反向解析:

{%url "namespace:name" "參數(shù)1" "參數(shù)2" %} # 反向解析

{%crsf_token%} # 用于跨站請求偽造保護(hù)

過濾器

{{變量|過濾器}} # “|” 符合是管道符號互订,用來應(yīng)用過濾器

這里用“:”來傳遞參數(shù)

{{name|length>10}} 選出長度大于10 的name

注釋

{# 注釋 #}

模板的繼承

# 這是用在父模板的吱肌,base.html

{%block my_name%} # 這里的my_name只是用做變量的區(qū)分

這里是要填充的內(nèi)容

{%endblock my_name%}

# 父模板中可以用很多的{%block%}

# 這是用在子模板的,demo.html

{%extends "要繼承的父模板的位置"%} # 這個語句要寫在子模板的首行

{%block my_name%}

這里來填充父模板的block

{%endblock my_name%}

注意:一旦使用了繼承仰禽,那么要顯示的內(nèi)容只能寫在block中氮墨,寫在外邊的無法顯示

html轉(zhuǎn)義:

1 用過濾器:

{{value|escape}} # 這里是轉(zhuǎn)義纺蛆,默認(rèn)是轉(zhuǎn)義

{{value|safe}} # 這里是不轉(zhuǎn)義

2 用標(biāo)簽

{%autoescape off%} # 這里是關(guān)閉自動轉(zhuǎn)義

{{value}} # 這里是不希望轉(zhuǎn)義的后臺傳輸過來的內(nèi)容

{%endautoescape%}

'''

django高級

'''
靜態(tài)文件

static

STATIC_URL = "url"

STATICFILES_DIRS = [

    os.path.join(BASE_DIR, "static")

# 注意這里的static是項目下的一個文件夾的名字,這里必須相同

]

在網(wǎng)頁中首行添加

{%load static from staticfiles %}

在img標(biāo)簽中的src使用{%static "fileName"%}

中間件

MIDDLEWARE_CLASS

是一個輕量級规揪、底層的插件系統(tǒng)

__init__方法:第一次訪問

process_request:url匹配前

process_view:視圖前

process_tmplate_response:視圖后犹撒,html前,在請求上調(diào)用

process_response:HTML后,返回到瀏覽器之前

process_exception:視圖中出錯粒褒,到次方法识颊,轉(zhuǎn)到process_response
'''
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奕坟,隨后出現(xiàn)的幾起案子祥款,更是在濱河造成了極大的恐慌,老刑警劉巖月杉,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刃跛,死亡現(xiàn)場離奇詭異,居然都是意外死亡苛萎,警方通過查閱死者的電腦和手機桨昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腌歉,“玉大人蛙酪,你說我怎么就攤上這事∏谈牵” “怎么了桂塞?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馍驯。 經(jīng)常有香客問我阁危,道長,這世上最難降的妖魔是什么汰瘫? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任狂打,我火速辦了婚禮,結(jié)果婚禮上混弥,老公的妹妹穿的比我還像新娘趴乡。我一直安慰自己,他們只是感情好剑逃,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布浙宜。 她就那樣靜靜地躺著,像睡著了一般蛹磺。 火紅的嫁衣襯著肌膚如雪粟瞬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天萤捆,我揣著相機與錄音裙品,去河邊找鬼俗批。 笑死,一個胖子當(dāng)著我的面吹牛市怎,可吹牛的內(nèi)容都是我干的岁忘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼区匠,長吁一口氣:“原來是場噩夢啊……” “哼干像!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驰弄,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤麻汰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后戚篙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體五鲫,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年岔擂,在試婚紗的時候發(fā)現(xiàn)自己被綠了位喂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡乱灵,死狀恐怖塑崖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阔蛉,我是刑警寧澤弃舒,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站状原,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏苗踪。R本人自食惡果不足惜颠区,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望通铲。 院中可真熱鬧毕莱,春花似錦、人聲如沸颅夺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吧黄。三九已至部服,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拗慨,已是汗流浹背廓八。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工奉芦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剧蹂。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓声功,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宠叼。 傳聞我的和親對象是個殘疾皇子先巴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容