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