python 協程及socket網路程式設計
阿新 • • 發佈:2018-11-10
協程
什麼是協程
協程,英文Coroutines,是一種比執行緒更加輕量級的存在。正如一個程序可以擁有多個執行緒一樣,一個執行緒也可以擁有多個協程。
最重要的是,協程不是被作業系統核心所管理,而完全是由程式所控制(也就是在使用者態執行)。
這樣帶來的好處就是效能得到了很大的提升,不會像執行緒切換那樣消耗資源。
協程優勢:
- 有較高的執行效率, 始終只有一個執行緒, 不存在建立執行緒和銷燬執行緒需要的時間;
- 也沒有執行緒切換的開銷, 任務需要開啟執行緒數越多, 協程的優勢越明顯;
- 不需要多執行緒的鎖機制
如何實現協程
yiled實現協程
import threading import time def producer(c): c.__next__() n = 0 while n < 5: n += 1 print("[生產者]生產資料:%s" %(n)) res = c.send(n) print("[消費者的返回值為:%s" %(res)) def consumer(): r = 'a' while True: n = yield r if not n: return print("[消費者]執行%s...." %(n)) time.sleep(1) r = '200 ok' if __name__=='__main__': print(threading.active_count()) c = consumer() producer(c) print(threading.active_count())
gevent 實現協程
# 由於切換是在IO操作時自動完成, 所以gevent需要修改python自帶的一些標準庫; # gevent提供了patch_*來對於標準庫作修改; import time from gevent import monkey monkey.patch_all() import gevent def job(n): for i in range(n): print(gevent.getcurrent(),i) time.sleep(1) def main1(): # 建立三個協程, 並讓該協程執行job任務 # 假設多協程執行的任務, 沒有IO操作或者等待, 那麼協程間是依次執行, 而不是交替執行; # 假設多協程執行的任務, IO操作或者等待, 那麼協程間是交替執行; g1 = gevent.spawn(job,2) g2 = gevent.spawn(job,3) g3 = gevent.spawn(job,2) # 等待所有的協程執行結束, 再執行主程式; gevent.joinall([g1,g2,g3]) print("任務執行結束....") main1()
協程案例
import time from urllib.request import urlopen from concurrent.futures import ThreadPoolExecutor import gevent # 1. 打補丁 from gevent import monkey from mytimeit import timeit monkey.patch_all() def load_url(url): with urlopen(url) as conn: data = conn.read() print("%s網頁位元組數為%s" %(url,len(data))) URLS = ['http://httpbin.org', 'http://example.com/']*100 @timeit def gevent_main(): gevents = [gevent.spawn(load_url,url) for url in URLS] gevent.joinall(gevents) @timeit def thread_main(): with ThreadPoolExecutor(max_workers=100) as f: f.map(load_url,URLS) if __name__=="__main__": thread_main() gevent_main()
socket網路程式設計
網路通訊三要素
- IP
- 分類:
IPv4: 172.25.254.100 ===> 32位的二進位制格式, 點分十進位制法; 2^32-1
IPv6: ===> 128位的二進位制格式 , 冒分十六進位制; - 檢視:
ip addr show br0
- port: 為了標識通訊的應用程式(埠)
- 常見的port和服務的對應關係:/etc/services
- 已經被分配的port: 0-1024
- 自定義埠號的範圍: 1024-65535
- 通訊協議: TCP和UDP
import socket
print(socket.gethostname())
# 'www.baiu.com'根據域名獲取對應伺服器的ip地址
print(socket.gethostbyname('www.baidu.com'))
# 根據IP獲取對應的主機名
print(socket.gethostbyaddr('114.114.114.114'))
#獲取詳細資訊
print(socket.getaddrinfo('www.xunlei.com',80),type(socket.getaddrinfo('www.xunlei.com',80)))
# AddressFamily.AF_INET : ipv4
# socket.AF_INET6 : ipv6
# SOCK_STREAM: TCP協議
# socket.SOCK_DGRAM: UDP協議
socket實現web簡易伺服器
TCP工作方式:
import socket
def handle_request(sockobj):
sockobj.send(b'HTTP/1.1 200 OK\r\n\r\n')
with open('hello.html') as f:
sockobj.send(f.read().encode('utf-8'))
if __name__ == '__main__':
# 1. 建立一個socket物件,預設引數 AddressFamily.AF_INET : ipv4, SOCK_STREAM: TCP協議
server = socket.socket()
# 2. 繫結ip和埠
server.bind(('172.25.254.78',9001))
# 3. 監聽是否有客戶端連線
server.listen(3)
print("伺服器端已經啟動9001埠....")
while True:
# 4. 接受客戶端連線
sockobj , address = server.accept()
print(sockobj,address)
# 5. 接受客戶端傳送的訊息
recv_data = sockobj.recv(1024)
# 6. 與客戶端進行互動, 返回給客戶端資訊
handle_request(sockobj)
sockobj.close()
TCP 實現客戶與服務端聊天
服務端
# 1. 建立一個socket物件
import socket
server = socket.socket()
# 2. 繫結ip和埠
server.bind(('172.25.254.78',9002))
# 3. 監聽是否有客戶端連線
server.listen()
print("服務端已經啟動9002埠.....")
# 4. 接收客戶端連線
sockobj , address =server.accept()
while True:
# 5. 接收客戶端傳送的訊息
recv_data = sockobj.recv(1024).decode('utf-8')
print('client>:%s' %(recv_data))
if recv_data == 'quit':
break
# 6. 給客戶端回覆訊息
send_data = input("server>:")
sockobj.send(send_data.encode('utf-8'))
if send_data == 'quit':
break
# 7. 關閉socket物件
sockobj.close()
server.close()
客戶端
import socket
HOST = '172.25.254.78'
PORT = 9002
# 1. 建立客戶端的socket物件
client = socket.socket()
# 2. 連線服務端, 需要指定埠和IP
client.connect((HOST,PORT))
while True:
# 3. 給服務端傳送資料
send_data = input("client>:")
client.send(send_data.encode('utf-8'))
if send_data == 'quit':
break
# 4. 獲取服務端返回的訊息
recv_data = client.recv(1024).decode('utf-8')
print('server>:%s' %(recv_data))
if recv_data=='quit':
break
# 5. 關閉socket連線
client.close()
拓展 協程實現服務端對多個客戶端(TCP)
服務端:
def handle_request(sockobj):
while True:
recv_data = sockobj.recv(1024).decode('utf-8')
print("client>:%s" %(recv_data))
if recv_data == 'quit':
break
send_data = input("server>:")
sockobj.send(send_data.encode('utf-8'))
if send_data == 'quit':
break
from gevent import monkey
monkey.patch_all()
import gevent
import socket
server = socket.socket()
server.bind(('172.25.254.78',9001))
server.listen()
print("服務已經啟動9001埠...")
while True:
sockobj , address = server.accept()
#建立協程
gevent.spawn(handle_request,sockobj)
sockobj.close()
server.close()
客戶端:
import socket
HOST = '172.25.254.78'
PORT = 9001
# 1. 建立客戶端的socket物件
client = socket.socket()
# 2. 連線服務端, 需要指定埠和IP
client.connect((HOST,PORT))
while True:
# 3. 給服務端傳送資料
send_data = input("client>:")
client.send(send_data.encode('utf-8'))
if send_data == 'quit':
break
# 4. 獲取服務端返回的訊息
recv_data = client.recv(1024).decode('utf-8')
print('server>:%s' %(recv_data))
if recv_data=='quit':
break
# 5. 關閉socket連線
client.close()
UDP 實現客戶與服務端聊天
UDP 工作方式:
服務端:
import socket
HOST = '172.25.254.78'
PORT = 9001
# 1. 建立socket物件
server = socket.socket(type=socket.SOCK_DGRAM)
# 2. 繫結IP和port
server.bind((HOST,PORT))
print("等待客戶端的UDP請求.....")
# 3. 接收客戶端傳送的訊息
data , address = server.recvfrom(1024)
print("接受到客戶端的訊息:",data.decode('utf-8'))
print("客戶端的連線的socket地址:",address)
# 4. 給客戶端回覆訊息
server.sendto(b'hello client',address)
# 5. 關閉socket物件
server.close()
客戶端:
import socket
HOST = '172.25.254.78'
PORT = 9001
# 1. 建立socket物件
client = socket.socket(type=socket.SOCK_DGRAM)
# 2. 傳送訊息給服務端
client.sendto(b'hello server',(HOST,PORT))
# 3. 接收服務端返回的資訊
data, address = client.recvfrom(1024)
print("接收服務端的訊息:",data)
# 4. 關閉socket物件
client.close()
通過socket爬取網頁內容
import socket
from urllib.request import urlopen
# 獲取網頁內容
#print(urlopen('http://www.baidu.com').read())
client = socket.socket()
client.connect(('www.baidu.com',80))
client.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
recv_data = client.recv(1024*100)
print(recv_data.decode('utf-8'))
client.close()