大師兄的Python學(xué)習(xí)筆記(十四): 迭代器、生成器和協(xié)程
大師兄的Python學(xué)習(xí)筆記(十六): FTP與ftplib
一吱殉、關(guān)于互聯(lián)網(wǎng)的一些基礎(chǔ)知識(shí)
- 當(dāng)我們需要在不同的計(jì)算機(jī)運(yùn)行程序友雳,并進(jìn)行通信時(shí)押赊,就需要涉及到互聯(lián)網(wǎng)編程涕俗。
- 本篇案例的運(yùn)行環(huán)境包括一臺(tái)本地計(jì)算機(jī)和一臺(tái)Linux服務(wù)器再姑。
1.關(guān)于程序架構(gòu)
1.1 C/S架構(gòu)
- 客戶端(Client)與服務(wù)器端(Server)的架構(gòu)元镀。
- 客戶端一般泛指應(yīng)用程序,對(duì)用戶端的電腦操作環(huán)境依賴較大蔽挠。
-
比如: steam和qq等澳淑。
1.2 B/S架構(gòu)
- 瀏覽器端(Browser)與服務(wù)器端(Server)的架構(gòu)。
- 僅需要瀏覽器氢拥,就可以通過HTTP請(qǐng)求服務(wù)器端的資源和增刪改查嫩海。
-
比如微博網(wǎng)頁版和各種網(wǎng)站等叁怪。
2.關(guān)于IP和Port
- ip+port可以在互聯(lián)網(wǎng)上定位某個(gè)設(shè)備的某個(gè)程序涣觉,比如(百度): 220.181.38.150:80
2.1 關(guān)于IP
- IP是指互聯(lián)網(wǎng)協(xié)議地址(Internet Protocol Address), 是設(shè)備在互聯(lián)網(wǎng)上的唯一地址官册。
1)IPv4和IPv6
- IPv4(Internet Protocol version 4)指的是互聯(lián)網(wǎng)通信協(xié)議的第四版,是第一個(gè)被廣泛部署的版本。
- IPv4使用32位(4字節(jié))地址明刷,因此共有43億(2^32-1)個(gè)地址, 全球IPv4地址已于2019年11月26日耗盡愚争。
- IPv6(Internet Protocol Version 6)指的是互聯(lián)網(wǎng)通信協(xié)議的第六版,是用于替代IPv4的下一代IP協(xié)議轰枝。
- IPv6將IPv4中32位的地址長(zhǎng)度擴(kuò)展到了128位,即有2^128-1個(gè)地址,可以將地球上每一粒沙子都賦予ip地址。
2)IP地址的分類
類型 號(hào)段 說明 A類 1.0.0.0-126.0.0.0 第一個(gè)字節(jié)為網(wǎng)絡(luò)號(hào)诚撵,后三個(gè)字節(jié)為主機(jī)號(hào)寿烟。
該類IP地址的最前面為“0”筛武,所以地址的網(wǎng)絡(luò)號(hào)取值于1~126之間。
一般用于大型網(wǎng)絡(luò)硕噩。B類 128.0.0.0-191.255.0.0 前兩個(gè)字節(jié)為網(wǎng)絡(luò)號(hào)炉擅,后兩個(gè)字節(jié)為主機(jī)號(hào)谍失。
該類IP地址的最前面為“10”颠印,所以地址的網(wǎng)絡(luò)號(hào)取值于128~191之間线罕。
一般用于中等規(guī)模網(wǎng)絡(luò)钞楼。C類 192.0.0.0-223.255.255.0 前三個(gè)字節(jié)為網(wǎng)絡(luò)號(hào),最后一個(gè)字節(jié)為主機(jī)號(hào)宛琅。
該類IP地址的最前面為“110”嘿辟,所以地址的網(wǎng)絡(luò)號(hào)取值于192~223之間仓洼。
一般用于小型網(wǎng)絡(luò)。D類 最前面為“1110” 多播地址舌缤。
地址的網(wǎng)絡(luò)號(hào)取值于224~239之間陵吸。
一般用于多路廣播用戶壮虫。E類 最前面為“1111” 保留地址剩拢。
地址的網(wǎng)絡(luò)號(hào)取值于240~255之間徐伐。3)幾個(gè)需要記住的地址
地址 說明 0.0.0.0 -代表所有'不清楚'的主機(jī)和目的網(wǎng)絡(luò)。
-這里的“不清楚”是指在本機(jī)的路由表里沒有特定條目指明如何到達(dá)性穿。
-對(duì)本機(jī)來說季二,它就是一個(gè)“收容所”,所有不認(rèn)識(shí)的“三無”人員绊含,一 律送進(jìn)去。255.255.255.255 受限廣播地址讨便。
對(duì)于本機(jī)來說伴找,這個(gè)地址指本網(wǎng)段內(nèi)的所有主機(jī)技矮。127.0.0.1 本機(jī)地址衰倦,主要用于測(cè)試。
與"localhost"相同驻襟。
192.168.0.1是本機(jī)ip地址。224.0.0.1 組播地址劲适,從224.0.0.0到239.255.255.255都是這樣的地址。
224.0.0.1 特指所有主機(jī)
224.0.0.2 特指所有路由器愕贡。168.254.x.x Windows操作系統(tǒng)在DHCP信息租用失敗時(shí)自動(dòng)給客戶機(jī)分配的IP地址固以。 10.x.x.x, 172.16.x.x
~172.31.x.x,192.168.x.x內(nèi)網(wǎng)私有地址憨琳。
2.2 關(guān)于Port
1)Port的基礎(chǔ)知識(shí)
- Port(端口)是設(shè)備與外界通訊交流的出口。
- 如果理解互聯(lián)網(wǎng)是一個(gè)社區(qū)遍略,每臺(tái)設(shè)備是一棟建筑绪杏,ip是樓號(hào)蕾久,port就是具體的門牌號(hào)腔彰。
- Port的范圍是0-65535,其中知名端口(已占用)是0-1023杯拐。
2)一些常用端口
端口 服務(wù) 21 FTP 22 SSH 23 Telnet 25 SMTP 53 DNS(UDP) 69 TFTP(簡(jiǎn)單文件傳輸協(xié)議) 79 Finger(列出用戶的一些信息) 80 HTTP 110 POP3 111 RPC遠(yuǎn)程過程調(diào)用 113 Windows驗(yàn)證服務(wù) 119 NNTP網(wǎng)絡(luò)新聞組傳輸協(xié)議 135 RPC遠(yuǎn)程過程調(diào)用 137 NetBIOS 139 Windows文件和打印機(jī)共享朗兵, Unix中的Samba服務(wù) 161 SNMP簡(jiǎn)單網(wǎng)絡(luò)管理協(xié)議 389 LDAP 443 HTTPS 445 SMB 1080 Socks代理服務(wù) 2601、2604 zebra路由盐欺,默認(rèn)密碼zebra 5900 VNC 8080 WWW代理服務(wù)
3.關(guān)于網(wǎng)絡(luò)協(xié)議
- 網(wǎng)絡(luò)協(xié)議是為計(jì)算機(jī)網(wǎng)絡(luò)中進(jìn)行數(shù)據(jù)交換建立的規(guī)則冗美、標(biāo)準(zhǔn)或約定的集合。
3.1 OSI七層模型
- 開放式系統(tǒng)互聯(lián)(Open System Interconnect)按照分工不同將互聯(lián)網(wǎng)協(xié)議從邏輯上劃分成七層叶摄。
- 是ISO(國(guó)際標(biāo)準(zhǔn)化組織)組織在1985年研究的網(wǎng)絡(luò)互聯(lián)模型挫剑。
- 同一層中的各網(wǎng)絡(luò)節(jié)點(diǎn)都有相同的層次結(jié)構(gòu),具有同樣的功能唆铐。
- 同一節(jié)點(diǎn)內(nèi)相鄰層之間通過接口(可以是邏輯接口)進(jìn)行通信艾岂。
- 七層結(jié)構(gòu)中的每一層使用下一層提供的服務(wù),并且向其上層提供服務(wù)梅猿。
- 不同節(jié)點(diǎn)的同等層按照協(xié)議實(shí)現(xiàn)對(duì)等層之間的通信钞啸。
層 | 說明 |
---|---|
物理層 Physical |
- 位于最低層梭稚,是傳送信號(hào)的物理實(shí)體弧烤。 - 通過機(jī)械和電氣的方式將各站點(diǎn)連接起來暇昂,組成物理通路,以便使數(shù)據(jù)流通過奴迅。 |
數(shù)據(jù)鏈路層 Data Link |
- 在物理層所提供的數(shù)據(jù)傳輸電路的基礎(chǔ)上价认,提供了一條無差錯(cuò)的數(shù)據(jù)鏈路用踩。 - 進(jìn)行二進(jìn)制數(shù)據(jù)流的傳輸碎乃,并進(jìn)行差錯(cuò)檢測(cè)和流量控制梅誓。 |
網(wǎng)絡(luò)層 Network |
- 處理報(bào)文分組梗掰,完成分組的多路復(fù)用和分組交換,以及通信子網(wǎng)絡(luò)間的數(shù)據(jù)據(jù)傳輸拥坛。 |
傳輸層 Transport |
- 實(shí)現(xiàn)端點(diǎn)到端點(diǎn)的可靠數(shù)據(jù)傳輸。 |
會(huì)話層 Session |
- 用于建立丸氛、控制和終止終端用戶的實(shí)用進(jìn)程間的邏輯信道的連接。 - 提供支持同步和管理應(yīng)用進(jìn)程間的對(duì)話服務(wù)禾锤,驗(yàn)證會(huì)話雙方的身份恩掷,會(huì)話連接的恢復(fù)和釋放黄娘。 |
表示層 Presentation |
- 為用戶應(yīng)用進(jìn)程提供了一系列統(tǒng)一的數(shù)據(jù)表示方式的服務(wù)。 - 解決不同系統(tǒng)不同終端所用的信息代碼和控制字符等的差異誓焦。 |
應(yīng)用層 Application |
- 直接為端點(diǎn)用戶提供服務(wù)杂伟。 |
3.2 TCP/IP四層模型
- 在實(shí)際使用中OSI模型過于龐大稿壁,由技術(shù)人員自己開發(fā)的TCP/IP協(xié)議棧獲得了更為廣泛的應(yīng)用。
- TCP/IP協(xié)議棧是美國(guó)國(guó)防部高級(jí)研究計(jì)劃局計(jì)算機(jī)網(wǎng)(ARPANET)和其后繼因特網(wǎng)使用的參考模型蕾羊。
TCP/IP四層模型 | 對(duì)應(yīng)OSI七層模型 | 協(xié)議 |
---|---|---|
數(shù)據(jù)鏈路層 | 物理層 數(shù)據(jù)鏈路層 |
IEEE 802.1A, IEEE 802.2到IEEE 802.11 FDDI, Ethernet, Arpanet, PDN, SLIP, PPP |
網(wǎng)絡(luò)層 | 網(wǎng)絡(luò)層 | IP, ICMP, ARP, RARP, AKP, UUCP |
傳輸層 | 傳輸層 | TCP, UDP |
應(yīng)用層 | 會(huì)話層 表示層 應(yīng)用層 |
Telnet, Rlogin, SNMP, Gopher SMTP, DNS HTTP、TFTP, FTP, NFS, WAIS利凑、SMTP |
3.3 關(guān)于TCP和UDP協(xié)議
1)關(guān)于TCP協(xié)議(Transmission Control Protocol)
- TCP是一種可靠的哀澈、面向連接的傳輸協(xié)議膨报。
- 傳輸效率低全雙工通信(即同一時(shí)刻雙向傳輸)现柠。
- 面向字節(jié)流够吩。
- 有點(diǎn)類似快遞或掛號(hào)信周循,大部分文件傳輸、瀏覽器迄本、郵件都使用TCP協(xié)議嘉赎。
2)關(guān)于UDP協(xié)議(User Datagram Protocol)- UDP協(xié)議是一種不可靠的公条、不面向鏈接的傳輸協(xié)議。
- 安全性差路捧,沒有發(fā)送順序队寇。
- 包大小不能超過64kb佳遣,但速度快零渐。
- 類似平郵信辜纲,即時(shí)通訊比如微信耕腾、qq會(huì)用到UDP協(xié)議扫俺。
二狼纬、SOCKET編程
- 在[筆記十:多進(jìn)程和多線程]中曾經(jīng)提到過socket套接字,作為多進(jìn)程間傳輸信息的一種方案盈简。
- 實(shí)際上Socket是一個(gè)網(wǎng)絡(luò)通信的端點(diǎn)柠贤,能實(shí)現(xiàn)不同主機(jī)的進(jìn)程通信臼勉,網(wǎng)絡(luò)大多基于Socket通信。
- Socket作為應(yīng)用層與TCP/IP協(xié)議族(包括TCP協(xié)議和UDP協(xié)議)通信的中間軟件抽象層膏蚓,是一組接口。
- 在設(shè)計(jì)模式中剧董,Socket把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面翅楼。
- 對(duì)用戶來說毅臊,只需要面對(duì)簡(jiǎn)單的接口皂林,讓Socket去組織數(shù)據(jù)础倍,以符合指定的協(xié)議沟启。
- Socket編程通常將發(fā)起訪問端和接收訪問端分為Client端(用戶端)和Server端(服務(wù)器端)。
1.Socket模塊
- Python提供了兩個(gè)基本socket模塊胳搞,分別是
Socket
和SocketServer
流酬。 - Socket模塊提供了在構(gòu)建 socket 服務(wù)器和客戶機(jī)時(shí)所需要的所有功能芽腾。
1.1 Socket模塊的常用類方法
1) socket.socket(family,type)
- 創(chuàng)建并返回socket對(duì)象。
- family表示套接字家族艰躺,有兩種類型:
家族名 說明 AF_UNIX 基于文件的 AF_INET 基于網(wǎng)絡(luò)的
- type表示套接字的種類:
種類 說明 SOCK_STREAM TCP套接字 SOCK_DGRAM UDP套接字 >>>import socket >>>sock = socket.socket() >>>print(type(sock)) <class 'socket.socket'>
2) socket.getfqdn(name)
- 返回完整的主機(jī)名腺兴。
>>>import socket >>>sock = socket.getfqdn('127.0.0.1') >>>print(sock) DESKTOP-xxxxx
3) socket.gethostbyname(hostname)
- 返回主機(jī)的IP地址篓足。
>>>import socket >>>sock = socket.gethostbyname('www.baidu.com') >>>print(sock) 220.181.38.150
4) socket.fromfd(fd, family, type)
- 從文件描述符創(chuàng)建socket對(duì)象栈拖。
>>>import socket >>>sock = socket.socket() >>>print(sock) <socket.socket fd=1340, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> >>>new_sock = socket.fromfd(sock.fileno(),sock.family,sock.type) >>>print(new_sock) <socket.socket fd=1172, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
1.2 Socket模塊的常用實(shí)例方法
1) socket.bind((addres, port))
- 將socket實(shí)例綁定到指定的ip和port上。
>>>import socket >>>sock = socket.socket() >>>sock.bind(('127.0.0.1',80)) >>>print(sock) <socket.socket fd=1344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 80)>
2) socket.accept()
- 用于Server端接受Client端的socket贴彼。
- 返回Client端的socket和地址锻弓。
3) socket.listen()
- 使得一個(gè)進(jìn)程可以接受其它進(jìn)程的請(qǐng)求,從而成為一個(gè)服務(wù)器進(jìn)程青灼。
4) socket.connect( (addres, port) )
- 將socket連接到定義的主機(jī)和端口上。
5) socket.recv( buflen[, flags] )
- 從socket中接收數(shù)據(jù)弹沽,最多buflen個(gè)字符策橘。
6) socket.recvfrom( buflen[, flags] )
- 從 socket 中接收數(shù)據(jù)丽已,最多 buflen 個(gè)字符,同時(shí)返回?cái)?shù)據(jù)來源的遠(yuǎn)程主機(jī)和端口號(hào)督赤。
7) socket.send( data[, flags] )
- 通過socket發(fā)送數(shù)據(jù)
8) socket.send( data[, flags] ,addr)
- 通過 socket 發(fā)送數(shù)據(jù)到指定地址丑婿。
9) socket.close()
- 關(guān)閉socket毅贮。
10) socket.getsockopt( lvl, optname )
- 獲得指定socket的值。
11) socket.setsockopt( lvl, optname ,val)
- 設(shè)置指定 socket 選項(xiàng)的值炫加。
>>>#Client端 >>>import socket >>>sock = socket.socket() >>>sock.connect(("127.0.0.1",10005)) # 連接服務(wù)器 >>>sock.send(b"from client...") # 發(fā)送 >>>ret = sock.recv(1024) # 接收 >>>print(ret) >>>sock.close() # 關(guān)閉套接字 b'from server...' >>>#Server端 >>>import socket >>>sock = socket.socket() >>>sock.bind(("127.0.0.1",10005)) >>>sock.listen() # 監(jiān)聽sock >>>conn,addr = sock.accept() # 接受客戶端socket >>>ret = conn.recv(1024) # 接受客戶端信息 >>>print(ret) >>>conn.send(b'from server...') # 向客戶端發(fā)送信息 >>>print('Client Socket : ',conn) >>>print('Client Address:',addr) >>>conn.close() # 關(guān)閉客戶端socket >>>sock.close() # 關(guān)閉服務(wù)器socket b'from client...' Client Socket : <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 10005), raddr=('127.0.0.1', 58698)> Client Address: ('127.0.0.1', 58698)
2.基于UDP協(xié)議的Socket編程
1) Server端需要完成的任務(wù)
- 建立Socket實(shí)例。
- 為Socket實(shí)例綁定ip和port赋铝。
- 接收Client端的訪問革骨。
- 反饋信息(如果需要)良哲。
#Server端 >>>import socket >>># 服務(wù)器函數(shù) >>>def server_sample(): >>> # 1.建立socket對(duì)象 >>> sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) >>> addr = ("127.0.0.1",10005) >>> # 2.綁定地址 >>> sock.bind(addr) >>> # 3.接收消息 >>> data,addr = sock.recvfrom(1024) >>> text = data.decode() # 數(shù)據(jù)流解碼 >>> print(text) >>> # 4.返回消息 >>> rsp = b"from serer..." >>> sock.sendto(rsp,addr) >>> sock.close() >>>if __name__ == '__main__': >>> print("啟動(dòng)服務(wù)器...") >>> server_sample() >>> print("關(guān)閉服務(wù)器...") 啟動(dòng)服務(wù)器... from client... 關(guān)閉服務(wù)器...
2) Client端需要完成的任務(wù)
- 建立Socket實(shí)例。
- 發(fā)送內(nèi)容到Server端巍实。
- 接收Server端的反饋(如果有)棚潦。
#Client端 >>>import socket >>>def client_sample(): >>> # 建立socket實(shí)例 >>> sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) >>> # 發(fā)送內(nèi)容到Server端 >>> data = b'from client...' >>> sock.sendto(data,("127.0.0.1",10005)) >>> # 接收Server端反饋 >>> reply,addr = sock.recvfrom(1024) >>> text = reply.decode() >>> print(text) >>> sock.close() >>>if __name__ == '__main__': >>> client_sample() from serer...
3.基于TCP協(xié)議的Socket編程
- 與UDP不同瓦盛,TCP協(xié)議在每次傳輸之前先要建立一個(gè)雙向管道。
1) Server端需要完成的任務(wù)
- 建立Socket實(shí)例,此實(shí)例僅用于接收請(qǐng)求嘱吗。
- 為Socket實(shí)例綁定ip和port谒麦。
- 監(jiān)聽接入的Socket绕德。
- 接受Client端訪問的Socket,建立通訊鏈接通路耻蛇。
- 使用Client端Socket接收內(nèi)容
- 反饋信息(如果需要)臣咖。
- 關(guān)閉鏈接通路疚漆。
>>># Server端 >>>import socket >>>def server_sample(): >>> # 1. 建立socket實(shí)例 >>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) >>> # 2. 綁定地址 >>> addr = ("127.0.0.1",10005) >>> sock.bind(addr) >>> # 3. 開啟監(jiān)聽模式 >>> sock.listen(5) >>> while True: >>> # 4.接受訪問娶聘,建立通訊鏈路 >>> connection,address = sock.accept() >>> # 5.接受內(nèi)容,內(nèi)容解碼 >>> data = connection.recv(1024) >>> data = data.decode() >>> reply = ("Received {} from {}".format(data,address)) >>> # 6 發(fā)送反饋 >>> connection.send(reply.encode()) >>> print(reply) >>> # 7.關(guān)閉鏈路 >>> connection.close() >>>if __name__ == '__main__': >>> print("Start Server...") >>> server_sample() >>> print("End Server...") Received from client... from ('127.0.0.1', 62157)
2) Client端需要完成的任務(wù)
- 建立Socket實(shí)例趴荸。
- 與Server端建立鏈接通路发钝。
- 發(fā)送內(nèi)容到Server端酝豪。
- 接收Server端反饋(如果有)
- 關(guān)閉鏈接通路
>>>import socket >>>def client(): >>> # 1.建立socketobj >>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) >>> #2.建立鏈接 >>> addr = ("127.0.0.1",10005) >>> sock.connect(addr) >>> #3.發(fā)送內(nèi)容 >>> msg = b"from client..." >>> sock.send(msg) >>> #4.接受反饋 >>> rst = sock.recv(1024) >>> print(rst.decode()) >>> #5.關(guān)閉鏈路 >>> sock.close() >>>if __name__ == '__main__': >>> client() Received from client... from ('127.0.0.1', 62157)
4. 處理多個(gè)Client(用戶端)
- Server端在處理多個(gè)客戶端的請(qǐng)求時(shí),需要解決信息阻塞的問題瘫证。
4.1 多進(jìn)程解決方案
- 用派生服務(wù)器的方式編寫Server端背捌,避免Client端堵塞毡庆。
- 使用多進(jìn)程fork()方式派生服務(wù)器毅否。
- Server端:
# Linux服務(wù)器
# Server端
>>>import os,time,sys,socket
>>>host = '172.21.0.4'
>>>post = 10005
>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,post))
>>>sock.listen(5)
>>>activeChildren = [] # 子進(jìn)程容器
>>>def replace_children():
>>> # 移除子進(jìn)程
>>> while activeChildren:
>>> pid,stat = os.waitpid(0,os.WNOHANG)
>>> if not pid:break
>>> activeChildren.remove(pid)
>>>def handle_client(conn):
>>> # 處理子進(jìn)程
>>> time.sleep(5)
>>> while True:
>>> data = conn.recv(1024)
>>> if not data:break
>>> reply = 'Receive {} at {}'.format(data,time.ctime(time.time()))
>>> conn.send(reply.encode())
>>> conn.close()
>>> os._exit(0) # 如果不退出螟加,每個(gè)子進(jìn)程會(huì)在返回后繼續(xù)運(yùn)行
>>>def main():
>>> # 監(jiān)聽接入
>>> while True:
>>> print("awaiting connection")
>>> connection,address = sock.accept()
>>> print("Server connected by {} at {}".format(address,time.ctime(time.time())))
>>> replace_children()
>>> childPid = os.fork()
>>> if childPid == 0:
>>> handle_client(connection)
>>> else:
>>> activeChildren.append(childPid)
>>>if __name__ == '__main__':
>>> print("starting server..")
>>> main()
>>> print("stoping server...")
Server connected by ('117.119.102.10', 58962) at Sun Apr 12 18:26:30 2020
awaiting connection
Server connected by ('117.119.102.10', 58968) at Sun Apr 12 18:26:32 2020
awaiting connection
Server connected by ('117.119.102.10', 58970) at Sun Apr 12 18:26:34 2020
awaiting connection
- Client端:
# 本地PC端
>>># Client端
>>>import socket,threading,time
>>>def client():
>>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> addr = ("123.206.41.120",10005)
>>> sock.connect(addr)
>>> msg = b"from client..."
>>> sock.send(msg)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> for i in range(3): # 用線程模擬多個(gè)Client端
>>> t = threading.Thread(target=client,args=())
>>> t.start()
>>> time.sleep(2)
Receive b'from client...' at Sun Apr 12 18:26:35 2020
Receive b'from client...' at Sun Apr 12 18:26:37 2020
Receive b'from client...' at Sun Apr 12 18:26:39 2020
4.2 多線程解決方案
- 相比進(jìn)程,線程的消耗更小(尤其是在Windows下),可以跨平臺(tái)部署卒蘸,且不用擔(dān)心線程的回收問題。
- Server端:
>>>import time,threading,socket
>>>host='127.0.0.1'
>>>port=10005
>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,port))
>>>sock.listen(5)
>>>now = lambda: time.ctime(time.time())
>>>def handle_client(connection):
>>> time.sleep(5)
>>> while True:
>>> data = connection.recv(1024)
>>> if not data:break
>>> reply = "got {0} at {1}".format(data,now())
>>> connection.send(reply.encode())
>>> connection.close()
>>>def main():
>>> while True:
>>> connection,address = sock.accept()
>>> print('Server connected by {0} at {1}...'.format(address,now()))
>>> t = threading.Thread(target=handle_client,args=(connection,))
>>> t.start()
>>>if __name__ == '__main__':
>>> main()
Server connected by ('127.0.0.1', 54071) at Mon Apr 13 09:34:59 2020...
Server connected by ('127.0.0.1', 54073) at Mon Apr 13 09:35:01 2020...
Server connected by ('127.0.0.1', 54077) at Mon Apr 13 09:35:03 2020...
- Client端:
# Client端
>>>import socket,threading,time
>>>def client():
>>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> addr = ("127.0.0.1",10005)
>>> sock.connect(addr)
>>> msg = b"from client..."
>>> sock.send(msg)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> for i in range(3): # 用線程模擬多個(gè)Client端
>>> t = threading.Thread(target=client,args=())
>>> t.start()
>>> time.sleep(2)
got b'from client...' at Mon Apr 13 09:35:04 2020
got b'from client...' at Mon Apr 13 09:35:06 2020
got b'from client...' at Mon Apr 13 09:35:08 2020
4.3 socketserver包解決方案
- 標(biāo)準(zhǔn)庫中的socketserver包提供了更簡(jiǎn)單的多線程服務(wù)器或多進(jìn)程服務(wù)器方法:
ThreadingTCPServer(address,server)
和ForkingTCPServer(address,server)
- UDP服務(wù)器同理趾牧。
- Server端:
>>>import socketserver,time
>>>host='127.0.0.1'
>>>port=10005
>>>now = lambda: time.ctime(time.time())
>>>class MyClientHandler(socketserver.BaseRequestHandler):
>>> def handle(self):
>>> # 處理每個(gè)接入的請(qǐng)求
>>> print('Server connected by {0} at {1}...'.format(self.client_address, now()))
>>> time.sleep(5)
>>> while True:
>>> data = self.request.recv(1024)
>>> if not data:break
>>> reply = "got {0} at {1}".format(data, now())
>>> self.request.send(reply.encode())
>>> self.request.close()
>>>def main():
>>> server = socketserver.ThreadingTCPServer((host,port),MyClientHandler)
>>> server.serve_forever()
>>>if __name__ == '__main__':
>>> print('Starting server...')
>>> main()
>>> print('Stoping Server...')
Starting server...
Server connected by ('127.0.0.1', 60706) at Mon Apr 13 11:22:27 2020...
Server connected by ('127.0.0.1', 60708) at Mon Apr 13 11:22:29 2020...
Server connected by ('127.0.0.1', 60710) at Mon Apr 13 11:22:31 2020...
- Client端:
>>># Client端
>>>import socket,threading,time
>>>def client():
>>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> addr = ("127.0.0.1",10005)
>>> sock.connect(addr)
>>> msg = b"from client..."
>>> sock.send(msg)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> for i in range(3): # 用線程模擬多個(gè)Client端
>>> t = threading.Thread(target=client,args=())
>>> t.start()
>>> time.sleep(2)
got b'from client...' at Mon Apr 13 11:22:32 2020
got b'from client...' at Mon Apr 13 11:22:34 2020
got b'from client...' at Mon Apr 13 11:22:36 2020
4.4 協(xié)程解決方案
- 用asyncio包創(chuàng)建協(xié)程版TCP服務(wù)器。
- Server端:
>>>import time,asyncio
>>>host='127.0.0.1'
>>>port=10005
>>>now = lambda: time.ctime(time.time())
>>>async def handle_client(reader,writer):
>>> data = await reader.read(1024)
>>> message = data.decode()
>>> address = writer.get_extra_info('peername')
>>> print('Server connected by {0} at {1}...'.format(address, now()))
>>> reply = "got {0} at {1}".format(message,now())
>>> writer.write(reply.encode())
>>> await writer.drain()
>>> print('Closing Client:{}'.format(address))
>>> writer.close()
>>>def main():
>>> loop = asyncio.get_event_loop() # 創(chuàng)建loop
>>> task = asyncio.start_server(handle_client,host,port,loop=loop) # 將事件拋到loop中
>>> server = loop.run_until_complete(task)
>>> print('serving on {}'.format(server.sockets[0].getsockname()))
>>> try:
>>> loop.run_forever()
>>> except KeyboardInterrupt: # 用Ctrl+c打斷服務(wù)器
>>> pass
>>> server.close()
>>> loop.run_until_complete(server.wait_closed())
>>> loop.close()
>>>if __name__ == '__main__':
>>> print('Starting server...')
>>> main()
>>> print('Stoping Server...')
Starting server...
serving on ('127.0.0.1', 10005)
Server connected by ('127.0.0.1', 59093) at Mon Apr 13 10:46:30 2020...
Closing Client:('127.0.0.1', 59093)
Server connected by ('127.0.0.1', 59095) at Mon Apr 13 10:46:32 2020...
Closing Client:('127.0.0.1', 59095)
Server connected by ('127.0.0.1', 59096) at Mon Apr 13 10:46:34 2020...
Closing Client:('127.0.0.1', 59096)
- Client端:
# Client端
>>>import socket,threading,time
>>>def client():
>>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> addr = ("127.0.0.1",10005)
>>> sock.connect(addr)
>>> msg = b"from client..."
>>> sock.send(msg)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> for i in range(3): # 用線程模擬多個(gè)Client端
>>> t = threading.Thread(target=client,args=())
>>> t.start()
>>> time.sleep(2)
got from client... at Mon Apr 13 10:46:30 2020
got from client... at Mon Apr 13 10:46:32 2020
got from client... at Mon Apr 13 10:46:34 2020
4.5 select包 I/O多路復(fù)用解決方案
- 用輪詢的方式在單線程中實(shí)現(xiàn)多重通道的傳輸柬唯。
-
select.select(rlist, wlist, xlist,timeout)
函數(shù)包含三個(gè)返回值:readable, writable, exceptional
參數(shù) | 含義 |
---|---|
rlist | 可讀集合 |
wlist | 可寫集合 |
xlist | 異常集合 |
timeout | 等待期限 0:立即返回 省略:等待至少一個(gè)對(duì)象準(zhǔn)備好锄奢。 |
- Server端
>>>import time,socket
>>>from select import select
>>>def main():
>>> host = '127.0.0.1'
>>> port = 10005
>>> now = lambda: time.ctime(time.time())
>>> num_socks = 3 # 允許的接入數(shù)量
>>> mainsocks,readsocks,writesocks = [],[],[]
>>> for i in range(num_socks):
>>> # 建立一個(gè)連接
>>> portsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> portsock.bind((host,port))
>>> portsock.listen(5)
>>> # 將連接放到列表中
>>> mainsocks.append(portsock)
>>> readsocks.append(portsock)
>>> port += 1
>>> print('loop starting...')
>>> while True:
>>> readables,writeables,exceptions = select(readsocks,writesocks,[])
>>> for sock in readables:
>>> if sock in mainsocks: # 如果sock已經(jīng)準(zhǔn)備好
>>> # 如果是新的接入
>>> newsock,address = sock.accept()
>>> print('Server connected by {0} at {1}...'.format(address, now()))
>>> readsocks.append(newsock) # 加入到已讀列表
>>> else:
>>> # 如果是池子中的連接
>>> data = sock.recv(1024)
>>> if not data:
>>> sock.close()
>>> readsocks.remove(sock)
>>> else:
>>> # 可能會(huì)阻塞的部分
>>> reply = "got {0} at {1}".format(data, now())
>>> sock.send(reply.encode())
>>>if __name__ == '__main__':
>>> main()
loop starting...
Server connected by ('127.0.0.1', 56875) at Mon Apr 13 14:49:33 2020...
Server connected by ('127.0.0.1', 56877) at Mon Apr 13 14:49:35 2020...
Server connected by ('127.0.0.1', 56879) at Mon Apr 13 14:49:37 2020...
- Client端:
>>># Client端
>>>import socket,threading,time
>>>def client():
>>> sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>> addr = ("127.0.0.1",10005)
>>> sock.connect(addr)
>>> msg = b"from client..."
>>> sock.send(msg)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> for i in range(3): # 用線程模擬多個(gè)Client端
>>> t = threading.Thread(target=client,args=())
>>> t.start()
>>> time.sleep(2)
got b'from client...' at Mon Apr 13 14:49:33 2020
got b'from client...' at Mon Apr 13 14:49:35 2020
got b'from client...' at Mon Apr 13 14:49:37 2020
5. Socket流的重定向
- 使用socket.makefile(),將Socket流與文件相關(guān)聯(lián)胯陋。
socket.makefile(mode ='r'遏乔,buffering = None盟萨,*捻激,encoding = None胞谭,errors = None丈屹,newline = None )
-
mode
參數(shù)支持'r'
或'w'
彩库,分別對(duì)應(yīng)讀寫骇钦。 - Server端:
>>>import time,sys,socket
>>>host='127.0.0.1'
>>>port=10005
>>>sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
>>>sock.bind((host,port))
>>>sock.listen(1)
>>>now = lambda: time.ctime(time.time())
>>>def handle_client(connection):
>>> time.sleep(5)
>>> while True:
>>> file = connection.makefile('r') # 將輸入流關(guān)聯(lián)到file
>>> data = file.readline().rstrip() # 讀取第一行
>>> if not data:break
>>> reply = "got {0} at {1}".format(data,now())
>>> connection.send(reply.encode())
>>> connection.close()
>>>def main():
>>> while True:
>>> connection,address = sock.accept()
>>> print('Server connected by {0} at {1}...'.format(address, now()))
>>> handle_client(connection)
>>>if __name__ == '__main__':
>>> print('starting server...')
>>> main()
>>> print('stoping server...')
starting server...
Server connected by ('127.0.0.1', 56127) at Mon Apr 13 16:19:18 2020...
- Client端:
>>># Client端
>>>import socket,sys,time
>>>host = "127.0.0.1"
>>>port = 10005
>>>sysout = sys.stdout # 備份輸出流
>>>def client():
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.connect((host, port))
>>> # 將輸出流導(dǎo)入file
>>> file = sock.makefile('w')
>>> sys.stdout = file
>>> msg = b"from client..."
>>> print(msg)
>>> sys.stdout.flush() # 閃現(xiàn)緩存
>>> sys.stdout = sysout # 將輸出流恢復(fù)默認(rèn)
>>> rst = sock.recv(1024)
>>> print(rst.decode())
>>> sock.close()
>>>if __name__ == '__main__':
>>> client()
got b'from client...' at Mon Apr 13 16:19:23 2020
參考資料
- https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
- https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
- https://www.runoob.com 菜鳥教程
- http://www.tulingxueyuan.com/ 北京圖靈學(xué)院
- http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 兩點(diǎn)水
- https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鳥
- https://realpython.com/python-string-formatting/ Dan Bader
- https://www.liaoxuefeng.com/ 廖雪峰
- https://blog.csdn.net/Gnewocean/article/details/85319590 新海說
- https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
- https://www.cnblogs.com/dalaoban/p/9331113.html 超天大圣
- 《Python學(xué)習(xí)手冊(cè)》Mark Lutz
- 《Python編程 從入門到實(shí)踐》Eric Matthes
本文作者:大師兄(superkmi)