protocol stack
复杂的网络建立在简单网络服务的基础之上
用API获取一个JSON文档
import requests
def geocode(address):
parameter = {'address': address, 'sensor': 'false'}
base = 'http://maps.googleapis.com/maps/api/geocode/json'
respose = requests.get(base, params=parameter)
ans = respose.json()
print(ans['result'][0]['geometry']['location'])
if __name__=='__main__':
# decode
in_byte = b'\xff\xfe4\x001\x003\x00 \x00i\x00s\x00 \x00i\x00n\x00.\x00'
in_char = in_byte.decode('utf-16')
print(repr(in_char)) # repr()将对象转化为解释器可读取的对象
# encode
out_char = 'We copy you down. Eagle.\n'
out_byte = out_char.encode('utf-8')
with open('eagle.txt', 'wb') as f:
f.write(out_byte)
主机名转IP(DNS)
import socket
if __name__=='__main__':
hostname = 'www.baidu.com'
addr = socket.gethostbyname(hostname)
print("The IP address of {} is {}".format(hostname, addr))
RFC文档
multiplexing
reliable transport
>>>import socket
>>>socket.getservbyname('domain')
# 53
socket.socket
对象的方法请求使用该套接字的系统调用时, 该对象都会自动使用内部维护的套接字整数标识符promiscuous
不考虑地址是否正确, 接受并处理所有收到的数据包的网络监听客户端
解决方案
设计唯一标识符
检查数据包的地址与请求数据包的地址是否相同
== 或者 connect()
ctrl + z # 暂停服务器
ctrl + c # 暂停服务器
UDP套接字名是IP接口和UDP端口号组成的二元组
sendto()
指定每个数据包的目标地址, 然后使用recvfrom()
接受相应connect()
将其与目标地址连接, 然后使用send()
和recv()
进行通信(只支持同时与一台服务器交互的情况)选项 | 说明 |
---|---|
SO_BROADCAST | 允许发送并接受UDP广播数据包 |
SO_DONTROUTE | 数据包不经由网关发送 |
SO_TYPE | 返回套接字类型 |
IP地址
与端口号
特定IP地址
与特定端口号
local_ip
local_port
remote_ip
remote_port # remote 远程
connect()
调用只是对绑定套接字进行了设置, 设置了后续send()
或recv()
调用所要使用的默认远程地址, 不会导致任何错误connect()
是真实的网络操作, 会在要通信的客户端和服务器之间进行三次握手, 这意味着connect()
是有可能失败的网络栈可能碰到的三种情况
最后一种情况的存在, 调用send()需要检查返回值, 还需要在一个循环内进行send()调用
# sendall()方法 该方法用C语言实现
# 因为在循环中释放了GIL锁, 因此其他线程在数据发送完成前不会竞争资源
bytes_sent = 0
while bytes_sent < len(message):
message_remaining = message[bytes_sent:]
bytes_sent += s.send(message_remaining)
没有相应的库封装
如果一个协议没有严格要求服务器在客户端 请求发送完成后才读取完整的请求, 然后再返回完整的相应,就会,发送阻塞
解决办法: 手动关闭程序,重新进行编写, 以改进程序的设计
关闭双向套接字中任一方向的通信连接
返回一个Python文件对象,该对象实际上会在底层调用recv()和send()
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
hasattr(sock, 'read')
# False
f = sock.makefile()
hasattr(f, ,'read')
# True
mysocket.accept()
:由TCP流的监听套接字调用。
mysocket.bind(address)
:将特定的本地地址分配给套接字mysocket.connect(address)
:通过套接字发送的数据会被传输至特定的远程地址mysocket.getpeername
:返回了与套接字连接的远程地址mysocket.getsockname
:返回了套接字自身的本地端点地址mysocket.recvfrom(...)
:用于UDP, 返回一个二元组
mysocket.sendto(data, address)
:未连接的UDP端口使用该方法向特定远程地址发送数据创建及部署套接字的步骤:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('localhost, 1060'))
- 地址族(address family)—>AF_INET
- 套接字类型(socket type)—>SOCK_DGRAM/STREAM
- 协议(protocol)—IPPROTO_TCP/UDP
- IP地址
- 端口号
getaddrinfo
:将用户指定的主机名和端口号转换为可供套接字方法使用的地址
from pprint import pprint
infolist = socket.getaddrinfo('gatech.edu', 'www')
pprint(infolist)
为服务器绑定端口
from socket import getaddrinfo
getaddrinfo(None, 'smtp', 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
连接服务
getaddrinfo('ftp.kernel.org', 'ftp', 0, socket.SOCK_STREAM, 0,
socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
请求规范的主机名
getaddrinfo('iana.org', 'www', 0, socket.SOCK_STREAM, 0,
socket.AI_ADDRCONFIG | socket.AI_V4MAPPED | socket.AI_CANONNAME)
getaddrinfo()标记 | 说明 |
---|---|
AI_ALL | IPv4地址重写为与之对应的IPv6地址 |
AI_NUMERICHOST | 将主机名字符串作为字面IPv4或IPv6地址来解析 |
AI_NUMERICSERV | 禁用’www’符号形式端口名, 采用’80’端口号 |
whois [域名]
# sudo apt-get install whois 下载
计算机的内存芯片和网卡都支持将字节作为通用传输单元
0b1100010
# 98
0b1100010 == 0o142 == 98 == 0x62
# True
ASCII: American Standard Code for Information Interchange
for i in range(32, 128, 32):
print(' '.join(chr(j) for j in range(i ,i+32)))
# ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
将真正的Unicode字符串转换为字节字符串
将字节字符串转换为真正的Unicode字符
大端法(big-endian)
小端法(little-endian)
import struct
struct.pack(', 4253) # 小
# b'\x9d\x10\x00\x00'
struct.pack('>i', 4253) # 大
# b'\x00\x00\x10\x9d'
struct.unpack('>i', b'\x00\x00\x10\x9d')
# (4253,)
shutdown()
,看上去对称, 也能提高冗余性将文本命令与数据混合使用
import pickle
pickle.dumps([5, 6, 7])
# b'\x80\x03]q\x00(K\x05K\x06K\x07e.'
>>> import zlib
>>> data = zlib.compress(b'Python') + b'.' + zlib.compress(b'zlib') + b'.'
>>> data
b'x\x9c\x0b\xa8,\xc9\xc8\xcf\x03\x00\x08\x97\x02\x83.x\x9c\xab\xca\xc9L\x02\x00\x04d\x01\xb2.'
>>> len(data)
28
OSError
: socket模板的可能抛出的主要错误socket.gaierror
: 该异常在getaddrinfo
无法找到提供的名称或服务时被抛出socket.timeout
: 有时我们会决定为套接字设定超时参数, 而不希望永远等待granular
: 针对每个网络调用
blanket
: 特定的多个连续网络操作结合起来
import sys
...
try:
deliver_updated_keyfiles(...)
except (socket.error, socket.gaierror) as e:
print("cannot deliver remote keyfiles: {}".format(e), file=sys.stderr)
exit(1)
except:
FatalError("cannot send replies: {}".format(e))
TLS: Transport Layer Security 传输层安全协议
LLS: Secure Sockets Layer 安全套接层
证书:certificate CA列表
公钥:public key
签名:signature CA为证书加上的数学标记
TLS特别关注的路两种类型的字段: notBefore日期/notAfter日期 表示有效期
临时:intermedia
方案一更易于升级与修改
context
对象, 该对象保存了我们对证书认证与加密算法选择的偏好设置wrap _socket
方法, 表示让OpenSSL库负责控制我们的TCP连接, 然后与通信对方交换必要握手信息, 并建立加密连接wrap_socket()
调用返回ssl_sock对象, 进行所以后续通信
ssl.create_default_context()
常见上下文对象的目的, 可将第一个参数设置成Purpose.SERVER_AUTH
,表示该上下文对象为客户端所使用, 用于验证连接服务器
cafile
脚本验证远程证书时信任的证书机构
ssl.wrap_socket
4个缺点
协议 | 说明 |
---|---|
http.client | 构造HTTPSConnection对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
smtplib | 构造SMTP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
poplib | 构造POP3_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
imaplib | 构造IMAP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
ftplib | 构造FTP_TLS对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
nntplib | 构造NNTP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext |
getpeercert()
:返回一个Python字典, 包含了证书选出的字段cipher()
:返回OpenSSL与通信对象的TLS实现最终协商确认compression()
:返回正在使用的压缩算法名称或是Python的单例对象None业界广泛使用的方法: 在服务器前端配置一个
负载均衡器(load balancer)
,客户端直接连接到负载均衡器, 然后由负载均衡器将连接请求转发至实际的服务器
部署(deployment)
fork()
创建一个Unix守护进程, 安排进行系统级的日志操作, 支持配置文件以及提供启动/关闭/重启的相关机制Platform as a Service “平台即服务”
测试每一行代码花费的时间
python3 -m trace --tg --ignore-dir=/usr filename.py ""
每个线程都可以拥有服务器监听套接字的一个副本, 并运行自己的accept()函数。 操作系统会将每个新的客户端连接交由任何运行了accept()函数并处于等待的线程来处理。 如果所有线程都处于繁忙状态的话, 操作系统会将该连接置于队列中, 直到某个线程空闲为止
send()
或recv()
调用时永远不会阻塞调用进程select()
调用—>效率低poll()
调用epoll()
调用回调风格
协程风格 (coroutine)
yield
语句的函数在该服务的inted.conf配置文件中将第4个字段设为nowait
1060 stream tcp nowait brandon /usr/bin/python3 /usr/bin/python3 filenam.py
内存缓存守护进程(memory cache deamon), Memcached将安装它的服务器上的空闲RAM与一个很大的近期最少使用(LRU)的缓存结合使用
pip install python3-memcached
import memcached
mc = memcached.Client(["127.0.0.1:11211"])
mc.set('user:19', "Simple is better than complex")
# True
mc.get('user:19')
# "Simple is better than complex"
Memcached
允许我们为缓存中的每一项设置一个过期时间, 到达这个时间时, Memcached
会负责悄悄将这些项丢掉当Memcached客户端得到包含了多个Memcached实例的列表时, 会根据每个键的字符串值的散列值对Memcached数据库进行分区(shard), 由计算出的散列值决定用Memcached集群中的哪台服务器来存储特定的记录
alpha
server0 36355 0.36
server1 23667 0.23
server2 29893 0.29
server3 12486 0.12
hash
server0 25750 0.25
server1 25587 0.25
server2 25657 0.25
server3 25407 0.25
md5
server0 25626 0.25
server1 25600 0.25
server2 25496 0.25
server3 25679 0.25
消息队列协议允许我们发送可靠的数据块, 注意不是数据报(datagram)
recv()
这样的函数pip install gunicorn httpbin requests
gunicorn httpbin:app
gzip
和deflate
两种压缩格式的HTTP响应, 而urllib
则不支持Content-Length
头,Content-Length
的值是一个十进制整数, 表示消息体包含的字节数Transfer-Encodin
头, 并将其设置为chunked
Connection: close
, 然后就能够随意发送任意大小的消息体请求服务器将请求路径指向的文档作为响应发送回浏览器
不包含消息体, 附加到请求路径后面的任何参数都至只能修改返回后的文档,而不能修改服务器上的数据
提交新文档/评论以及数据库行
两次运行同一个POST会在服务器上进行两次相同的操作,因此既不能将POST操作的结果存入缓存以提高后续重复操作的速度,也不能在没有接收到响应的时候自动重试POST
其余的方法简单分为两大类
1、类似于GET
2、类似于POST
请求与给定路径匹配的HTTP头的值
请求服务器做好一切发送资源的准备, 但只发送头信息
向服务器发送一个新的文档, 该文档上传后就会存在于指定的路径上
请求服务器删除指定的路径及所有存在与该路径下内容
用于调试
将所使用的协议从HTTP切换为其他协议
状态码 | 说明 |
---|---|
200~300 | 成功 |
300~400 | 重定向,不包含消息体 |
400~500 | 客户端的请求无法被识别或非法,包含消息体 |
500~600 | 服务器错误导致了一些意外错误,包含消息体 |
请求成功。如果是
POST
操作, 表明已经对服务器产生了预期的影响
尽管路径合法,但是该路径已经不是所请求资源目前的官方路径了。
客户端若要获取响应,应请求
Location
头中给出的URL。如果客户端希望将URL存入缓存, 则所有后续的请求都会直接忽略旧URL, 直接转向新URL
通过某个路径请求资源时, 客户端可以通过使用GET方法对响应信息的
Location
头中给出的URL进行请求, 以获取响应结果, 但是对该资源的后续请求仍然需要通过当前请求路径来完成任何使用POST正确提交的表单都应该返回303状态码, 这样就能通过安全/幂等GET方法获取客户端实际看到的页面了
不需要在响应中包含文档内容, 原因在于请求头指出了客户端已经在缓存中存储了所请求文档的最新版本
无论客户端使用GET或POST方法发起了什么样的请求,都需要使用响应的
Location
头中给出的另一个URL重新发送请求
这不是一个合法的HTTP请求
客户端没有在请求中向服务器提供正确的密码、cookie或其他验证数据来证明客户端有访问服务器的权限
路径没有指向一个已经存在的资源
服务器能够识别方法和路径,但是该方法无法应用于该路径
服务器希望完成请求,但是由于某些内部错误,暂时无法请求
服务器无法识别请求中给出的HTTP方法
请求的服务器是一个网关或代理,它无法连接到真正为该请求路径提供响应的服务器
>>> import requests
>>> r = requests.get('http://httpbin.org/status/301')
>>> (r.status_code, r.url)
(200, 'http://httpbin.org/get')
>>> r.history
[<Response [301]>, <Response [302]>]
>>>r = requests.get('http://httpbin.org/status/301',
allow_redirects=False)
>>> r.raise_for_status()
>>> (r.status_code, r.url, r.headers['Location'])
(301, 'http://httpbin.org/status/301', '/redirect/1')
>>> r = requests.get('https://baidu.com/')
>>> r.url
'http://www.baidu.com/'
>>> r = requests.get('http://www.github.com/')
>>> r.url
'https://github.com/'
cookie是一个键值对。任何从服务器发送至客户端的成功响应中都可以传输cookie
Web服务器网关接口(Web Server Gateway)
environ
,用于接收一个字典,字典中提供的键值对是旧式的CGI环境变量集合的扩展start_response()
,本身也可以被调用,WSGI应用程序通过该方法来声明响应头信息app
函数可以开始生成字符串,也可以返回一个可迭代对象python
编写的服务器,服务器代码中可以直接调用WSGI接口mod_wsgi
并运行Apache,在一个独立的WSGIDaemonProcess
中运行Python代码,由mod_wsgi
启动守护进程Gunicorn
的PythonHTTP
服务器,然后在前端运行一个既能返回静态文件,又能对Python编写的动态资源服务进行反向代理的Web服务器(Apache/nginx)基于CPython的3个运行时特性,即解释器占用内存大/解释器运行慢/全局解释器锁(GIL)禁止多个线程同时运行Python字节码
平台即服务(Platform as a Service)提供商,Docker支持用户在本地的Linux机器上运行Heroku风格的容器
Representational State Transfer 表述性状态转移
四个接口规范
WebOb的
Response
类提供了content_type
和charset
两个独立的属性, 允许用户将Content-Type
头信息的两部分(text/plain; charset=utf-8)看作两个独立的值进行处理
>>> from urllib.parse import urlsplit
>>> u = urlsplit('https://www.google.com/search?q=apod&btnI=yes')
>>> tuple(u)
('https', 'www.google.com', '/search', 'q=apod&btnI=yes', '')
>>> u.scheme
'https'
>>> u.netloc
'www.google.com'
>>> u.path
'/search'
>>> u.query
'q=apod&btnI=yes'
>>> u.fragment
''
>>> u = urlsplit('https://brandon:atigdng@localhost:8000/')
>>> u.netloc
'brandon:atigdng@localhost:8000'
>>> u.username
'brandon'
>>> u.password
'atigdng'
>>> u.hostname
'localhost'
>>> u.port
8000
>>> from urllib.parse import urljoin
>>> base = 'http://tools.itef.org/html/rfc3986'
>>> urljoin(base, 'rfc7320')
'http://tools.itef.org/html/rfc7320'
>>> urljoin(base, '.')
'http://tools.itef.org/html/'
>>> urljoin(base, '..')
'http://tools.itef.org/'
>>> urljoin(base, '/dailydose/')
'http://tools.itef.org/dailydose/'
>>> urljoin(base, '?version=1.0')
'http://tools.itef.org/html/rfc3986?version=1.0'
>>> urljoin(base, '#section-5.4')
'http://tools.itef.org/html/rfc3986#section-5.4'
# 斜杠问题
>>> urljoin('http://tools.itef.org/html/rfc3986', 'rfc7320')
'http://tools.itef.org/html/rfc7320'
>>> urljoin('http://tools.itef.org/html/rfc3986/', 'rfc7320')
'http://tools.itef.org/html/rfc3986/rfc7320'
# 不确定HTTP(s)
>>> urljoin(base, '//www.google.com/search?q=apod')
'http://www.google.com/search?q=apod'