python3_网络编程

网络编程

socket标准库

socket标准库是非常底层的接口库,socket是一种通用的网络编程接口
协议族Address Family
* AF_INET: IPV4
* AF_INET6: IPV6
* AF_UNIX: Unix Domain Socket,unix系统主机的socket
socket类型
* SOCK_STREAM: 面向连接的流socket, TCP协议
* SOCK_DGRAM: 无连接的datagram数据报socket, UDP协议


TCP编程

服务器

  1. 创建socket对象, sock= socket.socket(family, type)
  2. 绑定ip地址和端口, sock.bind(laddr: str,port: int)
  3. 开始监听, sock.listen()
  4. 接受accept连接,创建用于传输数据的socket对象
    s, addr(raddr,port) = sock.accept()
    阻塞,直到有连接创建,如果阻塞过程中被另一个线程close(),抛出OSError在非套接字上尝试操作
  5. 接收receive和发送send数据

    • recv(bufsize), 阻塞,直到从socket接收最大bufsize大小的data,返回bytes对象,如果remote end(远端) 正常closed, 函数返回b”, 强制关闭会抛异常; 如果在阻塞过程中,被另一个线程close(),抛出OSError”中止了一个连接”“, bufsize的大小最好是2的幂, 例如4096
    • send(data: bytes), 发送数据
    • close(), 关闭连接,

其他一些方法:

socket.makefile(mode='rw', buffering=None, *,encoding=None, errors=None,newline=None) 返回一个与该socket**相关联**的类文件对象,recv和send方法被read和write方法代替
socket.getpeername() 返回socket remote end地址,元组(rattr, port)
socket.getsockname() 返回socket 自己的地址,(lattr, port)
socket.setblocking(flag) flag为0,将socket设置为非阻塞模式,recv()不阻塞,没有数据就抛异常

客户端

  1. 创建socket,
  2. 创建连接 connect((raddr, port))
  3. 传输数据 ,send(),recv()
  4. 停止发送或接受数据, shutdown(flag), flag等于socket.SHUT_RD | SHUT_WR | SHUT_RDWR,关闭接收数据,关闭发送数据,全部关闭.关闭发送数据(SHUT_WR)后,对端每次recv立刻返回一个空bytes,
  5. close(),关闭连接,只是释放和连接相关的资源,要明确关闭连接,请先使用shutdown后再close
    close() releases the resource associated with a connection but does not necessarily close the connection immediately. If you want to close the connection in a timely fashion, call shutdown() before close().

UDP编程

服务端

  1. 创建socket, type=socket.SOCK_DGRAM
  2. bind((hostaddr, port)),绑定IP端口
  3. 接收数据recvfrom(),返回data和对端地址;
    发送数据sendto(bytes, (raddr, port))
    可以使用connect((raddr, port)),添加远端地址, 表示只接受指定地址的消息
  4. close(),关闭连接

客户端

  1. 新建socket, type=socket.SOCK_DGRAM
  2. 接收数据recvfrom(),返回data和对端地址; 发送数据sendto(bytes, (raddr, port))
    connect((raddr, port)),仅添加远端地址,只接受指定地址的消息
  3. close(),关闭连接

SocketServer

SocketServer简化了网络服务器的编写
4个同步类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer
2个Mixin类:ForkingMixIn和ThreadingMixIN,用来支持异步

class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ForkingUDPServer(ForkingMixIN, UDPServer): pass
class ThreadingUDPServer(ThreadingMixIN, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIN, TCPServer): pass

fork创建多进程, thread是创建多线程
编程接口
socketserver.BaseServer(server_address, RequestHandlerClass)
RequestHandlerClass类必须是BaseRequestHandler类的子类,每一个请求会实例化对象,处理请求,拥有以下属性和方法:

self.request 是和客户端连接的socket对象
self.server 是server自己
self.client_address 是客户端地址
setup() 连接初始化
handle() 处理连接
finish() 连接清理

创建服务器步骤:

  1. 派生BaseRequestHandler子类,覆盖其中的,三个方法
  2. 实例化服务器类,传入服务端地址和请求处理类
  3. 启动服务器处理请求,处理一次handle_request()和永远处理serve_forever()
  4. 关闭服务器器,server_close()

server的方法和属性

address_family
socket_type
shutdown_request(self,request) shutdown并close一个独立的请求
close_request(self,request) close一个独立的请求
get_request(self) -> (request, client_addr) 获得一个独立的请求,会阻塞, 相对于accept
fileno(self) -> server.socket 的文件描述符, selector使用


