Python8
Python1-环境配置
Python2-基础认识
Python3-数据类型
Python4-面向对象
Python5-闭包和装饰器
Python6-IO模块
Python7-进程线程携程
Python8-网络编程
Python爬虫
tcp服务器
v1
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址,127.0.0.1也可不填为空
# sk.bind(('', 5000)) # 与下相同
sk.bind(('127.0.0.1', 5000))
# 监听连接请求
sk.listen(5) # 半连接池大小
print('服务端启动成功,在5000端口等待客户端连接')
# 取出连接请求,开始服务
conn,addr = sk.accept()
print('连接对象:',conn)
print('客户端ip+端口:',addr)
# 数据传输
data = conn.recv(1024) # 接收
data = data.decode('utf-8')
print('客户端发送的数据:',data)
conn.send(data.upper().encode('utf-8')) # 发送
# 结束服务
conn.close()
# 关闭服务端
# sk.close()
v2
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址
sk.bind(('127.0.0.1', 5000))
# 监听连接请求
sk.listen(5) # 半连接池大小
print('服务端启动成功,在5000端口等待客户端连接')
# 取出连接请求,开始服务
conn,addr = sk.accept()
print('连接对象:',conn)
print('客户端ip+端口:',addr)
# 数据传输
while True:
data = conn.recv(1024) # 接收
data = data.decode('utf-8')
if data == 'q':
break
print('客户端发送的数据:',data)
conn.send(data.upper().encode('utf-8')) # 发送
# 结束服务
conn.close()
# 关闭服务端
# sk.close()
v3
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址
sk.bind(('127.0.0.1', 5000))
# 监听连接请求
sk.listen(5) # 半连接池大小
print('服务端启动成功,在5000端口等待客户端连接')
# 取出连接请求,开始服务
conn,addr = sk.accept()
print('连接对象:',conn)
print('客户端ip+端口:',addr)
# 数据传输
while True:
try:
data = conn.recv(1024) # 接收
except:
break
# Linux和mac系统上使用的退出
if not data:
break
data = data.decode('utf-8')
print('客户端发送的数据:',data)
conn.send(data.upper().encode('utf-8')) # 发送
# 结束服务
conn.close()
# 关闭服务端
# sk.close()
v4
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址
sk.bind(('127.0.0.1', 5000))
# 监听连接请求
sk.listen(5) # 半连接池大小
print('服务端启动成功,在5000端口等待客户端连接')
# 取出连接请求,开始服务
conn,addr = sk.accept()
print('连接对象:',conn)
print('客户端ip+端口:',addr)
# 数据传输
while True:
try:
data = conn.recv(1024) # 接收
except:
break
# Linux和mac系统上使用的退出
if not data:
break
data = data.decode('utf-8')
print('客户端发送的数据:',data)
conn.send(data.upper().encode('utf-8')) # 发送
# 结束服务
conn.close()
# 关闭服务端
# sk.close()
v5
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址
sk.bind(('127.0.0.1', 5000))
# 监听连接请求
sk.listen(5) # 半连接池大小
print('服务端启动成功,在5000端口等待客户端连接')
# 取出连接请求,开始服务
# 服务多个对象
while True:
conn,addr = sk.accept()
print('连接对象:',conn)
print('客户端ip+端口:',addr)
# 数据传输
while True:
try:
data = conn.recv(1024) # 接收
except:
break
# Linux和mac系统上使用的退出
if not data:
break
data = data.decode('utf-8')
print('客户端发送的数据:',data)
conn.send(data.upper().encode('utf-8')) # 发送
# 结束服务
conn.close()
# 关闭服务端
# sk.close()
并发手动实现
# 进程实现
import socket
from multiprocessing import Process
# socket.socket(socket.AF_INET,socket.SOCK_STREAM) #流式协议, TCP协议
s = socket.socket() # 同上
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(('127.0.0.1',5000))
s.listen(5)
def task(conn):
while True:
try:
data = conn.recv(1024)
except:
break
if not data:
break
print(data.decode('utf-8'))
conn.send(data.upper())
conn.close()
if __name__ == "__main__":
while True:
conn, addr = s.accept()
p = Process(target=task,args=(conn,))
p.start()
# 线程实现
import socket
from threading import Thread
# socket.socket(socket.AF_INET,socket.SOCK_STREAM) #流式协议, TCP协议
s = socket.socket() # 同上
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(('127.0.0.1',5000))
s.listen(5)
def task(conn):
while True:
try:
data = conn.recv(1024)
except:
break
if not data:
break
print(data.decode('utf-8'))
conn.send(data.upper())
conn.close()
if __name__ == "__main__":
while True:
conn, addr = s.accept()
p = Thread(target=task,args=(conn,))
p.start()
面向对象编程方法
# 多进程web服务器
import socket
import multiprocessing
import sys
class StaticWebServer(object):
# 初始化StaticWebServer对象,创建socket实例对象,并放入StaticWebServer对象当中
def __init__(self,port:int) -> None:
self.server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
self.server.bind(('',port))
self.server.listen(128)
# 启动函数
def start(self):
# 循环等待客户端连入
while True:
client,ip_port = self.server.accept()
print(ip_port[0],"通过",ip_port[1],"连接成功")
# 设置多进程,将服务分发给其他进程
p = multiprocessing.Process(target=self.task,args=(client,))
p.start()
client.close()
# 关闭字符套接字
self.server.close
# 处理函数
def task(self,client):
# 设置接收请求,且请求最大字节为1024
request_data = client.recv(1024).decode()
# 字符判断如果是0直接退出
if len(request_data) == 0:
client.close()
# 将请求的位置进行截取出
else:
request_path = request_data.split(' ')[1]
print('请求的位置是',request_path)
# 字符判断如果是"/"根目录,则path为'/index.html'
if request_path == '/':
request_path = '/index.html'
# 尝试打开文件返回,打开成功执行else,失败执行except,最后执行finally
try:
with open('static' + request_path , 'rb') as file:
file_content = file.read()
except Exception as e:
response_line = 'HTTP/1.1 404 NOT FOUND\r\n'
response_head = 'Server: PSWS1.1\r\n'
with open('static/erroe.html','r',encoding="utf-8") as f:
error_data = f.read()
response_data = (response_line + response_head + '\r\n' + error_data).encode('utf-8')
client.send(response_data)
else:
response_line = 'HTTP/1.1 200 OK\r\n'
response_head = 'Server: PSWS1.1\r\n'
with open('static' + request_path , 'rb') as f:
response_body = f.read()
response_data = (response_line + response_head + '\r\n').encode('utf-8') + response_body
client.send(response_data)
finally:
client.close()
if __name__ == "__main__":
# 用sys库给port赋值,第0个是执行的文件,取第1个赋值给port
if len(sys.argv) != 2:
print('输入错误')
exit(1)
if sys.argv[1].isdigit():
port = int(sys.argv[1])
StaticWebServer(port).start()
else:
print('输入错误')
# 多线程web服务器
import socket
import threading
class ScoketServer(object):
def __init__(self,port) -> None:
# 创建socket实例,参数1代表IPv4,参数2代表tcp
self.tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,程序退出端口号立即释放
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 对socket指定连接对象,一个人对象是一个元组包含IP地址和端口号,空IP为本地
self.tcp_server_socket.bind(("",port))
# 设置监听,最大等待128个,设置listen后为被动模式,不能connect主动连接
self.tcp_server_socket.listen(128)
# 客户端的启动函数
def start(self):
self.service_client_socket.accept()
# 循环等到客户端的连接请求
while True:
# 等待客户端的连接
service_client_socket,ip_port = self.tcp_server_socket.accept()
print("客户端连接上来",ip_port)
# 创建子线程,将连接上来的客户端放入,子线程操作接收客户端的数据
# 设置守护主线程
sub_thread = threading.Thread(target=self.handle_client_request,args=(service_client_socket,ip_port),daemon=True)
# 开启子线程
sub_thread.start()
# 关闭tcp服务器的套接字
tcp_server_socket.close()
# 处理函数,当客户端连接上后进行交互
def handle_client_request(self,service_client_socket,ip_port):
# 循环接收客户端发送的请求
while True:
# 接收请求,且请求最大为1024
recv_data = service_client_socket.recv(1024)
# 判断客户端是否存活,使用if语句对数据进行判断,有数据存活,没有数据死亡
if recv_data:
print(recv_data.decode('gbk'),ip_port)
service_client_socket.send("ok,问题正在处理...".encode("gbk"))
else:
print("客户端下线",ip_port)
break
# 终止客户端的连接
service_client_socket.close()
if __name__ == "__main__":
# 携带数据,实例一个ScoketServer
server = ScoketServer(8080)
# 运行ScoketServer下的start函数
server.start()
并发socketserver
import socketserver
class RequestHandle(socketserver.BaseRequestHandler):
def handle(self): # 这个函数的名字不能改变
# 这个是一个连接对象,针对于tcp服务,udp没有连接
print(self.request) # self.request => conn
print(self.client_address)
# 数据传输
while True:
try:
data = self.request.recv(1024)
except:
break
if not data:
break
data = data.decode('utf-8')
print('客户端发送的数据',data)
self.request.send(data.upper().encode('utf-8'))
# 结束服务
self.request.close()
'''
Threading线程的意思
Forking进程的意思
windwos系统上不支持多进程,因为创建多进程需要调用系统接口
进程调用的是 os.fork()这个接口,而os.fork()是针对unix系统发起的造进程的系统调用
windows系统多进程调用的不是这几个接口。
'''
sk = socketserver.ThreadingTCPServer(('127.0.0.1',5000),RequestHandle) # 多线程
sk.serve_forever() # => while True: conn,addr = sk.accept() 只负责从半连接池中获取对象,每获取一个对象就会启动一个线程
并发非阻塞IO手动
import socket
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False) # 所有的网络阻塞都会变成非阻塞
c_list = []
d_lis = []
while True:
try:
conn, addr = server.accept()
c_list.append(conn)
except BlockingIOError:
for conn in c_list:
try:
data = conn.recv(1024)
if not data:
conn.close()
d_lis.append(conn)
conn.send(data.upper())
except BlockingIOError:
pass
except ConnectionResetError:
conn.close()
d_lis.append(conn)
for conn in d_lis:
c_list.remove(conn)
d_lis.clear()
并发selectors
import socket
import selectors
def accept(server):
conn,addr = server.accept()
sel.register(conn,selectors.EVENT_READ,read)
def read(conn):
try:
data = conn.recv(1024)
if not data:
conn.close()
sel.unregister(conn)
return
conn.send(data.upper())
except ConnectionResetError:
conn.close()
sel.unregister(conn)
return
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
sel = selectors.DefaultSelector()
sel.register(server,selectors.EVENT_READ,accept)
while True:
events = sel.select() # linux/mac sel.epoll()
for key,mask in events:
callback = key.data
callback(key.fileobj)
并发异步IO手动
import socket
import asyncio
async def waiter(conn,loop):
while True:
try:
data = await loop.sock_recv(conn,1024)
if not data:
break
await loop.sock_sendall(conn,data.upper())
except ConnectionResetError:
break
conn.close()
async def main(ip,port):
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind((ip,port))
server.listen(5)
server.setblocking(False) # 非阻塞
loop = asyncio.get_running_loop()
while True:
conn, addr = await loop.sock_accept(server)
# 创建task任务
loop.create_task(waiter(conn,loop))
asyncio.run(main('127.0.0.1',8000))
并发异步IOuvloop
import socket
import asyncio
# import uvloop # 目前不支持windows
# asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # 但是将asyncio修改为uvloop只需要添加这一行代码即可
# pip install uvloop
async def waiter(conn,loop):
while True:
try:
data = await loop.sock_recv(conn,1024)
if not data:
break
await loop.sock_sendall(conn,data.upper())
except ConnectionResetError:
break
conn.close()
async def main(ip,port):
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind((ip,port))
server.listen(5)
server.setblocking(False) # 非阻塞
loop = asyncio.get_running_loop()
while True:
conn, addr = await loop.sock_accept(server)
# 创建task任务
loop.create_task(waiter(conn,loop))
asyncio.run(main('127.0.0.1',8000))
tcp客户端
v1
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 建立连接
sk.connect(('127.0.0.1', 5000))
# 传输数据
msg = input('请输入>>').strip()
sk.send(msg.encode('utf-8'))
data = sk.recv(1024) # 接收
print('客户端发送的数据:',data.decode('utf-8'))
# 关闭连接
sk.close()
v2
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 建立连接
sk.connect(('127.0.0.1', 5000))
# 传输数据
while True:
msg = input('请输入>>').strip()
sk.send(msg.encode('utf-8'))
if msg == 'q':
break
data = sk.recv(1024) # 接收
print('客户端发送的数据:',data.decode('utf-8'))
# 关闭连接
sk.close()
v3
import socket
# 创建socket对象
sk = socket.socket() # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 建立连接
sk.connect(('127.0.0.1', 5000))
# 传输数据
while True:
msg = input('请输入>>').strip()
sk.send(msg.encode('utf-8'))
# 防止发空
if not msg:
continue
if msg == 'q':
break
data = sk.recv(1024) # 接收
print('客户端发送的数据:',data.decode('utf-8'))
# 关闭连接
sk.close()
udp服务器
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议(udp协议)
# 每次都会重用绑定好的ip+端口,使用后不会出现端口占用问题,在不熟悉套接字之前不要使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址
sk.bind(('127.0.0.1', 5000))
# 取出连接请求,开始服务
# 数据传输
while True:
data, addr = sk.recvfrom(1024) # 接收
print('客户端发送的数据:',data.decode())
sk.sendto(data.upper(),addr) # 发送
# 关闭服务端
# sk.close()
udp客户端
import socket
# 创建socket对象
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_STREAM tcp协议 SOCK_DGRAM 数据报协议
# 传输数据
while True:
msg = input('请输入>>').strip()
sk.sendto(msg.encode('utf-8'),('127.0.0.1',5000))
if msg == 'q':
break
data, addr = sk.recvfrom(1024) # 接收
print('客户端发送的数据:',data.decode('utf-8'))
# 关闭连接
sk.close()
为什么要引入会话管理?
首先,HTTP协议是一个“无状态”的协议,所以服务器并不能自动区分出前后两次请求是否来自同一个客户端(或用户)。但是,很多业务场景,必须要区分出来访者的身份。因此,服务器端必须要建立一套会话管理机制,来解决“http协议无状态的问题”。
Session
定义:是指在服务器端存储(并区分)当前访问客户端(非用户哦)会话的一种机制。
流程:
- 当客户端第一次请求服务器时,服务器端会生成一个sessionid(一般是文件形式存储)作为该客户端的唯一标识,并通过在响应头中(ResponseHeaders)下发Set-Cookie,通知浏览器需要保存该标识。
- 当客户端第N次(N>=2)请求服务器时,会在请求头中(RequestHeaders)自动携带这个Cookie:sessionid=xx
- 当服务器再次接收客户端请求时,会先去请求头中检查是否存在Cookie:sessionid=xx,如果没有Cookie或者 Cookie过期,就提示用户”必须登录后才能访问“,从而实现(并区分)有状态的会话管理。
通过以上知识,我们能够清楚的知道以下4个经典面试题的答案:
- 如果浏览器cookie被禁用,那么session机制也会同时失效;
- 如果相同的用户账号,利用不同浏览器登录系统,那么Session并不相同
- 如果关闭浏览器,Session会话并不会立即关闭(因为只有当session在服务器端过期时,服务器才会去删除session文件本身)
- 如果web服务器做了多台负载均衡机制,那么当某个请求被路由到另一台业务均衡服务器时,Session也会丢失。
集群会话管理
众所周知,单个服务器的负载上限是一个固定值。传统Session机制只适合单机版应用,一旦项目用户数量达到一定规模,服务器集群改造方案就会变得刻不容缓。
集群环境下的会话管理,目前业界有两种主流解决方案:
-
方案1:提供session共享机制
- 原理:不再用文件存储session,而是用第三方中间件(比如redis)来管理session
- 优点:项目改造成本较小(因为代码风格跟传统是一脉相承)
- 缺点:依赖于集中式中间件,一旦中间件本身性能出问题,就会立刻导致系统瓶颈。
-
方案2:token令牌方案
-
原理:
- token 由服务器本身根据算法生成后下发给客户端,服务器端无需额外存储。
- 客户端请求服务器时,在请求头中追加携带该token
- 服务器端对token进行验签,从而决定本次访问是拒绝还是放行。
-
优点:不依赖任何集中式中间件(完全依赖服务器算力解决)
-
缺点:token一旦下发,有效期就已经确定,不能像Session一样可以在服务器端随时中止会话。
-
cookie
-
Cookie 的两个作用( 适用场景):
- 记录用户身份
用户A第一次访问a网站,a网站会返回给用户A一个sessionid,这样A每次访问a网站就会带上这个会话 - 记录用户历史
假设a是一个购物网站,用户B把B1,B2商品加入购物车,过了几天后,B再次打开网站,发现商品仍然会在购物车中(因为浏览器不会轻易删除cookie)
- 记录用户身份
-
cookie 的属性
Cookie 有很多配置属性,比较关键的有3个:- Path:是指服务器资源路径(而不是指客户端存放的路径!!!)注:发送请求时,如果访问子路径,会自动携带父路径的Cookie,而访问父路径,并不会自动携带子路径的Cookie
- HttpOnly:在cookie上设置HttpOnly标识,浏览器就会知道该cookie只能由服务器获取,而客户端本身js脚本的访问都会被禁止,从而提升系统安全性。
- Secure:在cookie上设置Secure标识,那么就只有在使用 https 协议的时候自动携带 Cookie。从而提升系统安全性。
-
cookie 的限制
cookie是由浏览器管理的,很多浏览器为了自身性能会添加一些限制:- 一般情况下,单个站点,最多允许保存20个cookie;
- 一般情况下,单个cookie,最多允许保存4K数据;

浙公网安备 33010602011771号