版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
# -*- 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 urllib
help(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 urllib
help(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 threading
class DownThread(threading.Thread):
'''
定义 下载线程 类,继承自线程模块类
定义 DownThread 线程类,该线程类负责读取从 start_pos 开始、长度为 current_part_size 的所有字节数据,
并写入本地文件对象中
'''
def __init__(self, path, start_pos, current_part_size, current_part):
super().__init__()
self.path=path
self.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:
break
self.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方法,如果不存在默认返回0
f.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].length
return 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 t
if 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 socket
s=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 socket
s=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 threading
socket_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对应的客户端已经关闭,那么就删除该socket
except:
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 threading
s=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 socket
s=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=='':
break
print(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 socket
books=('血皇敖天','剑皇霸天',\
'水心云影','寒天晴岚') #定义字符串数组,准备用来 服务器端发送该数据的元素
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定义每个数据报的大小最大为4KB
print(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 socket
s=socket.socket(type=socket.SOCK_DGRAM) #实例化创建socket通信,指定通信协议为UDP协议
while True:
line=input('')
if line is None or line=='':
break
data=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 selectors
help(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_list
try:
data=skt.recv(1024)
#如果读取到数据,则将数据循环发送给每个socket_list
if 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, threading
sel=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':
break
s.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获取到的监听函数