1. 程式人生 > >python 協程及socket網路程式設計

python 協程及socket網路程式設計

協程

什麼是協程

協程,英文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網路程式設計

網路通訊三要素

  1. IP
  • 分類:
    IPv4: 172.25.254.100 ===> 32位的二進位制格式, 點分十進位制法; 2^32-1
    IPv6: ===> 128位的二進位制格式 , 冒分十六進位制;
  • 檢視:
    ip addr show br0
  1. port: 為了標識通訊的應用程式(埠)
  • 常見的port和服務的對應關係:/etc/services
  • 已經被分配的port: 0-1024
  • 自定義埠號的範圍: 1024-65535
  1. 通訊協議: 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()

在這裡插入圖片描述