python高级编程之网络编程
Python高級之網絡編程
- 端口
- 端口分類
- 知名端口
- 動態端口
- 查看端口
- socket簡介
- 電腦上進程之間的通信
- 什么是socket
- 創建socket
- 使用UDP套接字發送數據
- 使用UDP套接字接受數據
- TCP協議socket應用
- 特性
- socket客戶端
- socket服務器端
- TCP注意點
- 正則表達式
- 簡介
- 匹配單個字符
- 匹配多個字符
- 匹配開頭和結尾
- 分組
- python中正則使用
- HTTP協議
- HTTP簡介
- 瀏覽器向服務器發送請求數據包的數據頭包括:
- 服務器相應給瀏覽器的應答
- http協議驗證
- TCP的3次捂手和4次揮手
- TCP的3次捂手
- TCP的4次揮手
- 服務器的長連接和短連接
- 案例:返回瀏覽器需要的頁面-HTTP服務器
- 案例:返回瀏覽器需要的頁面-HTTP服務器——使用多進程實現
- 案例:返回瀏覽器需要的頁面-HTTP服務器——使用多線程實現
- 案例:返回瀏覽器需要的頁面-HTTP服務器——gevent實現
- 使用單進程、單線程、非解堵塞實現
- 實現長連接
- epoll技術
- 使用epoll
- epoll技術實現
端口
端口就好似是一座房子,端口號就貌似該房子的門牌號,范圍0~65535
用于在網絡上標記某一個程序的位置,當從計算機在網絡上接受到一個數據通過數據中包含的端口號信息判斷發送到某一個程序。
端口分類
知名端口
知名端口就是經常使用的端口,該端口都是規定好的一些服務,好比110治安報警,119火災報警等等,該類端口范圍從0~1023,
例如:
88端口分配給HTTP服務
21端口分配給ftp服務
動態端口
動態端口是不固定的,他一般不固定分配給哪一個服務,而是動態分配,
動態分配是指當某一個計算機程序需要網絡通信時,它向主機申請端口,主機在可用的端口中動態的分配一個端口給它,當網絡通信結束時該端口也被主機收回,
查看端口
netstat -an :查看端口狀態
lsof -i [tcp/udp]:端口號
socket簡介
電腦上進程之間的通信
進程:運行的程序以及運行時用到的資源這個整體稱為進程
進程間到通信:運行的程序之間的數據共享
要想了解進程間到通信首先要了如何標示一個進程,
在計算機中通過進程號(PID)可以標示一個進程
在網絡中則需要通過利用ip地址、協議、端口來標示進程。
什么是socket
socket(簡稱 套接字)是進程間通信的一種方式,它與其他進程間通信到一個主要不同是:它能實現不同主機間到進程通信,我們網絡上各種各樣的服務大多數基于Socket來完成通信的,例如我們每天瀏覽到網頁、微信、qq等
創建socket
在Python中使用socket模塊中的socket函數創建
import socket #導入socket模塊 socket.socket(AddressFamily,Type) #創建一個socket說明:
函數socket創建一個socket,該函數包含兩個參數:
- Address Family:可以選擇AF_INET(用于internet進程間通信)或者AF_UNIX(用于同一臺機器進程間通信),一般使用AF_INET參數
- Type:socket類型,SOCK_STREAM(用于tcp協議),SOCK_DGRAM(用于UDP協議)
創建一個tcp套接字
創建一個UDP套接字
# 導入模塊 import socket #創建一個UDP套接字 FTP_Socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM);#這里使用套接字功能 #......#關閉套接字 FTP_Socket.close();創建套接字流程
使用UDP套接字發送數據
#導入模塊 from socket import *;#創建UDP套接子 UDP_Socket = socket(AF_INET,SOCK_DGRAM); #sendto(data,tuple)函數 #該函數中有兩個參數,第一個參數是一個byte類型的數據,表示需要發送的數據, # 第二個參數是一個元組,該元組中有兩個數據第一個是字符串 # 類型表示目的ip,第二個是目的端口data = input("請輸入要發送的數據"); UDP_Socket.sendto(data.encode("utf-8"),("192.168.42.156",8088)); #因為發送的數據需要是byte類型,該數據為字符串類型所以需要對該數據進行解碼 #utf-8國際通用的編碼規范#關閉套接字 UDP_Socket.close();使用UDP套接字接受數據
#導入模塊 from socket import *; #創建套接字 UDP_Socket = socket(AF_INET,SOCK_DGRAM); #接受數據時需要綁定本地信息,Ip和端口號#設置一個元組用于保存ip、端口號local_addr = ('',8088); #''表示本機任意一個ip #bind(tuple)函數用于綁定本地信息 UDP_Socket.bind(local_addr);#接受信息 #recvfrom()函數用于接受數據 data = UDP_Socket.recvfrom(1024); #1024表示本次接受的最大字節數#因為接受到的數據是一個:(b"",()) 類型 #第一個數據是接受到的數據是一個byte類型,第二個元組包含的是該數據來自的主#機的信息#顯示數據 #所以輸出信息時就需要對該數據進行解碼,如果信息來自windows系統發送過來的 #則需要設置解碼規則為gbk,因為windows的編碼為gbk #解碼函數:decode();print(data[0].decode('gbk'));#關閉套接字 UDP_Socket.close();TCP協議socket應用
特性
tcp簡稱傳輸控制協議,它是一個比較安全到傳輸協議。不會出現掉包的情況
傳輸過程:客戶端向服務器發送一個請求消息,服務器接收到會給客戶端回應一個包告訴客戶端他已經受到請求,如果客戶端沒有受到回應,客戶端會再次詢問服務端,服務端會再次向客戶端回應一個包
socket客戶端
客戶端就是用戶使用的一些服務,例如本地軟件qq就屬于qq客戶端,向騰訊的qq服務器端訪問
客戶端創建:
1、創建套接字
2、設置服務端ip/port
server_ip = input("請輸入服務器的ip:") server_port = int(input("請輸入服務器的port:")) server_addr = (server_ip,server_port);3、連接服務器端
# 要想向服務器發送請求,首先要在客戶機和服務器之間建立一個通道 # 這就需要客戶機使用connect()函數連接服務器 tcp_client_socket.connect(server_addr);4、向服務器端發送請求
# 向服務器發送請求,使用send()函數,因為該客戶機已經和服務器建立通道所以不膩使用sendto()指定地址 send_data = input("請輸入你想要發送的數據:") tcp_client_socket.send(send_data.encode("utf-8")); # 編碼:win使用gbk;linux使用utf-85、接受服務器端的響應
server_data = tcp_client_socket.recvfrom(1024); print(server_data)6、關閉套接字
tcp_client_socket.close();socket服務器端
是為客戶端提供服務的
創建流程:
這個創建流程呢,首先使用一個例子來講解一下:
這個就好比我們想讓我們朋友在其他地方聯系你,朋友怎么聯系呢?通過電話嘛,對不對,
那么我們應該首先買一臺電話機,那么有電話機了還不行,還需要電話號,所以要裝一個電話卡
那么然后我們就把那個電話搞成只能接不能打,因為我們想讓朋友打來電話嘛,然后就在那等朋友打電話打來?
1、創建套接字(買一臺電話機)
2、綁定ip/port(安裝電話卡)
因為服務器端的ip和port是不允許變的,就好像我們玩游戲沒玩一次,都需要向游戲公司打電話問它ip和端口號是多少吧,這是不行的
3、設置為監聽模式(只接不打)
# 設置為監聽模式使用listen(128) 一般默認設置為128 tcp_server_socket.listen(128)4、等待客戶端連接(等待朋友打電話來)
# 使用accept()函數,該函數等待客戶機發送請求過來,當客戶機發信息過來,該函數會有進行堵塞,并返回兩個值 # 第一個值為一個套接字,第二個是一個元祖數據,包含了客戶機的ip和端口號 """ 我給你分析一下這里面的過程: 舉個例子:就好比我們給10086打電話,我們要查詢一些服務是不是要進行人工服務,當人工服務接通俺哥瞬間我們已經脫 離了10086這個大的會話,進入了一個另一個會話, 這個也是這樣,accept()函數在等待客戶機,當就到客戶機這一瞬間,該函數就在服務器上另開了一個端口就是生成了一 個對該客戶機進行一對一服務的人員,然后就將客戶機和總服務套接字進行了斷開并成功鏈接這一個新的套接字,那個總 套接字就去等待其他客戶機的到來,所以這第一個參數就是對該客戶機進行服務的新服務端這樣的好處就是可以增加工作效率,如果總服務端一直被這一個客戶機占著,其他用戶就會無法鏈接到服務器 """ # 我們一般使用解包的方式接受該數據new_socket,client_addr = tcp_server_socket.accept()6、接受請求并給出響應
# 因為客戶機已經和新端口建立通道,故使用新套接字進行響應操作 recv_data = new_socket.recv(1024) # 因為已經建立連接,故不需要對方的信息了,所以使用recv接受 # recv接受的數據只包含請求信息 print(recv_data) # 給出相應 new_socket.send("------ok-------");7、關閉套接字
new_socket.close(); tcp_server_socket.close();TCP注意點
1、一般TCP服務器都需要綁定端口,否則客戶端可能就會找不到該端口
2、TCP客戶端一般不需要綁定端口,因為是客戶端主動連接服務器,所以只要確認好服務器ip、port等信息,本地客戶端隨機就可以。
3、tcp服務器中通過listen可以將socket創建出來的套接字變為被動的,這是tcp服務器必須做的。
4、當服務器需要連接服務器時,就需要使用connect進行連接,udp不需要連接就可以直接通信,但tcp必須建立連接,只有建立連接成功才可以通信。
5、當tcp客戶端需要連接到服務器時,服務器端會生成一個新的套接字,這個套接字用來標記該客戶端
6、listen后的套接字是被動套接字,所以該套接字只能用來進行接收客戶端發送的請求,而accept返回的套接字使用來標記該用戶的,
7、關閉listen后的套接字意味著被動套接字關閉了,會導致客戶端不能夠成功連接到服務器,但已經建立的連接可以正常通信。
8、關閉accept返回的套接字,意味著這個客戶端的服務已經完成
9、當客戶端的套接字調用close關閉后,服務器端recv會解堵塞,并且返回的長度為0,因此服務端可以通過返回數據的長度來區別客戶端是否下線。
正則表達式
簡介
正則表達式通俗的說就是一種匹配規則,用于判定字符串是否符合某一種規則。當字符串的格式符合這個規則該字符串就匹配,否則就不匹配。
正則表達式由一系列字符組成,這每一個字符都用于表示某一類數據。
匹配單個字符
- \d:匹配數字,即0-9只匹配一個
例:
正 則:\d
字符串:“1”
結 果:匹配
正 則:\d
字符串:“12”
結 果:不匹配
因為\d表示只匹配一個數字而“12”是由1 2 組成 - \D:表示不匹配數字,即\d可以匹配的,它都不匹配
- \w:匹配a-z,A-Z,0-9任意一個字符
- \W:匹配非\w匹配的字符
- \s:匹配空格、tab鍵
- \S:匹配非空白
- .:匹配任意一個字符(除\n(換行))
- []:匹配[]中列舉的字符
例:
正則:[abc123]
字符:“a”
結果:匹配
正則:[abc123]
字符:“2”
結果:匹配
正則:[abc123]
字符:“5”
結果:不匹配
匹配多個字符
-
:表示該字符前面的那個字符可以在字符串中匹配0或多次
例:
正則:a
字符:“aaa”
結果:匹配
正則:a*
字符:“a”
結果:匹配
正則:ba*
字符:“baaa”
結果:匹配
正則:ba*
字符:“b”
結果:匹配 -
+:匹配一次或多次
和的用法一樣但+前面的字符必須出現一次
正則:ba
字符:“b”
結果:不匹配 -
?:匹配前一個字符可以出現一次或零次
-
{m}:表示匹配前面的字符必須出現m次
-
{n,m}:表示前面的字符最少出現n次最多出現m次
匹配開頭和結尾
- ^ :表示匹配字符串必須以該符號后面的那個字符開頭
- $:表示匹配的字符串必須匹配到結尾,完全和匹配規則相同
分組
- |:功能類似于“或”,將正則表達式以|為中分成兩個,滿足任意一個都行
- ( | ):表示字符串滿足正則表達式中和|右邊語句結合的語句也可以匹配,滿足左邊的也行
例:
正則:ningning@(163|qq).com
字符:“ningnign@163.com”
結果:匹配
正則:ningning@(163|qq).com
字符:“ningnign@qq.com”
結果:匹配 - ():用于將正則進行分組
用法1:可以使用函數指定下標,將指定括號中的數據取出來
例:
正則:aaa(bb)dddd(fff)
字符串:aaabbddddfff;
結果:可以匹配
正則對象.group(1)
結果:bb
正則對象.group(2)
結果:fff
用法2: 可以使用“\下標值”將該括號中的數據完全一樣的作為匹配規則使用。表示前面的字符匹配成什么,后面調用的時候也必須是什么。
正則:<(\w*)>.</\1>
字符:<h1>fjafv<h1>
結果:匹配
正則:<(\w)>.*</\1>
字符:"<h1>fjafv<h2>"
結果:不匹配 - (?P):分組起別名,用法和分組第二種一樣,就是調用時使用(?P=name)使用,name:需要設置的名字
python中正則使用
在python中要想使用正則表達式,首先要先導入re包
-
match(正則表達式,字符串)
用于判斷在字符串是否是否在匹配規則,符合返回一個正則對象,可以使用group()方法將匹配的字符串取出來。 -
search(正則,字符串)
-
在字符串中查找符合該正則規則的字串并返回一個正則對象,使用group()可以將匹配到的數據取出來
-
findall(正則,字符串)
將字符串中的所有符合該匹配規則的字串全部取出來并保存到一個列表中 -
sub(正則,參數,字符串)
參數:可以是一個字符,也可以是一個返回值是字符的函數
將在字符串中匹配到的所有字串都替換為該參數,并返回一個全部替換過的字符串, -
split(正則,字符串)
在字符串中按照符合匹配規則的字符進行切割,返回值是一個列表
HTTP協議
HTTP簡介
在web應用中服務器將網頁傳遞給瀏覽器,實則是將一個HTML文件傳遞給瀏覽器,瀏覽器通過解析HTML代碼最終將頁面顯示在瀏覽器,而瀏覽器和服務器之間遵循的規則就是HTTP協議。
瀏覽器向服務器發送請求數據包的數據頭包括:
請求方式 請求的網頁地址 HTTP協議版本
GET / HTTP/1.1
請求服務器的域名或者地址端口
Host: www.baidu.com
表示請求的方式,超鏈接
Connection: keep-alive
Upgrade-Insecure-Requests: 1
支持的系統版本
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
支持的文件格式
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
支持的壓縮格式
Accept-Encoding: gzip, deflate, sdch, br
支持的語言編碼
Accept-Language: zh-CN,zh;q=0.8
表示該瀏覽記錄用戶的一些愛好
Cookie:
服務器相應給瀏覽器的應答
- 頭部Headers
瀏覽器的版本 響應狀態
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xff90574800000ada
Cache-Control: private
Connection: keep-alive
表示響應的數據的壓縮格式
Content-Encoding: gzip
文件類型 編碼格式
Content-Type: text/html;charset=utf-8
時間
Date: Tue, 17 Nov 2020 02:21:22 GMT
Expires: Tue, 17 Nov 2020 02:20:55 GMT
服務器名稱
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=32809_1429_32857_33121_33058_31660_33098_33101_32962_26350_22160; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1605579682238956673018415314843067222746
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
http協議驗證
模擬HTTP服務器,無論瀏覽器發送什么請求,服務器都響應"hahaha
# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharm #解釋:模擬HTTP服務器,無論瀏覽器發送什么請求,服務器都響應"hahaha" import socket# 服務器的響應 # str = """HTTP/1.1 200 OK # # <h1>hahaha</h1>""" str = "HTTP/1.1 200 OK\r\n" str += "\r\n"; str += "hahaha";def main(): """模擬HTTP服務器"""# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 綁定ip/portserver_addrs = ("127.0.0.1",8088);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);# 循環接受數據并給出相應while True:# 等待瀏覽器連接new_socket, client_add = tcp_socket.accept();# 接受瀏覽器發送請求data = new_socket.recv(1024);#打印瀏覽器發送的請求print(data.decode("gbk"));# 向瀏覽器回應數據new_socket.send(str.encode("gbk"))new_socket.close();new_socket.close();# 關閉套接字if __name__ == "__main__":main();TCP的3次捂手和4次揮手
因為HTPP是遵循TCP協議的,所以HTTP協議必須也的有。
TCP的3次捂手
第一次捂手
當瀏覽器想要向服務器發送請求時,瀏覽器會首先向服務器發送一個探索包,查看瀏覽器時候在線且可以建立連接并且提醒瀏覽器做好資源的準備。
流程:
瀏覽器準備一個并包以syn格式發送該包
包中準備一個數據 syn 數據
為了可以區分請求和應答,使用syn標記該類型數據為請求
第二次捂手
當瀏覽器收到請求,會在瀏覽器發送的那個數據做一點改變比如加1,表示該數據舞已經收到了,此時服務器會將該數據重新封裝并以ack的格式相應給客戶端。
流程:
服務器收到包
將包中的數據加1并重新封裝,并以ack的方式相應給客戶端。
第三次捂手
當服務器將響應包發送完成后,自己也會封裝一個探索包,查看客戶端是否可以收到并讓瀏覽器做好準備。
流程:
服務器準備一個包并以syn格式發送該包
包中準備一個數據 syn 數據
第四次捂手
瀏覽器收到服務器響應后,會立刻收到一個來自服務器的探索包,瀏覽器也會將探索包中的數據加1并重新封裝并將該包以ack的方式發送給瀏覽器
流程:
瀏覽器收到包
將包中的數據加1并重新封裝,并以ack的方式響應給服務器。
組合:
在第二次和第三次時都是瀏覽器發送是數據,一般為了節省時間服務器會將響應包和探索包組合在一起,也就是在第二次捂手時服務器將響應給瀏覽器的包做完改變并在該包中重新添加一個syn方式數據發送給瀏覽器。
TCP的4次揮手
四次揮手是在客戶端和服務器斷開連接的時候進行的,
第一次揮手
當客戶端調用close函數關閉套接字時,客戶端會發送一個包告訴瀏覽器它要斷開連接,然后客戶端就會關閉send發送端,因為socket套接字是全雙工的因此recv接受端不會關閉。
第二次揮手
當服務器接受到客戶端發送過來的數據時,服務器會告訴recv解堵塞,然后關閉recv接受端。
第三次揮手
當服務器關閉了recv接受端后,此時recv返回的數據為空,然后判斷數據為空后,服務器端也會調用close然后,然后也會向客戶端發送一個數據包告訴客戶端它要關閉。此時服務器端關閉send發送
第四次揮手
當客戶端的recv接受端收到數據后,也會回應服務器端一個數據然后關閉recv接受服務。
注意:這第二次揮手和第三次揮手是不能合并的,因為第三次揮手是當服務器端調用close而產生的,而close調不調用是不確定的,而客戶端會一直在等待服務器的第二次揮手,
誰先調用close
一般都是客戶端先調用close,因為誰先調用close誰最后會等待兩分鐘,如果客戶端先調用close如果該服務器突然出現意外斷開比如關閉,這就會很尷尬,因為當想再次啟動該服務時就會起動不開因為服務器都是固定的端口,因為是服務器先調用的close所以該端口還在被占用中所以就會出現啟動不起來的現象。
客戶端的端口都是隨機分配的所以不會出現此尷尬的問題。
解決:解決服務器的尷尬問題,在程序開頭添加 套接字.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
服務器的長連接和短連接
短連接當瀏覽器發送一個請求時,服務器將該請求給響應完畢后,就會斷開連接,如果瀏覽器還需要一個數據就需要再次向服務器連接,并發送請求,HTTP 1.0版本為短連接
長連接當瀏覽器和服務器建立連接后,只有服務器給瀏覽器的所有請求響應完畢后,瀏覽器自動斷開連接,服務再斷開連接。HTTP 1.1 版本為長連接
不過我們之前創建的都屬于短連接,但現在HTTP版本都是1.1,如果想要創建長連接只需要在響應頭中加入:Content-Length:數據長度,
案例:返回瀏覽器需要的頁面-HTTP服務器
# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import redef recv_data(new_socket):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans = "HTTP/1.1 200 OK\r\n";respans += "\r\n";new_socket.send(respans.encode("gbk"));new_socket.send(html_content);# 關閉套接字new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 綁定ip/portserver_addrs = ("127.0.0.1",8088);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);# 循環接受數據并給出相應while True:# 等待連接new_socket, client_add = tcp_socket.accept();# 處理數據recv_data(new_socket)# 關閉套接字tcp_socket.close();if __name__ == "__main__":main();案例:返回瀏覽器需要的頁面-HTTP服務器——使用多進程實現
# -*- codeing = utf-8 -*- # @Time : 2020/11/21 11:19 # @Author : King # @File : 服務器模擬——使用多線程實現.py # @software : PyCharm# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import multiprocessingdef recv_data(new_socket):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans = "HTTP/1.1 200 OK\r\n";respans += "\r\n";new_socket.send(respans.encode("gbk"));new_socket.send(html_content);new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 綁定ip/portserver_addrs = ("127.0.0.1",8085);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);# 循環接受數據并給出相應while True:# 等待連接new_socket, client_add = tcp_socket.accept();# 創建進程處理數據p = multiprocessing.process(target = recv_data, args = (new_socket,))p.start();new_socket.close();# 關閉套接字tcp_socket.close();if __name__ == "__main__":main();案例:返回瀏覽器需要的頁面-HTTP服務器——使用多線程實現
# -*- codeing = utf-8 -*- # @Time : 2020/11/21 11:19 # @Author : King # @File : 服務器模擬——使用多線程實現.py # @software : PyCharm# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import multiprocessing import threadingdef recv_data(new_socket):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans = "HTTP/1.1 200 OK\r\n";respans += "\r\n";new_socket.send(respans.encode("gbk"));new_socket.send(html_content);new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 綁定ip/portserver_addrs = ("127.0.0.1",8085);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);# 循環接受數據并給出相應while True:# 等待連接new_socket, client_add = tcp_socket.accept();# 創建進程處理數據p = threading.Thread(target=recv_data, args=(new_socket,))p.start();#new_socket.close();# 關閉套接字# tcp_socket.close();if __name__ == "__main__":main();案例:返回瀏覽器需要的頁面-HTTP服務器——gevent實現
# -*- codeing = utf-8 -*- # @Time : 2020/11/21 11:19 # @Author : King # @File : 服務器模擬——使用多線程實現.py # @software : PyCharm# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import gevent from gevent import monkeydef recv_data(new_socket):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans = "HTTP/1.1 200 OK\r\n";respans += "\r\n";new_socket.send(respans.encode("gbk"));new_socket.send(html_content);new_socket.close();print(">>>>>>>>>>>>")def main():# 將解堵塞全部修改為gevent的解堵塞monkey.patch_all();# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 綁定ip/portserver_addrs = ("127.0.0.1",8085);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);# 循環接受數據并給出相應while True:# 等待連接new_socket, client_add = tcp_socket.accept();# 創建寫協程處理數據gevent.spawn(recv_data, new_socket);#new_socket.close();# 關閉套接字# tcp_socket.close();if __name__ == "__main__":main();使用單進程、單線程、非解堵塞實現
# -*- codeing = utf-8 -*- # @Time : 2020/11/21 12:03 # @Author : King # @File : 單進程、單線程、非解堵塞.py # @software : PyCharm# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import timedef server_repans(new_socket,data):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求#data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans = "HTTP/1.1 200 OK\r\n";respans += "\r\n";new_socket.send(respans.encode("gbk"));new_socket.send(html_content);new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 防止服務器先調用close出錯tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 設置非解堵塞tcp_socket.setblocking(False)# 綁定ip/portserver_addrs = ("127.0.0.1",8088);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);socket_list = list();# 循環接受數據并給出相應while True:# time.sleep(0.5)# 等待連接# 捕捉等待數據時的異常,沒有數據請求會產生try:new_socket, client_add = tcp_socket.accept();except Exception as ret :# print("----沒有客戶端請求-----");pass;else:# print("----新的客戶端連接---");# 將新客戶設置為非堵塞new_socket.setblocking(False);# 將新套接字加入套接字列表socket_list.append(new_socket);# 遍歷套接字列表,循環服務每位客戶for client_socket in socket_list:# 捕捉接受異常,沒有數據請求會產生try:recv_data = client_socket.recv(1024);except Exception as ret :#print(ret);pass;else:# 判斷客戶是否請求完畢# 如果有數據,但數據為空,表示客戶調用closeif recv_data:server_repans(client_socket,recv_data.decode("gbk"));else:client_socket.close();socket_list.remove(client_socket);# 處理數據# 關閉套接字tcp_socket.close();if __name__ == "__main__":main();實現長連接
# -*- codeing = utf-8 -*- # @Time : 2020/11/21 13:16 # @Author : King # @File : 長連接實現.py # @software : PyCharm # -*- codeing = utf-8 -*- # @Time : 2020/11/21 12:03 # @Author : King # @File : 單進程、單線程、非解堵塞.py # @software : PyCharm# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import timedef server_repans(new_socket,data):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求#data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();# 封裝body部分respans_body = html_content;# 封裝header部分respans_headr = "HTTP/1.1 200 OK\r\n";respans_headr += "Content-Length:%d\r\n" % len(respans_body);respans_headr += "\r\n";respans = respans_headr.encode("gbk") + respans_body;new_socket.send(respans)#new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 防止服務器先調用close出錯tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 設置非解堵塞tcp_socket.setblocking(False)# 綁定ip/portserver_addrs = ("127.0.0.1",8088);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);socket_list = list();# 循環接受數據并給出相應while True:# time.sleep(0.5)# 等待連接# 捕捉等待數據時的異常,沒有數據請求會產生try:new_socket, client_add = tcp_socket.accept();except Exception as ret :# print("----沒有客戶端請求-----");pass;else:# print("----新的客戶端連接---");# 將新客戶設置為非堵塞new_socket.setblocking(False);# 將新套接字加入套接字列表socket_list.append(new_socket);# 遍歷套接字列表,循環服務每位客戶for client_socket in socket_list:# 捕捉接受異常,沒有數據請求會產生try:recv_data = client_socket.recv(1024);except Exception as ret :#print(ret);pass;else:# 判斷客戶是否請求完畢# 如果有數據,但數據為空,表示客戶調用closeif recv_data:server_repans(client_socket,recv_data.decode("gbk"));else:client_socket.close();socket_list.remove(client_socket);# 處理數據# 關閉套接字tcp_socket.close();if __name__ == "__main__":main();epoll技術
我們一般的程序是屬于應用程序單獨一塊內存空間,操作系統一塊內存空間,相互之間無法訪問,所以說,當我們的套接字需要判斷是否有數據到來時,需要進程copyt一份給操作系統操作系統判斷然后告訴該進程,又因為同時啟動的進程不只一個所以就會非常影響效率
epoll技術解決了這個copy的問題,epoll使用內存映射技術,創建一個應用程序和操作系統共享的內存空間,這樣進程將套接字放入到這一空間中就不需要拷貝,而操作系統可以自己訪問該空間檢查。
不過這樣也有一個弊端如果當套接字非常多的時候遍歷一遍非常消耗時間,這樣一遍一遍的遍歷的方式稱為輪詢,還有一種方式是事件通知,
事件通知是如果該套接字有事件發生自己找操作系統,操作系統給你向進程響應,這樣就大大的提高了效率
使用epoll
首先導入select莫快遞
創建epoll:select.epoll();
取epoll中的數據epoll對象.poll();
注將epl對象中數據取出來,取出來的是一個列表,列表中的元素是一個元組(fd,事件)
將數據加入epoll epoll對象.register(fd,事件)
fd:資源注冊符,使用fileno()獲取,
事件:EPOLLIN 接受事件
EPOLLOUT 發送事件
刪除epoll對象.unregister();
epoll技術實現
# -*- codeing = utf-8 -*- # @Time : 2020/11/16 9:55 # @Author : King # @File : 服務器模擬器.py # @software : PyCharmimport socket import re import selectdef server_repans(new_socket,data):"""接受并對客戶進行響應"""print("-------------")#接受用戶的請求#data = new_socket.recv(1024).decode("gbk");# 將請求以行分割,用于提取用戶請求的文件data_list = data.splitlines();# 'GET / HTTP/1.1',# 使用正則提取文件數據ret = re.match("[^/]+(/[^ ]*)",data_list[0])# 判斷數據是否提取出來if ret :file_name = ret.group(1);#如果用戶后面文件名沒有加則返回主頁if file_name == "/":file_name = "/text.html"print(file_name);# 開始拼裝文件名try:f = open("./第十六次作業"+file_name,"rb");except: # 打開文件失敗respans = "HTTP/1.1 404 NOT FOUND\r\n";respans += "\r\n";respans +="------你的文件沒找到————————";new_socket.send(respans.encode("gbk"));else: # 打開文件成功html_content = f.read(1024);f.close();respans_body = html_content;respans_headr = "HTTP/1.1 200 OK\r\n";respans_headr += "Content-Length:%d\r\n" % len(respans_body);respans_headr += "\r\n";respans = respans_headr.encode("gbk") + respans_body;new_socket.send(respans)new_socket.close();print(">>>>>>>>>>>>")def main():# 創建套接字tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 防止服務器先調用close出錯tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 設置非解堵塞tcp_socket.setblocking(False)# 創建一個epoll對象,用于表示那一塊共享的內存epl = select.epoll();# 將套接字對應的fd注冊符加入epl對象中并設置為接受事件epl.register(tcp_socket.fileno(),select.EPOLLIN)# 創建一個字典,用于將fd和套接字對應起來fd_socket_dict = dict();# 綁定ip/portserver_addrs = ("127.0.0.1",8088);tcp_socket.bind(server_addrs);# 設置監聽模式tcp_socket.listen(128);socket_list = list();# 循環接受數據并給出相應while True:# 將epl對象中數據取出來,取出來的是一個列表,列表中的元素是一個元組(fd,事件)fd_even_list = epl.poll();# 遍歷該列表for fd,even in fd_even_list:# 判斷是否為監聽套接字if fd == tcp_socket.fileno():new_socket, client_add = tcp_socket.accept();# 將該套接字加入epl中epl.register(new_socket,select.EPOLLIN);# 將該套接字加入字典fd_socket_dict[fd] = new_socket;elif even == select.EPOLLIN:try:recv_data = fd_socket_dict[fd].recv(1024);except Exception as ret:# print(ret);pass;else:# 判斷客戶是否請求完畢# 如果有數據,但數據為空,表示客戶調用closeif recv_data:server_repans(fd_socket_dict[fd], recv_data.decode("gbk"));else:fd_socket_dict.close();# 從epl中刪除epl.unregister(fd);# 從字典中刪除del fd_socket_dict[fd];# 關閉套接字tcp_socket.close();if __name__ == "__main__":main();總結
以上是生活随笔為你收集整理的python高级编程之网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实现打字机特效
- 下一篇: kubernetes文章收集