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

定义:是指在服务器端存储(并区分)当前访问客户端(非用户哦)会话的一种机制。
流程:

  1. 当客户端第一次请求服务器时,服务器端会生成一个sessionid(一般是文件形式存储)作为该客户端的唯一标识,并通过在响应头中(ResponseHeaders)下发Set-Cookie,通知浏览器需要保存该标识。
  2. 当客户端第N次(N>=2)请求服务器时,会在请求头中(RequestHeaders)自动携带这个Cookie:sessionid=xx
  3. 当服务器再次接收客户端请求时,会先去请求头中检查是否存在Cookie:sessionid=xx,如果没有Cookie或者 Cookie过期,就提示用户”必须登录后才能访问“,从而实现(并区分)有状态的会话管理。

通过以上知识,我们能够清楚的知道以下4个经典面试题的答案:

  1. 如果浏览器cookie被禁用,那么session机制也会同时失效;
  2. 如果相同的用户账号,利用不同浏览器登录系统,那么Session并不相同
  3. 如果关闭浏览器,Session会话并不会立即关闭(因为只有当session在服务器端过期时,服务器才会去删除session文件本身)
  4. 如果web服务器做了多台负载均衡机制,那么当某个请求被路由到另一台业务均衡服务器时,Session也会丢失。

集群会话管理

众所周知,单个服务器的负载上限是一个固定值。传统Session机制只适合单机版应用,一旦项目用户数量达到一定规模,服务器集群改造方案就会变得刻不容缓。

集群环境下的会话管理,目前业界有两种主流解决方案:

  1. 方案1:提供session共享机制

    • 原理:不再用文件存储session,而是用第三方中间件(比如redis)来管理session
    • 优点:项目改造成本较小(因为代码风格跟传统是一脉相承)
    • 缺点:依赖于集中式中间件,一旦中间件本身性能出问题,就会立刻导致系统瓶颈。
  2. 方案2:token令牌方案

    • 原理:

      • token 由服务器本身根据算法生成后下发给客户端,服务器端无需额外存储。
      • 客户端请求服务器时,在请求头中追加携带该token
      • 服务器端对token进行验签,从而决定本次访问是拒绝还是放行。
    • 优点:不依赖任何集中式中间件(完全依赖服务器算力解决)

    • 缺点:token一旦下发,有效期就已经确定,不能像Session一样可以在服务器端随时中止会话。

  1. Cookie 的两个作用( 适用场景):

    • 记录用户身份
      用户A第一次访问a网站,a网站会返回给用户A一个sessionid,这样A每次访问a网站就会带上这个会话
    • 记录用户历史
      假设a是一个购物网站,用户B把B1,B2商品加入购物车,过了几天后,B再次打开网站,发现商品仍然会在购物车中(因为浏览器不会轻易删除cookie)
  2. cookie 的属性
    Cookie 有很多配置属性,比较关键的有3个:

    • Path:是指服务器资源路径(而不是指客户端存放的路径!!!)注:发送请求时,如果访问子路径,会自动携带父路径的Cookie,而访问父路径,并不会自动携带子路径的Cookie
    • HttpOnly:在cookie上设置HttpOnly标识,浏览器就会知道该cookie只能由服务器获取,而客户端本身js脚本的访问都会被禁止,从而提升系统安全性。
    • Secure:在cookie上设置Secure标识,那么就只有在使用 https 协议的时候自动携带 Cookie。从而提升系统安全性。
  3. cookie 的限制
    cookie是由浏览器管理的,很多浏览器为了自身性能会添加一些限制:

    • 一般情况下,单个站点,最多允许保存20个cookie;
    • 一般情况下,单个cookie,最多允许保存4K数据;
posted @ 2025-03-27 12:34  *--_-  阅读(32)  评论(0)    收藏  举报