zerorpc

是一个非常轻巧的, 跨语言的通信模块, 官网


异步编程

同步和异步
函数和方法被调用时,调用者是否直接得到最终的结果
直接得到就是同步调用,不直接得到就是异步调用,
阻塞和非阻塞
函数和方法被调用时,是否立即返回,
立即返回就是非阻塞,不立即返回就是阻塞
同步异步和阻塞非阻塞不相干

读取(read)IO两个阶段:
1. 数据准备阶段,内核从输入设备读取数据
2. 内核空间数据复制到用户进程缓冲区阶段

IO模型
同步IO模型包括: 阻塞IO, 非阻塞IO

  1. 同步阻塞IO,进程阻塞,直到拿到数据
  2. 同步非阻塞IO,进程在数据准备阶段不阻塞,但时不时会询问内核数据是否准备好,第二阶段还是会阻塞
  3. IO多路复用,同步非阻塞IO的增强,不用进程自己询问,而是实现一个应用程序接口,作为系统调用,同时监控多个IO请求(通过检查文件描述符状态),进程阻塞在接口处,一旦有一个数据可读,就通知进程来读取数据,即进入第二阶段,同样要阻塞.
    • select 数组,线性遍历O(n),有连接上限1024(x86)
    • poll 链表,线性遍历O(n),无连接上限
    • epoll 哈希表,时间通知机制,增加回调机制,O(1),无连接上限,fd一次拷贝
      异步IO模型: 进程不阻塞,内核完成数据准备后,直接将数据放入用户空间缓冲区,再通知进程进行后续操作

Python中的IO多路复用
select库,实现了select,poll系统调用,通用性好,操作系统都支持,但性能较差,部分实现了epoll

selectors库

实现了kqueque,epoll,devpoll,poll,select,
selectors.DefaultSelector会选择,当前系统性能最优的实现
编程步骤:

  1. 创建selector对象,DefaultSelector
  2. 创建文件对象sock,绑定端口,开启监听,设置非阻塞setblocking(False)
  3. 注册sock文件对象, selector.register(fileobj,event,data=None)返回一个SelectorKey对象,其实是一个namedtuple
    SelectorKey = namedtuple(‘SelectorKey’, [‘fileobj’, ‘fd’, ‘events’, ‘data’])
    fileobj文件对象,
    event事件selector.EVENT_READ(ob1) | selector.EVENT_WRITE(ob10),
    fd 文件描述符,
    data 回调函数或数据,当事件被触发时使用
  4. 开启selector监控循环(while True),
    ready = selector.select() -> 阻塞的,返回一个list,表示准备好的事件列表,每一个元素是一个二元组,(selectorkey,mask),mask表示发生的事件,1表示可读, 2表示可写, 3表示读写都可
    selectorkey存储了注册时的所有信息,fileobj,fd,events,data
  5. 反注册所有已注册fileobj,并关闭fileobj,fileobj存储在selector.get_map()中,最后关闭selector,

selector对象方法和属性
selector.get_map() -> dict, {fd:selectorkey, …}, key是文件描述符


aiohttp

from aiohttp import web
import asyncio
from aiohttp import ClientSession

async def handle(request: web.Request):
    print(request.match_info)
    print(request.query_string)
    return web.Response(text=request.match_info.get('id', '0000'), status=200)

# app = web.Application()
# app.router.add_get('/{id}', handle)
# web.run_app(app, host='0.0.0.0', port=9977)

# client
async def get_html(url: str):
    async with ClientSession() as session:
        async with session.get(url) as res:
            print(res.status)
            text = await res.text()
            with open('bai', 'w', encoding='utf8') as f:
                f.write(text)

url = 'http://www.baidu.com'
loop = asyncio.get_event_loop()
loop.run_until_complete(get_html(url))
loop.close()

WSGI协议 Web Server Gateway Interface

Browser—(http request)—>WSGI Server—(解包,封装eviron)—>WSGI App处理数据
—(http request)—>WSGI Server—(解包,封装eviron)—>WSGI App处理数据
Browser<—(http body)—WSGI Server
Browser<—(http status, response header)—WSGI App使用WSGI Server提供的方法
WSGI服务器,wsgiref参考库

from wsgiref.simple_server import make_server, demo_app
server = make_server(ip, port, demo_app)
try:
    server.serve_forever()  # server.handle_request()
except:
    server.shutdown()
    server.server_close()

