大師兄的Python學(xué)習(xí)筆記(十五): Socket編程

大師兄的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模塊胳搞,分別是SocketSocketServer流酬。
  • 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

參考資料



本文作者:大師兄(superkmi)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坦仍,一起剝皮案震驚了整個(gè)濱河市叨襟,隨后出現(xiàn)的幾起案子糊闽,更是在濱河造成了極大的恐慌提澎,老刑警劉巖念链,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異看成,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)梦重,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門琴拧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艾蓝,“玉大人,你說我怎么就攤上這事馍盟≌炅耄” “怎么了瞄桨?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)柱查。 經(jīng)常有香客問我唉工,道長(zhǎng),這世上最難降的妖魔是什么汹忠? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任淋硝,我火速辦了婚禮雹熬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谣膳。我一直安慰自己竿报,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布仰楚。 她就那樣靜靜地躺著,像睡著了一般捂襟。 火紅的嫁衣襯著肌膚如雪纽帖。 梳的紋絲不亂的頭發(fā)上扒吁,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天融撞,我揣著相機(jī)與錄音,去河邊找鬼趁窃。 笑死裆针,一個(gè)胖子當(dāng)著我的面吹牛澡刹,可吹牛的內(nèi)容都是我干的陆赋。 我是一名探鬼主播灾锯,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了赦肋?” 一聲冷哼從身側(cè)響起麦锯,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤千扶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后顺呕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年棍厂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝴悉,到底是詐尸還是另有隱情簇抵,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布雏逾,位于F島的核電站仇让,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏踊淳。R本人自食惡果不足惜剪芥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疑枯,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撕彤。三九已至睦裳,卻和暖如春蛛蒙,著一層夾襖步出監(jiān)牢的瞬間诺苹,已是汗流浹背滓玖。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工兑宇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留比规,地道東北人灾常。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓缠俺,卻偏偏與公主長(zhǎng)得像躏救,于是被迫代替她去往敵國(guó)和親少办。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鞋拟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼宏悦,假如你寫了兩個(gè)python文件a.py和b.py派哲,分別去運(yùn)...
    go以恒閱讀 2,021評(píng)論 0 6
  • Python之網(wǎng)絡(luò)編程(socket模塊) 什么是socket持隧?Socket是應(yīng)用層與TCP / IP協(xié)議族通信的...
    免跪姓黃閱讀 257評(píng)論 0 2
  • 最近在學(xué)習(xí)Python看了一篇文章寫得不錯(cuò),是在腳本之家里的,原文如下,很有幫助: 一、網(wǎng)絡(luò)知識(shí)的一些介紹 soc...
    qtruip閱讀 2,717評(píng)論 0 6
  • 大綱 一.Socket簡(jiǎn)介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,344評(píng)論 0 5
  • 說明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,122評(píng)論 0 16