協(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)址
網(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ù)鏈路層饵史,物理層
對上圖的說明:
網(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é)果圖
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ù)器训柴、客戶端
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)建流程圖
# 創(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報文首部
源端口和目的端口干花,各占2個字節(jié)妄帘,分別寫入源端口和目的端口;
序號池凄,占4個字節(jié)抡驼,TCP連接中傳送的字節(jié)流中的每個字節(jié)都按順序編號。例如肿仑,一段報文的序號字段值是 301 致盟,而攜帶的數(shù)據(jù)共有100字段,顯然下一個報文段(如果還有的話)的數(shù)據(jù)序號應(yīng)該從401開始尤慰;
確認(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擎椰;
數(shù)據(jù)偏移,占4位创肥,它指出TCP報文的數(shù)據(jù)距離TCP報文段的起始處有多遠(yuǎn)达舒;
保留,占6位叹侄,保留今后使用巩搏,但目前應(yīng)該都位0;
緊急URG趾代,當(dāng)URG=1贯底,表明緊急指針字段有效。告訴系統(tǒng)此報文段中有緊急數(shù)據(jù)撒强;
確認(rèn)ACK禽捆,僅當(dāng)ACK=1時,確認(rèn)號字段才有效飘哨。TCP規(guī)定胚想,在連接建立后所有報文的傳輸都必須把ACK置1;
推送PSH芽隆,當(dāng)兩個應(yīng)用進(jìn)程進(jìn)行交互式通信時浊服,有時在一端的應(yīng)用進(jìn)程希望在鍵入一個命令后立即就能收到對方的響應(yīng),這時候就將PSH=1胚吁;
復(fù)位RST牙躺,當(dāng)RST=1,表明TCP連接中出現(xiàn)嚴(yán)重差錯腕扶,必須釋放連接孽拷,然后再重新建立連接;
同步SYN蕉毯,在連接建立時用來同步序號乓搬。當(dāng)SYN=1,ACK=0代虾,表明是連接請求報文进肯,若同意連接,則響應(yīng)報文中應(yīng)該使SYN=1棉磨,ACK=1江掩;
終止FIN,用來釋放連接。當(dāng)FIN=1环形,表明此報文的發(fā)送方的數(shù)據(jù)已經(jīng)發(fā)送完畢策泣,并且要求釋放;
窗口抬吟,占2字節(jié)萨咕,指的是通知接收方,發(fā)送本報文你需要有多大的空間來接受火本;
檢驗和危队,占2字節(jié),校驗首部和數(shù)據(jù)這兩部分钙畔;
緊急指針茫陆,占2字節(jié),指出本報文段中的緊急數(shù)據(jù)的字節(jié)數(shù)擎析;
選項簿盅,長度可變,定義一些其他的可選的參數(shù)揍魂。
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ù)步责,揮手完*
最開始的時候客戶端和服務(wù)器都是處于CLOSED狀態(tài)。主動打開連接的為客戶端禀苦,被動打開連接的是服務(wù)器蔓肯。
TCP服務(wù)器進(jìn)程先創(chuàng)建傳輸控制塊TCB,時刻準(zhǔn)備接受客戶進(jìn)程的連接請求振乏,此時服務(wù)器就進(jìn)入了LISTEN(監(jiān)聽)狀態(tài)蔗包;
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ù)忆谓,但需要消耗掉一個序號裆装。
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ù),但是同樣要消耗一個序號蔽挠。
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四次揮手
數(shù)據(jù)傳輸完畢后昵仅,雙方都可釋放連接。最開始的時候累魔,客戶端和服務(wù)器都是處于ESTABLISHED狀態(tài)摔笤,然后客戶端主動關(guān)閉,服務(wù)器被動關(guān)閉垦写。
客戶端進(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ù)么鹤,也要消耗一個序號终娃。
服務(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ù)的時間。
客戶端收到服務(wù)器的確認(rèn)請求后塞栅,此時者铜,客戶端就進(jìn)入FIN-WAIT-2(終止等待2)狀態(tài),等待服務(wù)器發(fā)送連接釋放報文(在這之前還需要接受服務(wù)器發(fā)送的最后的數(shù)據(jù))。
服務(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)探赫。
客戶端收到服務(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)。
服務(wù)器只要收到了客戶端發(fā)出的確認(rèn)刨晴,立即進(jìn)入CLOSED狀態(tài)屉来。同樣,撤銷TCB后狈癞,就結(jié)束了這次的TCP連接茄靠。可以看到蝶桶,服務(wù)器結(jié)束TCP連接的時間要比客戶端早一些慨绳。
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的2MSL問題
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短連接的情況:
client 向 server 發(fā)起連接請求
server 接到請求先嬉,雙方建立連接
client 向 server 發(fā)送消息
server 回應(yīng) client
一次讀寫完成,此時雙方任何一個都可以發(fā)起 close 操作
在第 步驟5中楚堤,一般都是 client 先發(fā)起 close 操作疫蔓。當(dāng)然也不排除有特殊的情況。
從上面的描述看身冬,短連接一般只會在 client/server 間傳遞一次讀寫操作衅胀!
TCP長連接
模擬一種TCP長連接的情況:
client 向 server 發(fā)起連接請求
server 接到請求,雙方建立連接
client 向 server 發(fā)送消息
server 回應(yīng) client
一次讀寫完成酥筝,連接不關(guān)閉
后續(xù)讀寫操作...
長時間操作之后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攻擊
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ù)傳輸
當(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ù))
'''
同一時刻只能為一個客戶進(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
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
'''