demo_app(environ, start_response) ->iterable:
# demo_app 接收两个参数
# environ dict,http请求头部信息,key: REQUEST_METHOD, PATH_INFO, QUERY_STRING....
# start_response 向Browser发送response status,header的方法,接收两个参数
#           start_response(status:str, response_header:二元组, exc_info=None)
# return必须是iterable, return要在start_response后

测试命令: curl -I url 获得请求头
curl -X POST -d data -X指定方法,-d传输数据

类Flask框架实现
urllib库解析QUERY_STRING
from urllib import parse
parse.parse_qs(environ.get(‘QUERY_STRING’))
webob–environ解析库

request = webob.Request(environ)   #将environ解析成request对象
request.headers/method/path/query_string/GET/POST/params
# GET返回url中的数据, POST返回body中提交的数据, params返回所有数据
from webob.multidict import MultiDict
# 多值字典,add(key, value), key不能为int, 相同的key可以共存, 
# md.getone(k)有且只有一个, md.getall()返回所有

res = webob.Response() # 实例化response对象,参数可以定义响应的status, body等
return res(environ, start_response) # response实例**可调用**,返回一个可迭代对象

# 使用 webob.dec装饰器实现app,一个request一个reponse
from webob.dec import wsgify
@wsgify
def app(request: webob.Request) -> webob.Response:
    res = webob.Response('body')
    return res         # return 可以是str,bytes,或者Response对象

类Flask框架实现

  1. 路由功能实现:
    将不同的(method, url_pattern, handler)注册到列表lst(有序), request到了后,遍历注册的列表,method匹配,并且url匹配就执行handler(request),返回response_body
    url匹配使用正则表达式,使用命名分组可以表示匹配字段含义,
    三部分:
    • class App, 使用__call__方法作为调用函数,接收request
    • class Router, 路由方法定义,路由信息收集
    • handlers, 定义及注册, 装饰器
  2. 路由分组,实现一级目录,即url前缀
    Route类实例添加前缀信息, 实例分别存放对应的注册信息, Route实例注册到App中, 遍历route实例
  3. 正则表达式的化简
    化简注册时使用的这则表达式为,对应的字段名和取值类型,{name:type},简化注册,同时提高匹配效率,可以将url匹配到的数据,转换成注册时设置的类型,传给handler处理
  4. 模板技术
    将数据填入html模板中作为response, 使用jinja2模块

    # index.html
    <ul>
        {% for id, name, age in userlist %}
        <li>{{loop.index}} {{id}} {{name}} {{age}}</li>
        {% endfor %}
        {{usercount}}}
    </ul>
    # template.py
    from jinja2 import Environment, PackageLoader, FileSystemLoader
    env = Environment(loader=FileSystemLoader('/web/templates')) # 添执行时主模块的相对路径,或绝对路径
    template = env.get_template('index.html') # 搜索env中loader文件夹下的index.html
    res = template.render(d)  # 渲染返回填好数据str, d为dict, key对应模板中的{{key}}
    
  5. 拦截器
    Preinterceptor / Postinterceptor
    全局interceptor, app中
    局部interceptor, router中
  6. json支持
    response = webob.Response(json=d) d为dict类型
  7. 模块化, 发布
    setup.py sdist

django

Django是采用MVC框架设计的开源WEB快速开发框架
自带ORM, Template, Form, Auth核心组件
Django 1.11版本同时支持python2和3
安装pip install django==1.11
1. 管理程序:
/LIb/site-packages/django/bin/django-admin
django-admin startproject –help
创建项目:
2. django-admin startproject blog .
manage.py, 命令行工具, 应用创建, 数据库迁移等
blog/settings.py, 项目配置文件, 数据库参数等
blog/urls.py, URL路径映射配置
blog/wsgi, 定义WSGI接口信息, 一般无须改动
3. 数据库配置
DATABASES={
‘ENGINE’: ‘django.db.backends.mysql’
‘NAME’: ‘blog’,
‘USER’: ‘peijun’,
‘PASSWORD’: ‘centos’,
‘HOST’: ‘192.168.10.129’,
‘PORT’: ‘3306’
}
4. Mysql python数据库驱动, mysqlclient
pip install mysqlclient
5. 创建应用
python manage.py startapp user
在setting.py中添加user应用
文件作用:
admin.py 管理站点模型的声明文件,用于后台管理
models.py 模型层model类定义
views.py 定义URL响应函数
migrations包 数据迁移文件生成目录
apps.py 应用的信息定义文件
6. 模型Model, Django ORM
AutoField
BooleanField
NullBooleanField
CharField
TextField
IntegerField/BigIntegerField
DecimalField 使用python的Decimal实例表示十进制浮点数, max_digits/decimal_places
FloatField python的Float实例表示浮点数
DateField 使用python的datetime.date实例表示的日期, auto_now/auto_now_add
TimeField 使用python的datetime.time实例表示的时间
DateTimeField 使用python的datetime.datetime实例表示的时间
FileFile 上传文件的字段
ImageField 对上传文件进行校验, 确保是一个幼小的图片

