Py网络编程及应用(urllib、socket/selectors)
生活随笔
收集整理的這篇文章主要介紹了
Py网络编程及应用(urllib、socket/selectors)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
# -*- coding: utf-8 -*-'''
#Py網絡編程及應用.py
(urllib、socket/selectors)注意:
一、socket模塊:
1、通過指定socket類構造器參數type可指定通信協議類型:TCP協議通信、UDP協議通信
2、socket套接字提供兩個方法:send 和 recv,分別表示發送和接受數據
3、注意發送與接收數據的 編碼解碼問題二、selectors模塊:
1、selectors模塊使用sel的register方法注冊函數,然后使用select方法獲取注冊事件來準備調用注冊函數,
這樣以代替常規阻塞式通信的的accept方法循環 send發送數據 及 recv接收數據。深入:
1、socket通信的 UDP協議通信 多點廣播原理和實現 需后期繼續了解深入
http://c.biancheng.net/view/2663.html使用:
一、urllib 模塊
urllib 模塊可以打開任意 URL 所指向的資源,就像打開本地文件一樣,這樣程序就能完整地下載遠程頁面。
如果再與 re 模塊結合使用,那么程序完全可以提取頁面中各種信息,這就是所謂的“網絡爬蟲”的初步原理。
1、urllib.request子模塊 打開和讀取 URL 的各種函數。
結合 多線程threading模塊 以及 threading.timer定時器 來創建任務進行 多線程下載 以及 定時循環輸出下載完成進度。
2、urllib.parse子模塊 用于解析 URL 地址 和 查詢字符串的函數:二、socket模塊
socket通信 分為兩端:服務器端 和 客戶端;
socket通信 通過指定socket類的構造器參數type可指定通信協議類型TCP、UDP等。
socket通信 基本上是一個信息通道,兩端各有一個程序;三、selectors 模塊
selectors 模塊允許 socket 以非阻塞方式進行通信。
selectors 相當于一個事件注冊中心,程序只要將 socket 的所有事件注冊給 selectors 管理,當 selectors 檢測到 socket 中的特定事件之后,程序就調用相應的監聽方法進行處理。
selectors 主要支持兩種事件:
1、selectors.EVENT_READ:當 socket 有數據可讀時觸發該事件。當有客戶端連接進來時也會觸發該事件。
2、selectors.EVENT_WRITE:當 socket 將要寫數據時觸發該事件。'''# =============================================================================
# #urllib 模塊
# #urllib 模塊可以打開任意 URL 所指向的資源,就像打開本地文件一樣,這樣程序就能完整地下載遠程頁面。
# #如果再與 re 模塊結合使用,那么程序完全可以提取頁面中各種信息,這就是所謂的“網絡爬蟲”的初步原理。
# =============================================================================
'''
使用:一、urllib.request子模塊 打開和讀取 URL 的各種函數。
1、urllib.request.urlopen(url, data=None) 方法,該方法用于打開 url 指定的資源,并從中讀取數據。
根據請求 url 的不同,該方法的返回值會發生動態改變。
如果 url 是一個 HTTP 地址,那么該方法返回一個 http.client.HTTPResponse 對象。2、urllib.request模塊 結合 多線程threading模塊 以及 threading.timer定時器
來創建任務進行 多線程下載 以及 定時循環輸出下載完成進度。二、urllib.parse子模塊 用于解析 URL 地址 和 查詢字符串的函數:
1、urlparse() 解析URL字符串
2、urlunparse() 將解析后ParseResult對象或元祖 回復稱 URL字符串
3、parse_qs() 和 parse_qsl()(這個 l 代表 list)兩個函數都用于解析查詢字符串,只不過返回值不同而已,
4、urljoin() 函數負責將兩個 URL 拼接在一起,返回代表絕對地址的 URL。'''import urllibhelp(urllib)
urllib.__path__
dir(urllib)#import os
#os.startfile(urllib.__path__[0])#urllib 模塊則包含了多個用于處理 URL 的子模塊:
help(urllib.request) #這是最核心的子模塊,它包含了打開和讀取 URL 的各種函數。
help(urllib.parse) #用于解析 URL。
#help(urllib.robotparser) #主要用于解析 robots.txt 文件。(無此子模塊)
help(urllib.response) #
help(urllib.error) #主要包含由 urllib.request 子模塊所引發的各種異常。####################
#urllib.request 子模塊 核心的子模塊,它包含了打開和讀取 URL 的各種函數。
import urllibhelp(urllib.request)
urllib.request.__all__
dir(urllib.request)help(urllib.request.urlopen) help(urllib.request.Request)
help(urllib.request.Request.add_header)#使用:
#urllib.request.urlopen(url, data=None) 方法,該方法用于打開 url 指定的資源,并從中讀取數據。根據請求 url 的不同,該方法的返回值會發生動態改變。如果 url 是一個 HTTP 地址,那么該方法返回一個 http.client.HTTPResponse 對象。
from urllib.request import *result=urlopen('http://www.crazyit.org/index.php') #打開URL對應的資源
data=result.read(326) #按字節讀取數據
print(data.decode('utf-8')) #將字節解碼輸出with urlopen('http://www.crazyit.org/index.php') as f: #上下文管理 打開URL對應的資源data=f.read(326) #按字節讀取數據print(data.decode('utf-8')) #將字節解碼輸出#使用:
#urlopen() 函數打開遠程資源時,第一個 url 參數既可以是 URL 字符串,
#也可以使用 urllib.request.Request 對象。#urllib.request.Request類對象的的構造器方法來發送POST請求等#urllib.request.Request 對象的構造器如下:
#urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)#示例:
#實現了一個多線程下載的工具類:
#通過 urlopen() 函數打開遠程資源之后,也可以非常方便地讀取遠程資源(甚至實現多線程下載)。
from urllib.request import *
import threadingclass DownThread(threading.Thread):'''定義 下載線程 類,繼承自線程模塊類定義 DownThread 線程類,該線程類負責讀取從 start_pos 開始、長度為 current_part_size 的所有字節數據,并寫入本地文件對象中'''def __init__(self, path, start_pos, current_part_size, current_part):super().__init__()self.path=pathself.start_pos=start_pos #定義當前線程的下載位置self.current_part_size=current_part_size #定義當前線程負責下載的文件大小self.current_part=current_part #定義當前線程需要下載的文件快self.length=0 #定義該線程已經下載的字節數def run(self):req=Request(url=self.path, method='GET')req.add_header('Accept','*/*')req.add_header('Charset','UTF-8')req.add_header('Connection','Keep-Alive')f=urlopen(req)for i in range(self.start_pos): #跳過self.start_pos個字節,表明線程只下載自己負責的那部分內容f.read(1)while self.length < self.current_part_size: #讀取網路數據,并寫入本地文件data=f.read(1024)if data is None or len(data) <= 0:breakself.current_part.write(data)self.length += len(data) #累計該線程下載的總大小self.current_part.close()f.close()class DownUtil:'''DownUtils 類的 download() 方法負責按如下步驟來實現多線程下載:1、使用 urlopen() 方法打開遠程資源。2、獲取指定的 URL 對象所指向資源的大小(通過 Content-Length 響應頭獲取)。3、計算每個線程應該下載網絡資源的哪個部分(從哪個字節開始,到哪個字節結束)。4、依次創建并啟動多個線程來下載網絡資源的指定部分。'''def __init__(self,path, target_file, thread_num):self.path=path #定義下載資源的路徑self.thread_num=thread_num #定義線程數量self.target_file=target_file #定義所下載的文件的保存位置self.threads=[] #初始化線程列表def download(self):req=Request(url=self.path, method='GET') #實例化創建Request對象req.add_header('Accept', '*/*') #添加請求頭req.add_header('Charset','UTF-8') req.add_header('Connection', 'Keep-Alive') f=urlopen(req) #打開要下載的資源self.file_size=int(dict(f.headers).get('Content-Length',0)) #獲取所下載的文件大小:字典的get方法,如果不存在默認返回0f.close() current_part_size=self.file_size // self.thread_num + 1 #計算每個線程要下載的文件大小for i in range(self.thread_num):start_pos=i * current_part_size #計算每個線程的開始位置t=open(self.target_file,'wb') #打開文件進行下載t.seek(start_pos,0) #定義線程的下載位置td=DownThread(self.path, start_pos, current_part_size, t) #創建下載線程self.threads.append(td)td.start() #啟動線程def get_complete_rate(self):'''獲取下載的完成百分比'''sum_size=0 #統計多個線程已經下載的總大小for i in range(self.thread_num):sum_size += self.threads[i].lengthreturn sum_size / self.file_size #返回已經完成的百分比# DownUtil 工具類之后,接下來就可以在主程序中調用該工具類的 download() 方法執行下載
du=DownUtil('http://www.crazyit.org/data/attachment/'\+ 'forum/201801/19/121212ituj1s9gj8g880jr.png','測試文件\\a.png',3)
du.download() #實例化創建對象后開始下載操作def show_process():'''間隔時間連續循環顯示完成比例'''print('已完成:{:.0f}%'.format((du.get_complete_rate() *100)))global tif du.get_complete_rate()<1: #如果沒有100%完成t=threading.Timer(0.5,show_process) #定時器 0.5秒后調用自身函數循環t.start()
t=threading.Timer(0.1,show_process) #定時器 0.1秒后調用函數
t.start()#測試 單獨輸出文件大小
path='http://www.crazyit.org/data/attachment/'\+ 'forum/201801/19/121212ituj1s9gj8g880jr.png'req=Request(url=path, method='GET') #實例化創建Request對象
req.add_header('Accept', '*/*') #添加請求頭
req.add_header('Charset','UTF-8')
req.add_header('Connection', 'Keep-Alive') f=urlopen(req) #打開要下載的資源
file_size=int(dict(f.headers).get('Content-Length',0)) #獲取所下載的文件大小
print(file_size)f.close() ####################
#urllib.parse 子模塊 用于解析 URL 地址 和 查詢字符串的函數:help(urllib.parse)
urllib.parse.__all__
dir(urllib.parse)help(urllib.parse.urlparse)
help(urllib.parse.parse_qs)
help(urllib.parse.parse_qsl)
help(urllib.parse.urlencode)
help(urllib.parse.urljoin)#1、urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True):
#該函數用于解析 URL 字符串。程序返回一個 ParseResult 對象,可以獲取解析出來的數據。#2、urllib.parse.urlunparse(parts):
#該函數是上一個函數的反向操作,用于將解析結果反向拼接成 URL 地址。#3、urllib.parse.parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace'):
#該該函數用于解析查詢字符串(application/x-www-form-urlencoded 類型的數據),并以 dict 形式返回解析結果。#4、urllib.parse.parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace'):
#該函數用于解析查詢字符串(application/x-www-form-urlencoded 類型的數據),并以列表形式返回解析結果。#5、urllib.parse.urlencode(query, doseq=False, safe='', encoding=None, errors=None, quote_via=quote_plus):
#將字典形式或列表形式的請求參數恢復成請求字符串。該函數相當于 parse_qs()、parse_qsl() 的逆函數。#6、urllib.parse.urljoin(base, url, allow_fragments=True):
#該函數用于將一個 base_URL 和另一個資源 URL 連接成代表絕對地址的 URL。#########
#示例 使用urlparse()函數來解析URL字符串
from urllib.parse import *#使用:
#urlparse()解析URL字符串
#urlparse()方法解析 URL 字符串,返回一個 ParseResult 對象,該對象實際上是 tuple 的子類。
#因此,程序既可通過屬性名來獲取 URL 的各部分,也可通過索引來獲取 URL 的各部分。
result=urlparse('http://www.crazyit.org:80/index.php;yeeku?name=fkit#frag')
print(result)
print(type(result))#通過屬性名和索引來獲取URL的各部分
print('scheme:', result.scheme, result[0]) #輸出 http
print('主機和端口:', result.netloc, result[1]) #輸出 www.crazyit.org:80
print('主機:', result.hostname) #輸出 www.crazyit.org
print('端口:', result.port) #輸出 80
print('資源路徑:', result.path, result[2]) #輸出 index.php
print('參數:', result.params, result[3]) #輸出 yeeku
print('查詢字符串:', result.query, result[4]) #輸出 name=fkit
print('fragment:', result.fragment, result[5]) #輸出 frag
print(result.geturl())#使用:
#urlunparse()將解析后ParseResult對象或元祖 回復稱 URL字符串
result=urlunparse(('http','www.crazyit.org:80','index.php','yeeku','name=fkit','frag'))
print('反解析后的URL如下:\n' + result)#被解析的 URL 以雙斜線(//)開頭,那么 urlparse() 函數可以識別出主機,只是缺少 scheme 部分。
result=urlparse('//www.crazyit.org:80/index.php')
print('scheme:', result.scheme, result[0])
print('主機和端口:', result.netloc, result[1])
print('資源路徑:', result.path, result[2])
print('-----------------')#被解析的 URL 既沒有 scheme,也沒有以雙斜線(//)開頭,那么 urlparse() 函數將會把這些 URL 都當成資源路徑。
result=urlparse('www.crazyit.org:80/index.php')
print('scheme:', result.scheme, result[0])
print('主機和端口:', result.netloc, result[1])
print('資源路徑:', result.path, result[2])
print('++++++++++++++++++')#使用:
#parse_qs() 和 parse_qsl()(這個 l 代表 list)兩個函數都用于解析查詢字符串,只不過返回值不同而已,
#parse_qsl() 函數的返回值是 list(正如該函數名所暗示的)。
#urlencode() 則是它們的逆函數。
result = parse_qs('name=fkit&name=%E7%96%AF%E7%8B%82java&age=12') #解析查詢字符串,返回dict
print(result)result = parse_qsl('name=fkit&name=%E7%96%AF%E7%8B%82java&age=12') #解析查詢字符串,返回list
print(result)print(urlencode(result)) #將列表格式的請求參數 恢復成 請求參數字符串#使用:
#urljoin() 函數負責將兩個 URL 拼接在一起,返回代表絕對地址的 URL。
#這里主要可能出現 3 種情況:
#1、被拼接的 URL 只是一個相對路徑 path(不以斜線開頭),那么該 URL 將會被拼接到 base 之后,如果 base 本身包含 path 部分,則用被拼接的 URL替換 base 所包含的 path 部分。
#2、被拼接的 URL 是一個根路徑 path(以單斜線開頭), 那么該 URL 將會被拼接到 base 的域名之后。
#3、被拼接的 URL 是一個絕對路徑 path(以雙斜線開頭), 那么該 URL將會被拼接到 base 的 scheme 之后。# 被拼接URL不以斜線開頭
result = urljoin('http://www.crazyit.org/users/login.html', 'help.html')
print(result) # http://www.crazyit.org/users/help.html
result = urljoin('http://www.crazyit.org/users/login.html', 'book/list.html')
print(result) # http://www.crazyit.org/users/book/list.html# 被拼接URL以斜線(代表根路徑path)開頭
result = urljoin('http://www.crazyit.org/users/login.html', '/help.html')
print(result) # http://www.crazyit.org/help.html# 被拼接URL以雙斜線(代表絕對URL)開頭
result = urljoin('http://www.crazyit.org/users/login.html', '//help.html')
print(result) # http://help.html# =============================================================================
# #socket模塊
# #socket模塊 良好的封裝了基于TCP協議的網絡通信,Python使用socket對象來代表兩端的通信端口,并通過socket進行網絡通信
# #socket通信 分為兩端:服務器端 和 客戶端
# #socket通信 通過指定socket類的構造器參數type可指定通信協議類型TCP、UDP等
# #socket通信 基本上是一個信息通道,兩端各有一個程序
# ============================================================================='''
注意:
1、通過指定socket類構造器參數type可指定通信協議類型:TCP協議通信、UDP協議通信
2、socket的TCP協議通信機制提供兩個方法:send 和 recv,分別表示發送和接受數據
3、注意發送與接收數據的 編碼解碼問題
4、socket的UDP協議通信機制提供方法:sendto 和 recvfrom,表示發送和接收數據深入:
1、socket通信的 UDP協議通信 多點廣播原理和實現 需后期繼續深入
http://c.biancheng.net/view/2663.html使用:
一、socket的TCP協議通信的 服務器端 編程的基本步驟:
1、服務器端先創建一個 socket 對象。
2、服務器端 socket 將自己綁定到指定 IP 地址和端口。
3、服務器端 socket 調用 listen() 方法監聽網絡。
4、程序采用循環不斷調用 socket 的 accept() 方法接收來自客戶端的連接。二、socket的TCP協議通信的 客戶端 也是先創建一個 socket 對象,然
后調用 socket 的 connect() 方法建立與服務器端的連接,這樣就可以建立一個基于 TCP 協議的網絡連接。
TCP 通信的客戶端編程的基本步驟大致歸納如下:
1、客戶端先創建一個 socket 對象。
2、客戶端 socket 調用 connect() 方法連接遠程服務器。三、多線程實現socket通信 可實現一個命令行界面的 C/S 聊天室應用,四、socket的UDP協議通信
1、socket.sendto(bytes, address): 發送數據。將 bytes 數據發送到 address 地址。
2、socket.recvfrom(bufsize[, flags]): 接收數據。該方法可以同時返回 socket 中的數據和數據來源地址。'''
import socket####################
#探索socket模塊
help(socket)
socket.__doc__
socket.__file__
socket.__all__
dir(socket)help(socket.socket)
#通過socket類的構造器來創建 socket 實例:#socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)#上面構造器的前三個參數比較重要,其中:
#1、family 參數 用于指定網絡類型。
#該參數支持 socket.AF_UNIX(UNIX 網絡)、socket.AF_INET(基于 IPv4 協議的網絡)和 socket.AF_INET6(基于 IPv6 協議的網絡)這三個常量。#2、type 參數 用于指定網絡 Sock 類型。
#type參數可支持 SOCK_STREAM(默認值,創建基于 TCP 協議的 socket)、SOCK_DGRAM(創建基于 UDP 協議的 socket)和 SOCK_RAW(創建原始 socket)。
#一般常用的是 SOCK_STREAM 和 SOCK_DGRAM。
#如果將該參數指定為 SOCK_DGRAM,則意味著創建基于 UDP 協議的 socket。#3、proto 參數 用于指定協議號,如果沒有特殊要求,該參數默認為 0 ,并可以忽略。####################
#常用類、變量、函數help(socket.socket) #socket通信機制,type參數可支持通信協議類型 SOCK_STREAM(默認值,創建基于 TCP 協議的 socket)、SOCK_DGRAM(創建基于 UDP 協議的 socket)和 SOCK_RAW(創建原始 socket)。
dir(socket.socket)help(socket.gethostname) #獲取當前主機名稱
help(socket.socket.bind) #socket的TCP通信服務器端綁定主機/IP、端口方法
help(socket.socket.listen) #socket的TCP通信服務器端監聽方法
help(socket.socket.accept) #socket的TCP通信服務器端接收連接方法help(socket.socket.connect) #socket的TCP通信客戶端連接方法#socket 對象提供了如下常用方法:#1、socket.accept(): 作為服務器端使用的 socket 調用該方法接收來自客戶端的連接。#2、socket.bind(address):
#作為服務器端使用的 socket 調用該方法,將該 socket 綁定到指定 address,該 address 可以是一個元組,包含 IP 地址和端口。#3、socket.close(): 關閉連接,回收資源。
#4、socket.connect(address): 作為客戶端使用的 socket 調用該方法連接遠程服務器。
#5、socket.connect_ex(address): 該方法與上一個方法的功能大致相同,只是當程序出錯時,該方法不會拋出異常,而是返回一個錯誤標識符。#6、socket.listen([backlog]):
#作為服務器端使用的 socket 調用該方法進行監聽。#7、socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None):
#創建一個和該 socket 關聯的文件對象。#8、socket.recv(bufsize[, flags]):
#接收socket 中的數據。該方法返回 bytes 對象代表接收到的數據。#9、socket.recvfrom(bufsize[,flags]):
#該方法與上一個方法的功能大致相同,只是該方法的返回值是 (bytes, address) 元組。#10、socket.recvmsg(bufsize[, ancbufsize[, flags]]):
#該方法不僅接收來自 socket 的數據,還接收來自 socket 的輔助數據,因此該方法的返回值是一個長度為 4 的元組 (data, ancdata, msg_flags, address),其中 ancdata 代表輔助數據。#11、socket.recvmsg_into(buffers[, ancbufsize[, flags]]):
#類似于 socket.recvmsg() 方法,但該方法將接收到的數據放入 buffers 中。#12、socket.recvfrom_into(buffer[, nbytes[, flags]]):
#類似于 socket.recvfrom() 方法,但該方法將接收到的數據放入 buffer 中。#13、socket.recv_into(buffer[, nbytes[, flags]]):
#類似于 recv() 方法,但該方法將接收到的數據放入 buffer 中。#14、socket.send(bytes[, flags]):
#向socket 發送數據,該 socket 必須與遠程 socket 建立了連接。該方法通常用于在基于 TCP 協議的網絡中發送數據。#15、socket.sendto(bytes, address):
#向 socket 發送數據,該 socket 應該沒有與遠程 socket 建立連接。該方法通常用于在基于 UDP 協議的網絡中發送數據。#16、socket.sendfile(file, offset=0, count=None):
#將整個文件內容都發送出去,直到遇到文件的 EOF。#17、socket.shutdown(how):關閉連接。其中 how 用于設置關閉方式。####################
#示例:#最簡單的服務器
#testSocket_hostA.py
'''
使用:
#TCP 通信的服務器端編程的基本步驟:
#1、服務器端先創建一個 socket 對象。
#2、服務器端 socket 將自己綁定到指定 IP 地址和端口。
#3、服務器端 socket 調用 listen() 方法監聽網絡。
#4、程序采用循環不斷調用 socket 的 accept() 方法接收來自客戶端的連接。
'''
import sockets=socket.socket() #實例化創建socket對象,默認TCP協議類型通信host=socket.gethostname() #獲取計算機全名
port=1234 #指定端口號
s.bind((host,port)) #服務器端首先調用 bind方法綁定主機和端口,注意元組格式
s.listen(2) #服務器端其次調用 listen方法來監聽特定的地址#服務器端采用循環不斷調用socket的accept()方法接收來自客戶端的鏈接
while True:#服務器套接字開始監聽后,就可接受客戶端連接了,使用方法accept來完成.#accept方法將同步阻斷等待 到 客戶端連接到來為止,然后返回一個(client,address)的元組#返回的元組其中client是一個客戶端套接字,address是前面解釋過的地址。c,addr=s.accept() #服務器端接收連接。 開始監聽后,就可接受客戶端連接了,使用方法accept來完成
# print('Got connection from',addr)print(c)print('連接地址:',addr)#注意:傳輸數據,套接字提供兩個方法:send 和 recv,分別表示發送和接受數據#注意發送與接收數據的 編碼解碼問題c.send('您好,您收到了來自服務器的新年祝福。'.encode('utf-8')) #send方法發送數據,提供一個參數為字符串c.close()#注意:
#先運行 服務器端,再運行下面 客戶端#示例
#最簡單的客戶端
#testSocket_hostB.py
'''
使用:
#TCP 通信的客戶端也是先創建一個 socket 對象,然后調用 socket 的 connect() 方法建立與服務器端的連接,這樣就可以建立一個基于 TCP 協議的網絡連接。
#TCP 通信的客戶端編程的基本步驟大致歸納如下:
#1、客戶端先創建一個 socket 對象。
#2、客戶端 socket 調用 connect() 方法連接遠程服務器。
'''
import sockets=socket.socket() #實例化創建socket對象,默認TCP協議類型通信
host=socket.gethostname() #獲取主機名
port=1234 #指定端口號
s.connect((host,port)) #客戶端連接主機和端口 #注意發送與接收數據的 編碼解碼問題
print(s.recv(1024).decode('utf-8')) #recv方法接受數據,提供一個參數為指定最多接受多少個字節的數據####################
#使用:
#多線程實現socket的TCP通信 實現一個命令行界面的 C/S 聊天室應用,#服務器端應該包含多個線程,每個 socket 對應一個線程,該線程負責從 socket 中讀取數據(從客戶端發送過來的數據),
#并將所讀取到的數據向每個 socket 發送一次(將一個客戶端發送過來的數據“廣播”給其他客戶端),因此需要在服務器端使用 list 來保存所有的 socket。#示例
#testSocket_ThreadA.py
#多線程聊天室應用 服務器端import socket
import threadingsocket_list=[] #定義保存所有socket的列表ss=socket.socket() #實例化創建socket對象,默認TCP協議類型通信
ss.bind((socket.gethostname(),1235)) #綁定本機主機和端口
ss.listen() #服務器端開始監聽 來自客戶端的鏈接def read_from_client(s):#定義函數,用來嘗試 接收數據,如果沒有接收到數據則說明此通信端關閉,則從列表中刪除此 通信客戶端。try:return s.recv(2048).decode('utf-8')#如果捕獲到異常,則表明該socket對應的客戶端已經關閉,那么就刪除該socketexcept:socket_list.remove(s)def server_target(s):#定義了server_target() 函數,用作線程target參數函數#該函數將會作為線程執行的 target,負責處理每個 socket 的 通信服務器端。try:#循環不斷的從socket中 讀取 來自客戶端發送來的數據while True:content=read_from_client(s)print(content)if content is None:break#當服務器端線程讀取到客戶端數據之后,程序遍歷 socket_list 列表,#并將該數據向 socket_list 列表中的每個 socket 發送一次#(該服務器端線程把從 socket 中讀取到的數據向 socket_list 列表中的每個 socket 轉發一次)for client_s in socket_list:client_s.send(content.encode('utf-8'))except e:print(e.strerror)#循環不斷準備接收來自客戶端的連接,并未每個客戶端啟動一個線程服務
while True:s,addr=ss.accept() #準備接收來自客戶端的連接 此代碼會阻塞socket_list.append(s) #將對應的socket加入socket_list里表中保存,注意理解是對應的socket,然后開始線程開啟。#每當客戶端連接后,啟動一個 線程服務器端 為該 通信客戶端客戶端 服務threading.Thread(target=server_target, args=(s,)).start()#示例
#testSocket_ThreadB.py
#多線程聊天室應用 客戶端 可多開客戶端#每個客戶端都應該包含兩個線程,
#其中一個 負責讀取用戶的鍵盤輸入內容,并將用戶輸入的數據輸出到 socket 中,
#另一個 負責讀取 socket 中的數據(從服務器端發送過來的數據),并將這些數據打印輸出。由程序的主線程負責讀取用戶的鍵盤輸入內容,由新線程負責讀取 socket 數據。import socket
import threadings=socket.socket() #實例化創建socket對象,默認TCP協議類型通信
s.connect((socket.gethostname(),1235)) #socket通信客戶端 使用 connect方法鏈接 服務器端def read_from_server(s):#定義一個函數,用作線程target參數函數#該函數實現不斷讀取 接收數據 來自服務器發送的數據while True:print(s.recv(2048).decode('utf-8'))#客戶端啟動一個線程 該線程不斷地讀取來自 服務器端的數據
threading.Thread(target=read_from_server, args=(s,)).start()#如果程序讀取到用戶的鍵盤輸入內容,則將內容發送到服務器端
while True:line=input('')if line is None or line=='exit':break#將用戶的鍵盤輸入內容寫入socket服務器端s.send(line.encode('utf-8'))####################
#socket模塊的shutdown(how)方法詳解
#shutdown(how) 關閉方法,該方法可以只關閉 socket 的輸入或輸出部分,用以表示輸出數據已經發送完成。
import socket#shutdown 方法的 how 參數接受如下參數值:
#1、SHUT_RD: 關閉 socket 的輸入部分,程序還可通過該 socket 輸出數據。
#2、SHUT_WR: 關閉該 socket 的輸出部分,程序還可通過該 socket 讀取數據。
#3、SHUT_RDWR: 全關閉。該 socket 既不能讀取數據,也不能寫入數據。#示例
#shutdown() 方法的用法。在該程序中服務器端先向客戶端發送多條數據,
#當數據發送完成后,該 socket 對象調用 shutdown() 方法來關閉輸出部分,
#表明數據發送結束在關閉輸出部分之后,依然可以從 socket 中讀取數據。import sockets=socket.socket() #實例化創建socket對象,默認TCP協議類型通信
s.bind((socket.gethostname(),1236)) #socket通信服務器端 連接當前主機、端口號
s.listen() #socket通信服務器端 開始監聽
skt,addr=s.accept() #socket通信服務器端 開始接收連接
skt.send('服務器的第一行數據'.encode('utf-8')) #socket通信服務器端 發送數據
skt.send('服務器的第二行數據'.encode('utf-8')) #socket通信服務器端 發送數據#關閉該 socket通信服務器端 的輸出部分,程序還可通過該 socket 讀取數據。
skt.shutdown(socket.SHUT_WR) #socket通信服務器端 關閉socket的輸出,表明輸入數據已經結束while True:line=skt.recv(2048).decode('utf-8')if line is None or line=='':breakprint(line)skt.close()
s.close()'''
程序中,關閉了 socket 的輸出部分,此時該 socket 并未被徹底關閉,
程序只是不能向該 socket 中寫入數據了,但依然可以從該 socket 中讀取數據。當調用 socket 的 shutdown() 方法關閉了輸入或輸出部分之后,該 socket 無法再次打開輸入或輸出部分,
因此這種做法通常不適合保持持久通信狀態的交互式應用,只適用于一站式的通信協議,
例如 HTTP 協議,即客戶端連接到服務器端,開始發送請求數據,當發送完成后無須再次發送數據,
只需要讀取服務器端的響應數據即可,當讀取響應數據完成后,該 socket 連接就被完全關閉了。
'''####################
#socket基于UDP協議通信發送和接收數據 及 UDP多點廣播#程序在創建 socket 時,可通過 type 參數指定該 socket 的類型,如果將該參數指定為 SOCK_DGRAM,則意味著創建基于 UDP 協議的 socket。
#在創建了基于UDP 協議的 socket 之后,程序可以通過如下兩個方法來發送和接收數據:
#1、socket.sendto(bytes, address): 發送數據。將 bytes 數據發送到 address 地址。
#2、socket.recvfrom(bufsize[, flags]): 接收數據。該方法可以同時返回 socket 中的數據和數據來源地址。#UDP 協議進行網絡通信時,實際上并沒有明顯的服務器端和客戶端,因為雙方都需要先建立一個 socket 對象,用來接收或發送數據報。
#但在實際編程中,通常具有固定 IP 地址和端口的 socket 對象所在的程序被稱為服務器,因此該 socket 應該調用 bind() 方法被綁定到指定 IP 地址和端口,這樣其他 socket(客戶端 socket)才可向服務器端 socket(綁定了固定 IP 地址和端口的 socket)發送數據報,而服務器端 socket 就可以接收這些客戶端數據報。##########
#示例:
#UDP 協議的 socket 實現 C/S 結構的網絡通信
#本程序的服務器端通過循環 1000 次來讀取 socket 中的數據報,每當讀取到內容之后,便向該數據報的發送者發送一條信息。
#服務器端 socket的UDP協議通信
#testSocketUDP_A.py
import socketbooks=('血皇敖天','劍皇霸天',\'水心云影','寒天晴嵐') #定義字符串數組,準備用來 服務器端發送該數據的元素s=socket.socket(type=socket.SOCK_DGRAM) #實例化創建socket通信,指定通信協議為UDP協議
s.bind((socket.gethostname(),3000)) #socket通信 服務器端 綁定主機、端口號for i in range(1000):#socket的 UDP協議通信的 recvfrom方法 接收數據 可以同時返回 socket 中的數據 和 數據來源地址。data,addr=s.recvfrom(4096) #接收數據,返回數據 和 來源地址 #4096定義每個數據報的大小最大為4KBprint(data.decode('utf-8'))send_data=books[i % 4].encode('utf-8') #求余 定義socket通信服務器端 發送數據內容#socket的 UDP協議通信的 sendto方法 發送數據 到指定地址s.sendto(send_data, addr) #發送數據,到指定地址
s.close()#先運行服務器端,再運行客戶端#客戶端程序的代碼與服務器端類似,
#客戶端采用循環不斷地讀取用戶的鍵盤輸入內容,每當讀取到用戶輸入的內容后,就將該內容通過數據報發送出去;接下來再讀取來自 socket 中的信息(也就是來自服務器端的數據)。
#客戶端 socket的UDP洗衣通信
#testSocketUDP_B.py
import sockets=socket.socket(type=socket.SOCK_DGRAM) #實例化創建socket通信,指定通信協議為UDP協議while True:line=input('')if line is None or line=='':breakdata=line.encode('utf-8')#socket的 UDP協議通信的 sendto方法 發送數據 到指定地址s.sendto(data,(socket.gethostname(),3000))#recv不同于recvfrom#socket的 UDP協議通信的 recvfrom方法 接收數據 可以同時返回 socket 中的數據 和 數據來源地址。data=s.recv(4096)print(data.decode('utf-8'))
s.close()##########
#UDP多點廣播
#多點廣播 可以將數據報以廣播方式式發送到多個客戶端。
#多點廣播 則需要將數據報發送到一個組目標地址,當數據報發出后,整個組的所有主機都能接收到該數據報。
#每一個多點廣播地址都被看作一個組,當客戶端需要發送和接收廣播信息時,加入該組即可#注意:
#DDP多點廣播
#創建了 socket 對象后,還需要將該 socket 加入指定的多點廣播地址中,socket 使用 setsockopt() 方法加入指定組。
#如果創建僅用于發送數據報的 socket 對象,則使用默認地址、隨機端口即可。
#但如果創建接收數據報的 socket 對象,則需要將該 socket 對象綁定到指定端口;否則,發送方無法確定發送數據報的目標端口。import socket#setsockopt()方法 加入指定組
help(socket.socket.setsockopt)help(socket.IPPROTO_IP)
help(socket.IP_MULTICAST_TTL)help(socket.SOL_SOCKET)
help(socket.SO_REUSEADDR)help(socket.IP_ADD_MEMBERSHIP)
help(socket.inet_aton)#支持多點廣播的 socket 還可設置廣播信息的 TTL(Time-To-Live),
#該 TTL 參數用于設置數據報最多可以跨過多少個網絡:
#1、當TTL的值為 0 時, 指定數據報應停留在本地主機中;
#2、當TTL的值為 1 時, 指定將數據報發送到本地局域網中;
#3、當TTL 的值為 32 時, 意味著只能將數據報發送到本站點的網絡上;
#4、當TTL 的值為 64 時, 意味著數據報應被保留在本地區;
#5、當TTL 的值為 128 時, 意味著數據報應被保留在本大洲;
#6、當TTL 的值為 255 時, 意味著數據報可被發送到所有地方;#注意:UDP多點廣播 需后期繼續深入# =============================================================================
# #selectors 模塊
# #selectors 模塊允許 socket 以非阻塞方式進行通信。# #selectors 相當于一個事件注冊中心,程序只要將 socket 的所有事件注冊給 selectors 管理,當 selectors 檢測到 socket 中的特定事件之后,程序就調用相應的監聽方法進行處理。
# #selectors 主要支持兩種事件:
# #1、selectors.EVENT_READ:當 socket 有數據可讀時觸發該事件。當有客戶端連接進來時也會觸發該事件。
# #2、selectors.EVENT_WRITE:當 socket 將要寫數據時觸發該事件。
# ============================================================================='''
注意:
1、selectors模塊使用sel的register方法注冊函數,然后使用select方法獲取注冊事件來準備調用注冊函數,
這樣以代替常規阻塞式通信的的accept方法循環 send發送數據 及 recv接收數據。使用:
一、selectors模塊 實現非阻塞式編程的步驟大致如下:
1、創建 selectors 對象。
2、通過 selectors 對象為 socket 的 selectors.EVENT_READ 或 selectors.EVENT_WRITE 事件注冊監聽器函數。每當 socket 有數據需要讀寫時,系統負責觸發所注冊的監昕器函數。
3、在監聽器函數中處理 socket 通信。'''import selectorshelp(selectors)
selectors.__doc__
selectors.__file__
dir(selectors)help(selectors.EVENT_READ)
dir(selectors.EVENT_READ)help(selectors.EVENT_WRITE)
dir(selectors.EVENT_WRITE)####################
#常用類及屬性help(selectors.DefaultSelector()) #默認的selectors對象,實例化創建后 可以調用 類方法 進行事件注冊
dir(selectors.DefaultSelector) #默認的selectors對象,實例化創建后 可以調用 類方法 進行事件注冊help(selectors.DefaultSelector.register) #注冊事件
help(selectors.DefaultSelector.unregister) #取消注冊事件
help(selectors.DefaultSelector.select) #獲取注冊事件,返回列表(key,events)###################
#示例 ##########
#使用selectors模塊實現非阻塞式socket通信服務器端程序
#服務器端:
import selectors, socket#實例化創建selectors的默認對象,然后可以調用 類方法sel.register() 進行事件注冊。
sel=selectors.DefaultSelector() #創建默認的selectors對象
socket_list=[]def read(skt, mask):'''負責監聽"有數據可讀"事件的函數嘗試:如果讀取到數據則將讀取到的數據發送給每個socket通信客戶端,否則就是通信退出 則關閉該socket通信客戶端,并從列表中刪除。異常:則 取消 socket通信客戶端 的 注冊事件,并關閉該socket通信客戶端,并將其從列表中刪除。參數skt 為一個socket通信客戶端連接。'''#嘗試:讀取數據,并將讀取到的數據發送給每個socket_listtry:data=skt.recv(1024)#如果讀取到數據,則將數據循環發送給每個socket_listif data:for s in socket_list:s.send(data)#如果沒有讀取到數據,則 關閉 socket的通信客戶端連接 并 從socket_list中刪除此 該通信客戶端連接else:print('關閉',skt)skt.close() #socket的通信客戶端關閉socket_list.remove(skt) #從列表中刪除socket的通信客戶端#如果異常:將該socket關閉,并從socket_list列表中刪除except:print('關閉',skt)#取消注冊事件sel.unregister(skt) #取消skt的注冊事件skt.close() #socket的通信客戶端關閉socket_list.remove(skt) #從列表中刪除socket的通信客戶端def accept(sock, mask):'''負責監聽“有客戶端連接進來”事件的函數'''conn,addr=sock.accept() #socket通信服務器端 準備接收 客戶端連接socket_list.append(conn) #socket_list添加保存接收到的 socket通信客戶端conn.setblocking(False) #設置socket為非阻塞式#sel.register類方法 注冊事件 #即:為conn的READ事件注冊監聽函數read,用來讀取后循環輸出給每一個socket通信客戶端。sel.register(conn, selectors.EVENT_READ, read) #函數內注冊READ事件監聽函數read#創建socket通信服務器端的順序步驟
sock=socket.socket() #實例化創建通信
sock.bind((socket.gethostname(), 1236)) #socket通信服務器端 綁定本機主機名、端口號
sock.listen() #執行監聽
sock.setblocking(False) #設置該socket是非阻塞式的#注意:使用sel的register注冊方法注冊函數,然后使用select方法獲取注冊事件來調用注冊函數,
#這樣以代替常規阻塞式通信的的accept方法循環 send發送數據 及 recv接收數據。#使用sel為 sock的EVENT_READ事件 注冊read監聽函數
sel.register(sock, selectors.EVENT_READ, accept) #為對象sock注冊事件函數 即為socket服務器端注冊監聽函數事件
#register(self, fileobj, events, data=None)#采用死循環方式 不斷提取sel的事件
while True:#sel.select()類方法 獲取注冊事件 返回列表結構 元素包含兩個內容(key,events)events=sel.select()for key,mask in events:#key的data屬性 獲取為該事件注冊的監聽函數callback=key.data#key的fileobj屬性 獲取被監聽的socket對象callback(key.fileobj, mask) #執行key.data獲取到的監聽函數##########
#使用selectors模塊實現非阻塞式socket通信客戶端程序
#客戶端:
import selectors, socket, threadingsel=selectors.DefaultSelector() #創建默認的selectors對象def read(conn, mask):data=conn.recv(1024)if data:print(data.decode('utf-8'))else:print('closing', conn)sel.unregister(conn)conn.close()s=socket.socket() #創建socket對象
s.connect((socket.gethostname(),1236)) #socket通信客戶端 連接 本機主機名、端口
s.setblocking(False) #設置該socket為非阻塞式#使用sel為 s 的EVENT_READ事件 注冊 read監聽函數
sel.register(s, selectors.EVENT_READ, read) def keyboard_input(s):while True:line=input('')if line is None or line=='exit':breaks.send(line.encode('utf-8'))
#創建線程執行函數
threading.Thread(target=keyboard_input, args=(s,)).start()while True:#sel.select()類方法 獲取注冊事件 返回列表結構 元素包含兩個內容(key,events)events=sel.select()for key,mask in events:#key的data屬性 獲取為該事件注冊的監聽函數 callback=key.data#key的fileobj屬性 獲取被監聽的socket對象callback(key.fileobj, mask) #執行key.data獲取到的監聽函數
總結
以上是生活随笔為你收集整理的Py网络编程及应用(urllib、socket/selectors)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity 攻击范围检测
- 下一篇: hdu - 1435 Stable Ma