日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Py网络编程及应用(urllib、socket/selectors)

發布時間:2024/3/13 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。