字段选项
db_column 表中字段的名称, 未指定, 使用属性名
primary_key 设置主键
unique 设置唯一键
default 设施缺省值
null 设置null
blank Django表单验证中, 是否可以不填写, 默认为False
db_index 设置索引

关系类型字段
ForeignKey 表示一对多, ForeignKey(‘tablename.column’)/ForeignKey(‘seld’)
ManyToManyField 表示多对多
OneToOneField 表示一对一
一对多时, 自动创建会增加_id后缀, 对象.属性_id
从一访问多,使用对象.小写模型类_set
从一访问一,使用对象.小写模型类

创建Model类
from django.db import models
class User(models.Model):
class Meta:
db_table = ‘user’

模型操作:
模型类的objects属性, 是一个默认的manager, 用于和数据库交互, 也可以手动指定管理器
模型类实例调用save(), delete()的时候,事务自动提交
查询集,QuerySet: 1.惰性求值, 只有查询集被使用是才查询数据库 2.拥有缓存,一个查询集可以被一个变量保存
返回查询集的方法叫做过滤器:
User.object.all() :select * from User:
User.objects.all()[20:40] :limit offset查询集切片:
filter(column=’name’, pk=10) 筛选满足条件的记录/ exclude()排除满足条件的记录 :where子句:
order_by() :order by:
values() :记录用字典表示(column:value), 放入列表返回
pk总是表示主键
返回单值的方法:
get() 只返回一条记录, 多或者少都会抛异常
count() 返回查询总条数
first() 返回第一条记录
last() 返回最后一条记录
exist() 查询是否有数据, 有则返回True
字段查询表达式(Field Lookup)
语法: filter(colname__method=value)
exact: filter(isdeleted__exact=False) 严格等于, 可省略不写
contains: exclude(title__contains=”b”) 等价于 not like ‘%b%’
startswith: filter(title__startwith=”w”)
endswith: 以什么结尾
isnull/isnotnull: 是否为None
iexact/icontains/istartwith/iendswith: 忽略大小写
in: filter(pk__in=[1,2,3])
year,month, day, week_day, hour, minute, second: 对日期类型指定具体时间
Q对象
from django.db.models import Q
Q类接收条件, Q对象可以使用&(and), |(or), ~(not)操作
filter函数,如果混用关键字参数和Q对象, Q对象必须位于关键字参数前面,所有参数条件都会被and到一起

  1. 迁移Migration
    生成迁移文件
    python manage.py makemigrations
    执行迁移, 在数据库中生成表
    python manage.py migrate

  2. Django后台管理

    • 创建管理员
      python manage.py createsuperuser
    • 本地化 在settings.py中设置
      LANGUAGE_CODE = ‘zh-Hans’
      USE_TZ = True
      TIME_ZONE = ‘Asia/Shanghai’
    • 启动WEB Server
      python manage.py runserver
    • 登录后台管理
    • 注册应用模块 admin.py中添加
      admin.site.register(User)
  3. 路由, url函数
    url(regex_pattern, view, kwargs=None, name=None)
    url(r’^user/’, include(‘user.urls’))
    include动态导入指定app下的urls模块, 二级匹配使用
  4. 模板 template
    新建模板目录template, settings.py配置模板路径

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(file)))
    TEMPLATES = [{‘DIRS’: [os.path.join(BASE_DIR, ‘templates’)],},]
    模板使用分为两步:

    • 加载模板, template = django.template.loader.get_template(‘index.html’)
    • 渲染
      context = django.template.RequestContext(request, {‘content’: ‘something’})
      return django.http.HttpResponse(template.render(context))
      快捷方式渲染
      from django.shortcuts import render
      return render(request, ‘indext.html’, {‘content’: ‘something’})
      **DTL语法**Django Template Language
    • 变量: {{variable}}, 使用.点号访问容器内元素或对象内属性和方法, 调用方法不加括号, 变量未定义使用”“
    • 模板标签
      if/else标签:
      {% if condition %}
      …display
      {% elif condition %}
      …display
      {% else %}
      …display
      {% endif %}
      条件支持and, or, not
      for标签
      {% for athlete in athlete_list %}
      {{athlete.name}}
      {% endfor %}
      for标签内变量:
      forloop.counter 从1开始计数 forloop.revcounter 倒计数到1
      forloop.counter0 从0开始计数 forloop.recounter0 倒计数到0
      forloop.first 是否是循环的第一次 forloop.last 循环的最后一次
      forloop.parentloop 嵌套循环时, 内层循环使用外层循环
      {% for athlete in athlete_list reversed %} 反向迭代
      {% ifequal val1 val2 %}/{% ifnotequal val1 val2 %} 比较相等
      {% csrf_token %} 跨站请求保护, 防止跨站攻击??
      CSRF(Cross-site request forgery) 跨站请求伪造, cookie授权, 伪造受信任用户
      {# comment statement #} 单行注释
      {% comment %} statement {% endcomment %} 多行注释
      过滤器: 在变量被显示前修改它, 语法{{variable|handler}}|两边没有空格
      有的过滤器可以传参, :"para"
      {{name|lower}}
      {{name|first/last|upper}}
      {{my_list|join:”,”}}
      value|divisibleby:”2”|yesno:”True,False,None”, 能否被2整除, yesno可以只有两个参数true_false
      value|add:”100”
      |addslashes 在反斜杠和单引号或者双引号前面加上反斜杠
      |length
      |default:”” 变量等价False则使用缺省值
      |default_if_none:”” 变量为None则使用缺省值
      |date|”n j Y” 格式化日期, n月j日Y年
  5. 返回错误状态,返回json数据
    Django中有许多错误类, 实例可以作为view函数的返回值
    from django.http import HttpResponseBadRequest, JsonResponse
    HttpResponse(status=401)

认证

cookie和session
cookie存储session id, 每一次请求都被附带,
session id有过期的机制, 过期后session和cookie分别在服务端和客户端被清除
session信息会消耗服务器内存, 同时在多服务器部署时, 要考虑session共享的问题redis,memcached
memcached: 数据库查询缓存, 提高网站访问速度

无session方案, JWT(Json WEB Token)
服务器生成一个标识(代表客户端ID), 并对这个标识使用算法签名(防止数据被客户端篡改), 组成JWT数据
下次客户端将JWT数据发回, 服务端就可以确认是否是认证过的用户
pip install pyjwt
jwt_token = jwt.encode({‘payload’: ‘id’}, key, ‘HS256’) -> bytes
key是编码和解码用的密钥, 尽可能复杂, settings.py 中的SECRET_KEY是一个强密码,导入使用
设置超时: 将"exp": int(datetime.datetime.now().timestamp()) + TOKEN_EXPIRE 加入payload, TOKEN_EXPIRE是超时时间
from django.conf import settings
token以b’.’分为三部分,header(jwt,algorithm), payload, signature
前两部分使用base64编码的, 用base64解码后可以得到源信息, 所有jwt不是用来加密的, 仅保证数据不被修改
alg = algorithms.get_default_algorithms()[‘HS256’]
newkey = alg.prepare_key(SECRET_KEY)
signing_input, _, _ = token.rpartition(b’.’)
sign = alg.sign(signing_input, newkey)
signature = base64.urlsafe_b64encode(sign)
前面两部分字典转换成json格式的字符串(注意冒号两边没有空格),再encode成bytes, 再用base64编码
header和payload两部分由b’.’相连, 再有这个部分和密钥key生成的bytes, 再用base64编码,与前面相连
payload = jwt.decode(jwt_token, key, algorithms=[‘HS256’])

RSA是公开的密钥密码体制, 有PK和SK, 非对称算法, 所有人都可以使用PK加密,只有拥有SK,才能对消息解密. 对极大整数做因数分解的难度决定了RSA算法的可靠性.
HMAC数字签名, 对称算法, 一个密钥和一个消息作为输入, 输入一个签名
消息双方都有密钥, 用于验证连接是否合法, (发送随机数, 双方算出结果, 验证)
或者用于确保消息不被篡改, (消息和签名绑定)

密码加密bcrypt

慢算法, 耗时长, 不同密码使用不同盐

bcrypt.gensalt() 生成盐
bcrypt.hashpw(password, salt) 生成加密密钥
password是bytes, 密钥由盐决定, 生成的加密密钥也是bytes, 盐就是前22个字节
bcrypt.checkpw(password, enci_password)

猜你喜欢

转载自blog.csdn.net/qq_33287645/article/details/81455653
今日推荐