【网络与并发编程】

网络与并发编程

  • 1. 网络编程
    • 1.1 网络基础知识
      • 1.1.1 什么是网络
      • 1.2.3 网络功能
      • 1.2.3 网络分类
      • 1.2.4 网络性能衡量指标
      • 1.2.5 网络编程中的几个关键概念
      • 1.2.6 网络通信要解决的问题
      • 1.2.7 网络通信协议
      • 1.1.8 网络通信标准
      • 1.1.9 通信地址
    • 1.2 UDP 传输方法
      • 1.2.1 套接字简介
      • 1.2.2 UDP套接字编程
      • 1.2.3 UDP套接字特点
    • 1.3 TCP 传输方法
      • 1.3.1 TCP通信过程
      • 1.3.2 TCP服务端
      • 1.3.3 TCP客户端
      • 1.3.4 TCP套接字细节
      • 1.3.5 TCP与UDP对比
    • 1.4 数据传输过程
      • 1.4.1 传输流程
      • 1.4.2 TCP协议首部信息
  • 2. 多任务编程
    • 2.1 进程(Process)
      • 2.1.1 进程概述
      • 2.1.2 多进程编程
      • 2.1.3 进程相关函数
      • 2.1.4 创建进程类
      • 2.1.5 进程间通信
    • 2.2 线程 (Thread)
      • 2.2.1 线程概述
      • 2.2.2 多线程编程
      • 2.2.3 创建线程类
      • 2.2.4 线程互斥锁
      • 2.2.5 GIL问题
      • 2.2.6 进程线程的区别联系
  • 3. 网络并发模型
    • 3.1 网络并发模型概述
    • 3.2 多进程/线程并发模型
  • 4. web服务
    • 4.1 HTTP协议
      • 4.1.1 协议概述
      • 4.1.2 网页访问流程
      • 4.1.2 HTTP请求
      • 4.1.3 HTTP响应
  • 5. 高并发技术探讨
    • 5.1 高并发问题
    • 5.2 更高并发的实现

1. 网络编程

今天我们处在互联网非常发达的时代,绝大多数程序无法离开网络运行。掌握网络编程技术是程序员必备的专业技能。

1.1 网络基础知识

互联网(又译作因特网)是Internet的中文译名,它的前身是20世纪60年代末美国国防部高级研究计划局(ARPA)主持研制的ARPAnet。1974年,出现了连接分组网络的协议,其中就包括了TCP/IP——著名的网际互联协议IP和传输控制协议TCP。这两个协议相互配合,其中,IP是基本的通信协议,TCP是帮助IP实现可靠传输的协议。1983年,ARPAnet分成两部分:一部分军用,称为MILNET;另一部分仍称ARPAnet,供民用。Internet的发展引起了商家的极大兴趣。1992年,美国IBM、MCI、MERIT三 家公司联合组建了一个高级网络服务公司(ANS),建立了一个新的网络,叫做ANSnet,成为Internet的另一个主干网。它与NSFnet不 同,NSFnet是由国家出资建立的,而ANSnet则是ANS 公司所有,从而使Internet开始走向商业化。

1.1.1 什么是网络

  • 网络的定义:将多个节点通过特定的介质联系起来的一种关系,例如:铁路网、交通网、人际关系网
  • 计算机网络:以计算设备作为节点,通信线路作为介质的网络
  • 英特网:把全世界许多网络连到一起的网络

1.2.3 网络功能

  • 数据与信息的传输
  • 实现资源共享
  • 打破时空限制,优化资源配置

1.2.3 网络分类

按照范围

  • 局域网:局域网(Local Area Network,简写做LAN)自然就是局部地区形成的一个区域网络,其特点就是分布地区范围有限,可大可小,大到一栋建筑楼 与相邻建筑之间的连接,小到可以是办公室之间的联系。局域网自身相对其他网络传输速度更快,性能更稳定,框架简易,并且是封闭性。
  • 城域网:城域网(Metropolitan Area Network)是在一个城市范围内所建立的计算机通信网,简称MAN,可以理解为一种大型的LAN。
  • 广域网:广域网(英语:Wide Area Network,缩写为 WAN),又称外网、公网。是连接不同地区局域网或城域网计算机通信的远程网。通常跨接很大的物理范围,所覆盖的范围从几十公里到几千公里,它能连接多个地区、城市和国家,或横跨几个洲并能提供远距离通信,形成国际性的远程网络。

按照使用者

  • 公用网 (public network) :开放性网络,互联互通,比如:互联网,教育网等,挂载公共网络上电脑,容易被侵入
  • 专用网 (private network) :封闭性网络,用专线连接各个子网,比如:军队专网、政府专网、公司内部网络,防止外部侵入

1.2.4 网络性能衡量指标

  • 带宽:通信信道支持的最高数据频率(Mb/s, kb/s, Gb/s)
  • 传输速率:每秒传输多少个bit数据
  • 吞吐量:单位时间内通过某个网络的数据量
  • 时延
    • 传输时延:发送数据时候,到完成发送
    • 传播时延:电磁波、电信号传输需花费的时间
    • 处理时延:网络数据交换节点存储、转发所必需的处理时间
    • 排队时延:网络节点队列分组、排队所经历的时间

1.2.5 网络编程中的几个关键概念

  • 客户端:请求服务的一方
  • 服务器:提供服务的一方
  • 通信:数据传输过程
  • 协议:数据组织、编码、传输、校验、解码的规则

1.2.6 网络通信要解决的问题

客户端服务器
如何找到通信对方如何让对方找到自己
如何联系对方如何让对方联系自己
如何正确传输数据如何正确传输数据
如何让对方理解自己的意思如何让对方理解自己的意思
如何结束对话如何结束对话

1.2.7 网络通信协议

1)生活中的协议

在这里插入图片描述

2)网络通信协议

  • 是一组规则,对数据组织、发送、传输、解析、校验纠错的规则
  • 由第三方机构事先制定(中间组织、头部企业等),或通信双方约定
  • 需要通信各方共同遵守,否则就无法完成正常通信

在这里插入图片描述

1.1.8 网络通信标准

1)OSI七层参考模型

在网络技术发展早期,不同硬件、软件、网络厂商都开发了自己的通信标准,导致出现了互不兼容的情况。为了更好地促进互联网络的研究和发展,国际标准化组织ISO制定了网络互连的七层框架的一个参考模型,称为开放系统互连参考模型,简称OSI/RM(Open System Internetwork Reference Model)。 OSI参考模型是一个具有7层协议结构的开放系统互连模型,是由国际标准化组织在20世纪80年代早期制定的一套普遍适用的规范集合,使全球范围的计算机可进行开放式通信。

在这里插入图片描述

每一层的功能及数据形态如下表所示:

名称功能数据形态
应用层用户与网络接口,应用功能字节或字符
表示层数据编码的表示方式问题,进程间数据标准
会话层进程-进程会话管理
传输层进程-进程通信数据段/报文
网络层广域网主机-主机通信数据包/数据分组
数据链路局域网主机-主机通信数据帧
物理层物理、机械及电气标准BIT流

OSI模型的优点:

  • 建立了统一的通信标准
  • 降低开发难度,每层功能明确,各司其职
  • 七层模型实际规定了每一层的任务,该完成什么事情

OSI模型的缺点:

  • 复杂,分层过细
  • 只定义了概念,没有具体实现 (只有图纸,没有完成施工)

2)TCP/IP模型

ISO制定的OSI参考模型是理想化的模型,但是它过于庞大、复杂招致了许多批评。与此对照,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。因此现在TCP/IP协议已经称为Internet事实上的工业标准。并衍生出了TCP/IP模型指导实际的开发工作。

在这里插入图片描述

3)数据传输过程

  • 发送端由应用层逐层根据协议添加首部信息,最终在物理层实现发送
  • 发送的消息经过中间多个节点转发到达目标主机
  • 目标主机根据协议逐层解析首部,最终到达应用层获取数据

在这里插入图片描述

1.1.9 通信地址

  • IP地址 : 即在网络中标识一台计算机的地址编号

  • IP地址分类

    • IPv4 :192.168.1.5
    • IPv6 :fe80::80a:76cf:ab11:2d73
  • IPv4 特点

    • 分为4个部分,每部分是一个整数,取值分为0-255
  • IPv6 特点(了解)

    • 分为8个部分,每部分4个16进制数,如果出现连续的数字 0 则可以用 ::省略中间的0
  • IP地址相关命令

    • ifconfig : 查看Linux系统下计算机的IP地址

      在这里插入图片描述

    • ping [ip]:查看计算机的连通性

      在这里插入图片描述

  • 公网IP和内网IP

    • 公网IP指的是连接到互联网上的公共IP地址,大家都可以访问。(将来进公司,公司会申请公网IP作为网络项目的被访问地址)
    • 内网IP指的是一个局域网络范围内由网络设备分配的IP地址。
  • 端口号

    • 什么是端口号:用来区分同一台机器上,不同的服务(或应用程序)

    • 端口号的取值范围: 0~65535 的整数,不能重复。通常 0~1023 的端口会被一些有名的程序或者系统服务占用,个人一般使用 大于1024的端口

1.2 UDP 传输方法

UDP(User Datagram Protocol)用户数据报协议,是一种快速、高效、可靠性较低的传输数据协议。其特点有:

  • 无连接协议:在数据发送前,不需要进行连接,发送方直接将数据发给接收方
  • 无确认:发送方发送出数据后,接收方是否正确接受,接收方不应答响应信息
  • 无流量控制:吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制
  • 传输可靠性较低:由于面向非连接,数据收发没有确认机制,所以可能造成数据丢失
  • 传输效率较高:因为面向非连接,可靠性控制策略较少,所传输效率较高

UDP协议适合发送小尺寸数据(如对DNS服务器进行IP地址查询时)在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)适合于广播/组播式通信中。MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议。

1.2.1 套接字简介

  • 套接字(Socket) : 实现网络编程进行数据传输的一种技术手段,网络上各种各样的网络服务大多都是基于 Socket 来完成通信的

  • Python套接字编程模块:import socket

1.2.2 UDP套接字编程

  • 创建套接字
sockfd=socket.socket(family,type)
"""功能:创建套接字参数:family  网络地址类型 AF_INET表示ipv4type  套接字类型 SOCK_DGRAM 表示udp套接字 (也叫数据报套接字) 返回值: 套接字对象
"""
  • 绑定地址
    • 本地地址 : ‘localhost’ , ‘127.0.0.1’
    • 网络地址 : ‘172.40.91.185’ (通过ifconfig查看)
    • 自动获取地址: ‘0.0.0.0’
sockfd.bind(addr)
"""功能: 绑定本机网络地址参数: 二元元组 (ip,port)  ('0.0.0.0',8888)
"""
  • 消息收发
data,addr = sockfd.recvfrom(buffersize)
"""功能: 接收UDP消息参数: 每次最多接收多少字节返回值: data  接收到的内容addr  消息发送方地址n = sockfd.sendto(data,addr)功能: 发送UDP消息参数: data  发送的内容 bytes格式addr  目标地址返回值:发送的字节数
"""
  • 关闭套接字
sockfd.close()
"""功能:关闭套接字
"""
"""
udp_server.py
udp服务端实例代码
"""
from socket import *# 创建UDP套接字
udp_socket = socket(AF_INET,SOCK_DGRAM)#  绑定地址
udp_socket.bind(("0.0.0.0",8888))while True:# 接收发送消息  data--> bytesdata,addr = udp_socket.recvfrom(1024)# if data == b"##":#     breakprint("从",addr,"收到:",data.decode("utf-8"))# 发送给刚才收到的地址udp_socket.sendto(b"Thanks",addr)# 关闭套接字
udp_socket.close()
"""
udp_client.py
udp 客户端示例
"""
from socket import *# 服务器地址
ADDR = ("127.0.0.1",8888)# 与服务端相同套接字
udp_socket = socket(AF_INET,SOCK_DGRAM)# 发送消息
while True:msg = input(">>")if not msg:breakudp_socket.sendto(msg.encode("utf-8"),ADDR)# 结束发送# if msg == "##":#     breakdata,addr = udp_socket.recvfrom(1024)print("从服务端收到:",data.decode("utf-8"))udp_socket.close()
  • 服务端客户端流程

    在这里插入图片描述

示例:

使用udp完成网络单词查询
从客户端输入单词,发送给服务端,得到单词的解释,打印出来
利用 dict 数据库下的 words表来完成

【服务器端代码】


""" 测试SQL
create database dict charset=utf8;use dict;create table words (id int primary key auto_increment,word char(30),mean varchar(512)
);insert into words(word, mean) VALUES
('hello', '你好'),
('student', '学生'),
('apple', '苹果'),
('orange', '橙子'),
('grape', '葡萄'),
('car', '小汽车'),
('banana', '香蕉'),
('dog', '狗');
"""
########################## 服务端 ###############################
from socket import *
import pymysql# 数据处理类
class Dict:def __init__(self):self.kwargs = {"host": "127.0.0.1","port": 3306,"user": "root","password": "root12345678","database": "dict","charset": "utf8"}self.connect()# 完成数据库连接def connect(self):self.db = pymysql.connect(**self.kwargs)self.cur = self.db.cursor()# 关闭def close(self):self.cur.close()self.db.close()def get_mean(self, word):sql = "select mean from words where word=%s;"self.cur.execute(sql, [word])mean = self.cur.fetchone()  # (mean,) Noneif mean:return mean[0]else:return "Not Found"# 逻辑处理 网络搭建
class QueryWord:def __init__(self, host="0.0.0.0", port=8888):self.host = hostself.port = portself.dict = Dict()self.sock = self.create_socket()def create_socket(self):sock = socket(AF_INET, SOCK_DGRAM)sock.bind((self.host, self.port))return sockdef close(self):self.sock.close()# 查找单词方法def query_word(self):while True:print("Waiting for client...")word, addr = self.sock.recvfrom(128)# 查询单词mean = self.dict.get_mean(word.decode())self.sock.sendto(mean.encode(), addr)if __name__ == '__main__':query = QueryWord()query.query_word()

【客户端端代码】

############################ 客户端代码#########################
from socket import *# 服务器地址
ADDR = ("127.0.0.1", 8888)class QueryWord:def __init__(self):self.sock = socket(type=SOCK_DGRAM)def close(self):self.sock.close()# 网络传输def recv_mean(self, word):self.sock.sendto(word.encode(), ADDR)mean, addr = self.sock.recvfrom(1024)return mean.decode()# 输入输出def query_word(self):while True:word = input("Word:")if not word:breakmean = self.recv_mean(word)print("%s : %s" % (word, mean))if __name__ == '__main__':query = QueryWord()query.query_word()  # 查单词query.close()

1.2.3 UDP套接字特点

1)优点

  • 传输过程简单,实现容易
  • 数据传输效率较高
  • 数据以数据包形式表达传输
  • 适合传输少量、可靠性要求较低的数据

2)缺点

  • 可能会出现数据丢失的情况
  • 不适合传输大量、可靠性要求较高的数据

1.3 TCP 传输方法

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,其特点有:

  • 面向连接:在通信前需要建立连接(相当于通话之前拨电话)
  • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组
  • 可靠传输:提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复
  • 数据确认与应答机制:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认
  • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片
  • 流量控制:TCP提供了流量控制机制,使得收发双方处理速度基本一致,既保证发送效率,又保证传输质量
  • 数据校验:TCP将保持它首部和数据的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发

1.3.1 TCP通信过程

  • 整体通信过程

在这里插入图片描述

  • 三次握手(建立连接)

    • 客户端向服务器发送消息报文请求连接
    • 服务器收到请求后,回复报文确定可以连接
    • 客户端收到回复,发送最终报文连接建立

    在这里插入图片描述

  • 四次挥手(断开连接)

    • 主动方发送报文请求断开连接
    • 被动方收到请求后,立即回复,表示准备断开
    • 被动方准备就绪,再次发送报文表示可以断开
    • 主动方收到确定,发送最终报文完成断开

    在这里插入图片描述

1.3.2 TCP服务端

  • 创建套接字
sockfd=socket.socket(family,type)
"""功能:创建套接字参数:family  网络地址类型 AF_INET表示ipv4type  套接字类型 SOCK_STREAM 表示TCP套接字 (也叫流式套接字) 返回值: 套接字对象
"""
  • 绑定地址 (与udp套接字相同)
  • 设置监听
sockfd.listen(n)
"""功能 : 将套接字设置为监听套接字,确定监听队列大小参数 : 监听队列大小
"""

在这里插入图片描述

  • 处理客户端连接请求
connfd,addr = sockfd.accept()
"""功能: 阻塞等待处理客户端请求返回值: connfd  客户端连接套接字addr  连接的客户端地址
"""
  • 消息收发
data = connfd.recv(buffersize)
"""功能 : 接受客户端消息参数 :每次最多接收消息的大小返回值: 接收到的内容
"""n = connfd.send(data)
"""功能 : 发送消息参数 :要发送的内容  bytes格式返回值: 发送的字节数
"""
  1. 关闭套接字:socket.close()

服务器端代码示例:

"""
TCP服务端函数示例
"""
from socket import *# 创建tcp套接字
tcp_socket = socket(AF_INET,SOCK_STREAM)# 绑定地址
tcp_socket.bind(("0.0.0.0",8888))# 设置为监听套接字
tcp_socket.listen(5)# 等待客户端连接
while True:print("Waiting for connect...")connfd,addr = tcp_socket.accept()print("Connect from",addr)# 循环收发消息  客户端退出 recv立即返回b""while True:data = connfd.recv(5)# data=b""客户端直接关闭  b"##"客户端主动告知关闭if not data or data == b'##':breakprint("收到:",data.decode())connfd.send(b"Thanks/")connfd.close()# 关闭套接字
tcp_socket.close()

1.3.3 TCP客户端

在这里插入图片描述

  • 创建TCP套接字
  • 请求连接
sockfd.connect(server_addr)
"""功能:连接服务器参数:元组  服务器地址
"""
  • 收发消息:同服务器端

  • 关闭套接字:

tcp_socket.close()

客户端代码示例:

"""
TCP套接字编程 客户端
"""
from socket import *# 服务端地址
ADDR = ("127.0.0.1",8888)tcp_socket = socket() # 默认创建TCP socket# 发起连接
tcp_socket.connect(ADDR)# 循环发送接收消息
while True:msg = input(">>")tcp_socket.send(msg.encode())# 结束发送if msg == "##":breakdata = tcp_socket.recv(1024)print("From server:",data.decode())tcp_socket.close()

【示例:】

在客户端将一张图片上传到服务端,图片自选,上传到服务端后命名为 recv.jpg。思路:

  • 客户端 获取文件内容 → 发送出去
  • 服务端 接收文件内容 → 写入磁盘

服务器端代码:

from socket import *address = ("0.0.0.0", 9999)server = socket()
server.bind(address)
server.listen(5)
print("服务器已启动:", address)sockfd, addr = server.accept()  # 接受请求f = open("recv.png", "wb")  # 二进制写模式while True:data = sockfd.recv(1024)if not data:breakelse:f.write(data)f.close()  # 关闭文件
sockfd.close()  # 关闭通信socket
server.close()  # 关闭接收服务器

客户端部分:

# 发送端
from socket import *client = socket()
client.connect(("127.0.0.1", 9999))try:f = open("dog.png", "rb")  # 二进制读模式
except:print("读取文件错误")exit()while True:data = f.read(1024)if not data:breakelse:client.send(data)  # 发送数据f.close()
client.close()

1.3.4 TCP套接字细节

  • tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。

  • tcp连接中如果一端已经不存在,仍然试图通过send向其发送数据则会产生BrokenPipeError

  • 一个服务端可以同时连接多个客户端,也能够重复被连接

  • tcp粘包问题

    • 产生原因

      • 为了解决数据再传输过程中可能产生的速度不协调问题,操作系统设置了缓冲区
      • 实际网络工作过程比较复杂,导致消息收发速度不一致
      • tcp以字节流方式进行数据传输,在接收时不区分消息边界
    • 带来的影响

      • 如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。
    • 处理方法

      • 消息格式化处理,如人为的添加消息边界,用作消息之间的分割
    • 控制发送的速度

  • 练习:

服务器端

""" :
在客户端有一些数据
data = ["Jerry  18   177","Tom  19   180","Lily  120  183"
]
从客户端向服务端发送这些数据,在服务端将这些数据分别写入到一个文件中,每个数据占一行
"""######################### 服务端 ######################
from socket import *def recv_data(connfd):f = open("student.txt", 'wt')while True:data = connfd.recv(1024)if not data:breakdata = data.decode()#print("接受到的数据:", data)msg = ""for s in data:if s == "\n":f.write(msg)f.write("\n")msg = ""else:msg += sif msg != "":f.write(msg)f.close()def main():sock = socket()sock.bind(("0.0.0.0",8888))sock.listen(3)print("服务端启动成功")connfd,addr = sock.accept()print("连接:",addr)recv_data(connfd) # 接收数据if __name__ == '__main__':main()

客户端

###################### 客户端  ################################from socket import *
from time import sleepdata = ["Jerry  18   177","Tom  19   180","Lily  120  183"
]# 发送数据  (处理粘包方法1)
# def send_data(sock):
#     for item in data:
#         sock.send(item.encode())
#         sleep(0.1) # 延迟发送
#     sock.send(b"##") #   表示发送完成# 发送数据  (处理粘包方法2)
def send_data(sock):info = '\n'.join(data)sock.send(info.encode()) # 一次性发送print("发送完毕, 发送长度:", len(info))
def main():sock = socket()sock.connect(("127.0.0.1",8888))send_data(sock) # 发送数据sock.close()if __name__ == '__main__':main()

1.3.5 TCP与UDP对比

1)传输特征

  • TCP提供可靠的数据传输,但是UDP则不保证传输的可靠性
  • TCP传输数据处理为字节流,而UDP处理为数据包形式
  • TCP传输需要建立连接才能进行数据传,效率相对较低,UDP比较自由,无需连接,效率较高

2)套接字编程区别

  • 创建的套接字类型不同
  • tcp套接字会有粘包,udp套接字有消息边界不会粘包
  • tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
  • tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom

3)使用场景

  • tcp更适合对准确性要求高,传输数据较大的场景
    • 文件传输:如下载电影,访问网页,上传照片
    • 邮件收发
    • 点对点数据传输:如点对点聊天,登录请求,远程访问,发红包
  • udp更适合对可靠性要求没有那么高,传输方式比较自由的场景
    • 视频流的传输: 如部分直播,视频聊天等
    • 广播:如网络广播,群发消息
    • 实时传输:如游戏画面
  • 在一个大型的项目中,可能既涉及到TCP网络又有UDP网络
练习:
完成一个对话小程序,客户端可以发送问题给服务端,服务端接收到问题将对应答案给客户端,客户端打印出来
要求可以同时多个客户端提问,如果问题没有指定答案,则回答 “人家还小,不知道。”注意: 不需要使用数据库文件存储应答内容,在服务端用字典表示关键字和答案之间的对应关系即可
{"key":"value"}
key: 几岁
value : 我2岁啦################ 服务端 ############################
from socket import *# 对话字典
chat = {"你好":"你好啊!","叫什么":"我叫小美","男生女生":"我是机器人啦","你几岁":"我2岁啦"
}def handle(connfd):# q 客户端问题q = connfd.recv(1024).decode()for key,value in chat.items():if key in q:connfd.send(value.encode())breakelse:connfd.send("人家还小不知道啦。".encode())def main():sock = socket()sock.bind(("0.0.0.0",8888))sock.listen(5)# 循环处理对话while True:connfd,addr = sock.accept()handle(connfd) # 接收问题回答问题connfd.close()if __name__ == '__main__':main()#######################  客户端  ###############################
from socket import *# 服务器地址
ADDR = ("127.0.0.1",8888)def chat(msg):sock = socket()sock.connect(ADDR)sock.send(msg.encode())result = sock.recv(1024)sock.close()return result.decode()# 创建套接字
def main():while True:msg = input("我:")if not msg:breakresult = chat(msg)print("小美:",result)if __name__ == '__main__':main()

1.4 数据传输过程

1.4.1 传输流程

  • 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包
  • 发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机
  • 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息

在这里插入图片描述

1.4.2 TCP协议首部信息

在这里插入图片描述

  • 源端口和目的端口 各占2个字节,分别写入源端口和目的端口。

  • 序号 占4字节。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。例如,一报文段的序号是301,而接待的数据共有100字节。这就表明本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。

  • 确认号 占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。

  • 确认ACK(ACKnowledgment) 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。

  • 同步SYN(SYNchronization) 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。

  • 终止FIN(FINis,意思是“完”“终”) 用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。

2. 多任务编程

现今的操作系统大多为多任务的系统,即一个系统中可以同时运行多个任务,这样才能充分发挥硬件性能,提升计算机处理能力和吞吐量。多任务编程,就是编写一个包含多个任务同时运行(宏观上)的程序,这样就能大幅度提高系统处理能力。

  • 串行执行:多个任务先后执行,前面的任务执行完成后后面的任务再执行
  • 并行执行:多个任务同时执行(例如多核CPU)
  • 并发执行:多个任务宏观上同时执行,微观上分时间片(或时间段)执行,在操作系统中利用多进程、多线程方式实现

在这里插入图片描述

2.1 进程(Process)

2.1.1 进程概述

进程(Process)是操作系统中一个极其重要的概念,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。简单来讲,进程指程序在计算机中的一次执行过程,是一个正则运行的程序。

为了更好对操作系统进行研究、分析、设计、管理,60年代初首先由麻省理工学院的MULTICS系统和IBM公司的CTSS/360系统引入了进程的概念。

1)进程的特点

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
  • 结构特征:进程由程序、数据和进程控制块三部分组成

多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变

2)进程和程序的区别

  • 程序是一个可执行的文件,是静态的占有磁盘
  • 进程是一个动态的过程描述,占有计算机运行资源,是一个独立的运行单元,有一定的生命周期

3)进程状态

进程状态是描述一个进程“从生到死”的过程,描述进程生命周期的变化过程。进程状态包括:就绪状态(Ready)、运行状态(Running)、阻塞状态(Blocked)

  • 就绪状态:进程已获得除处理器(即CPU)外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行(可以理解为在医院挂了号,等待医生诊断)
  • 运行状态:正在处理器上执行(可以理解为正在接受医生诊断状态)
  • 阻塞状态:由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行(可以理解为,等待检查报告,拿到报告后才能进行下一步诊断)

在这里插入图片描述

进程状态发生变化,称为“进程状态转换”,进程状态转换是由一些操作系统事件引起的,进程状态转换有以下几种情况:

  • 就绪→执行:处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态
  • 执行→就绪:处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态
  • 执行→阻塞:正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等
  • 阻塞→就绪:处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态

注意:阻塞状态不能直接转换到运行状态!

4)进程结构

进程有程序段、数据段、进程控制块(Processing Control Block,简写PCB)三部分组成,如下图所示:

在这里插入图片描述

其中,PCB中记录了最重要的进程控制信息,用于操作系统对进程进行管理和调度,它是系统中的一个内存区域,存放操作系统用于描述进程情况及控制程序运行所需的全部信息。如下图所示:

在这里插入图片描述

5)进程管理命令

  • 查看进程信息

    ps 命令""" 参数说明:ps -a 显示现行终端机下的所有程序,包括其他用户的程序。ps -A 显示所有程序。
    ps -c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
    ps -e 列出程序时,显示每个程序所使用的环境变量。
    ps -f 用ASCII字符显示树状结构,表达程序间的相互关系。
    ps -H 显示树状结构,表示程序间的相互关系。
    ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。
    ps -s 采用程序信号的格式显示程序状况。
    ps -S 列出程序时,包括已中断的子程序资料。
    ps -u 以用户为主的格式来显示程序状况。
    ps -x 显示所有程序,不以终端机来区分
    """
    

    在这里插入图片描述

    • USER : 进程的创建者
    • PID : 操作系统分配给进程的编号,大于0的整数,系统中每个进程的PID都不重复。PID也是重要的区分进程的标志。
    • %CPU,%MEM : 占有的CPU和内存
    • STAT : 进程状态信息,S表示阻塞状态 ,R 表示运行状态,Z表示僵尸进程,s包含子进程,I表示多线程,<表示优先级高,N表示优先级低
    • START : 进程启动时间
    • COMMAND : 通过什么程序启动的进程
    • TIME:用掉的CPU时间
    • VSZ:虚拟内存大小,这是linux分配给进程的内存大小,但是这并不一定意味着这个进程使用了所有的内存
    • RSS:驻留集大小(Resident Set Size),这是进程当前加载其所有页面的内存大小
  • 结束进程
kill  -9  pid   #用于杀死一个进程

2.1.2 多进程编程

  • 使用模块 : multiprocessing
  • 创建流程
    • 将需要新进程执行的事件封装为函数
    • 通过模块的Process类创建进程对象,关联函数
    • 通过进程对象调用start启动进程
  • 主要类和函数使用
Process()
"""功能 : 创建进程对象参数 : target 绑定要执行的目标函数 args 元组,用于给target函数位置传参kwargs 字典,给target函数键值传参daemon  bool值,让子进程随父进程退出
"""
p.start()
"""功能 : 启动进程
"""

注意 : 启动进程此时target绑定函数开始执行,该函数作为新进程执行内容,此时进程真正被创建

p.join([timeout])
"""功能:阻塞等待子进程退出参数:最长等待时间
"""

示例:创建进程

"""
进程创建示例 01
"""
import multiprocessing as mp
from time import sleepa = 1  # 全局变量# 进程目标函数
def fun():print("子进程: 开始运行一个进程")sleep(4)  # 模拟事件执行事件global aprint("子进程: a =", a)  # Yesa = 10000print("子进程: 进程执行结束")# 实例化进程对象
process = mp.Process(target=fun)# 启动进程  进程产生 执行fun
process.start()print("主进程:我也做点事情")
sleep(3)
print("主进程:我也把事情做完了...")process.join()  # 阻塞等待子进程结束
print("主进程  a:", a)  # 1  10000

示例:创建进程,并向进程传参

"""
进程创建示例02 : 含有参数的进程函数
"""
from multiprocessing import Process
from time import sleep# 含有参数的进程函数
def worker(sec,name):for i in range(3):print("sec:", sec)sleep(sec)print("I'm %s"%name)print("I'm working....")# 元组位置传参
# p = Process(target=worker,args=(2,"Tom"))# 关键字传参
p = Process(target=worker,args = (2,),kwargs={"name":"Tom"},daemon=True) # 守护进程标志,守护进程指退出时要终止所有子进程
p.start()
p.join()
  • 进程执行现象理解

    • 新的进程是原有进程的子进程,子进程复制父进程全部内存空间,一个进程可以创建多个子进程。
    • 子进程只执行指定的函数,执行完毕子进程生命周期结束,但是子进程也拥有其他父进程资源。
    • 各个进程在执行上互不影响,也没有必然的先后顺序关系。
    • 进程创建后,各个进程空间独立,相互没有影响。
    • multiprocessing 创建的子进程中无法使用标准输入(即无法使用input)。

2.1.3 进程相关函数

  • 进程相关函数
os.getpid()
"""功能: 获取一个进程的PID值返回值: 返回当前进程的PID 
"""os.getppid()
"""功能: 获取父进程的PID号返回值: 返回父进程PID
"""

示例:创建多个子进程

"""
创建多个子进程
"""
from multiprocessing import Process
from time import sleep
import sys, osdef worker1():sleep(3)print("我是worker1, pid=", os.getppid(), " ppid=", os.getppid())def worker2():sleep(1)print("我是worker2, pid=", os.getppid(), " ppid=", os.getppid())def worker3():sleep(2)print("我是worker3, pid=", os.getppid(), " ppid=", os.getppid())# 循环创建子进程
jobs = []  # 存放每个进程对象
for th in [worker1, worker2, worker3]:p = Process(target=th)jobs.append(p)  # 存入jobsp.start()#  确保三件事都结束
for i in jobs:i.join()print("子进程执行完成, 主进程退出.")

2.1.4 创建进程类

进程的基本创建方法将子进程执行的内容封装为函数。如果我们更热衷于面向对象的编程思想,也可以使用类来封装进程内容。

  • 创建步骤

    【1】 继承Process类

    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性

    【3】 重写run()方法

  • 使用方法

    【1】 实例化对象

    【2】 调用start自动执行run方法

    """
    自定义进程类
    """
    from multiprocessing import Process
    from time import sleepclass MyProcess(Process):def __init__(self, value):self.value = valuesuper().__init__()  # 调用父类的init# 重写run 作为进程的执行内容def run(self):for i in range(self.value):sleep(2)print("自定义进程类。。。。")p = MyProcess(3)
    p.start() # 将 run方法作为进程执行
    

2.1.5 进程间通信

进程间通信指在不同进程间传递数据。进程的用户空间是互相独立的,一般而言是不能互相访问的,进程间通信需要遵循特定的方式。常用进程间通信方法:消息队列,网络套接字等。此处介绍消息队列进程间通信方式。

消息队列,是在内存中开辟空间,建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。

消息队列的主要操作有:

from multiprocessing import Queueq = Queue(maxsize=0)
# 功能: 创建队列对象
# 参数:最多存放消息个数
# 返回值:队列对象q.put(data)
# 功能:向队列存入消息
# 参数:data  要存入的内容q.get()
# 功能:从队列取出消息
# 返回值: 返回获取到的内容q.full()   # 判断队列是否为满
q.empty()  # 判断队列是否为空
q.qsize()  # 获取队列中消息个数

消息队列进程间通信示例:

from multiprocessing import *# 生产者(向队列中添加数据)
def producer(queue):for i in range(10):queue.put("Producer:" + str(i))# 消费者(从队列中取数据)
def consumer(queue):while True:item = queue.get()if item is None:breakprint(item)if __name__ == '__main__':queue = Queue()p1 = Process(target=producer, args=(queue,))p2 = Process(target=consumer, args=(queue,))p1.start()p2.start()p1.join()queue.put(None)p2.join()

2.2 线程 (Thread)

2.2.1 线程概述

  • 线程被称为轻量级的进程,也是多任务编程方式
  • 线程可以理解为进程中再开辟的分支任务
  • 线程也是一个运行行为,消耗计算机资源
  • 一个进程中的所有线程共享这个进程的资源
  • 多个线程之间的运行同样互不影响各自运行
  • 线程的创建和销毁过程给计算机带来的压力远小于进程

在这里插入图片描述

2.2.2 多线程编程

线程模块的用法几乎和进程一模一样,完全可以仿照完成。

  • 线程模块: threading

在这里插入图片描述

  • 线程主要操作
from threading import Thread t = Thread()
"""
功能:创建线程对象
参数:target 绑定线程函数args   元组 给线程函数位置传参kwargs 字典 给线程函数键值传参daemon bool值,主线程推出时该分支线程也推出
"""t.start()
"""启动线程
"""t.join([timeout])
"""
功能:阻塞等待分支线程退出
参数:最长等待时间
"""

示例:简单多线程示例

# 线程示例01:import threading
from time import sleep
import osa = 1#  线程函数
def music():global aprint("a =",a)a = 10000for i in range(3):sleep(2)print(os.getpid(),"播放:黄河大合唱")# 实例化线程对象
thread = threading.Thread(target=music)
# 启动线程 线程存在
thread.start()for i in range(4):sleep(1)print(os.getpid(),"播放:葫芦娃")# 阻塞等待分支线程结束
thread.join()
print("a:",a)

示例:包含参数传输的多线程示例

# 线程示例02:from threading import Thread
from time import sleep# 带有参数的线程函数
def func(sec,name):print("含有参数的线程来喽")sleep(sec)print("%s 线程执行完毕"%name)# 循环创建线程
for i in range(5):t = Thread(target=func,args=(2,),kwargs={"name":"T-%d"%i},daemon=True)t.start()

2.2.3 创建线程类

  1. 创建步骤

    【1】 继承Thread类

    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性

    【3】 重写run()方法

  2. 使用方法

    【1】 实例化对象

    【2】 调用start自动执行run方法

    from threading import Thread
    from time import sleepclass MyThread(Thread):def __init__(self,song):self.song = songsuper().__init__() # 得到父类内容# 线程要做的事情def run(self):for i in range(3):sleep(2)print("播放:",self.song)t = MyThread("让我们荡起双桨")
    t.start() # 运行run
    

2.2.4 线程互斥锁

  • 线程通信方法: 线程间使用全局变量进行通信

  • 共享资源争夺

    • 共享资源:多线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
    • 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要互斥机制协调。
  • 互斥锁机制

    当一个线程占有资源时会进行加锁处理,此时其他线程就无法操作该资源,直到解锁后才能操作。

在这里插入图片描述

  • 线程锁 Lock
from  threading import Locklock = Lock()  # 创建锁对象
lock.acquire() # 上锁  如果lock已经上锁再调用会阻塞
lock.release() # 解锁

线程锁示例:

# Lock使用示例:from threading import Thread, Locklock = Lock() # 创建锁
a = b = 0def value():while True:lock.acquire() # 上锁if a != b:print("a = %d,b = %d" % (a, b))lock.release() # 解锁t = Thread(target=value)
t.start()while True:lock.acquire()a += 1b += 1lock.release()
  • 线程锁的注意问题:死锁问题
    在这里插入图片描述
死锁产生条件* 互斥条件:即使用了互斥锁。* 请求和保持条件:锁住一定资源不解锁的情况先再请求锁住其他资源。* 不剥夺条件:不会受到线程外部的干扰,终止锁行为。* 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如 T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。

2.2.5 GIL问题

  • 什么是GIL问题 (全局解释器锁)

    由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。

  • 导致后果
    因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升。

  • 关于GIL问题的处理

    • 来自Guido:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235

    • 尽量使用进程完成无阻塞的多任务情况

    • 不使用c作为解释器 (可以用Java C#)

  • 结论
    • GIL问题与Python语言本身并没什么关系,属于解释器设计的历史问题。
    • 在无阻塞状态下,多线程程序程序执行效率并不高,甚至还不如单线程效率。
    • Python多线程只适用于执行有阻塞延迟的任务情形。

2.2.6 进程线程的区别联系

1)区别联系

  • 两者都是多任务编程方式
  • 进程的创建销毁过程给计算机带来的压力比线程多
  • 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
  • 一个进程可以有多个分支线程,两者有包含关系
  • 多个线程共享进程资源,在共享资源操作时往往需要互斥锁处理
  • Python线程存在GIL问题,但是进程没有。

2)使用场景

  • 任务场景:一个大型服务,往往包含多个独立的任务模块,每个任务模块又有多个小独立任务构成,此时整个项目可能有多个进程,每个进程又有多个线程。
  • 编程语言:Java,C#之类的编程语言在执行多任务时一般都是用线程完成,因为线程资源消耗少;而Python由于GIL问题往往使用多进程。

3. 网络并发模型

3.1 网络并发模型概述

  • 什么是网络并发

    在实际工作中,一个服务端程序往往要应对多个客户端同时发起访问的情况。如果让服务端程序能够更好的同时满足更多客户端网络请求的情形,这就是并发网络模型。

    在这里插入图片描述

  • 循环网络模型问题

    循环网络模型只能循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。这样的网络模型虽然简单,资源占用不多,但是无法同时处理多个客户端请求就是其最大的弊端,往往只有在一些低频的小请求任务中才会使用。

3.2 多进程/线程并发模型

多进程/线程并发模中每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程,多任务并发模型也是实际工作中最为常用的服务端处理模型。

  • 模型特点

    • 优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。
    • 缺点: 资源消耗较大
    • 适用情况:客户端请求较复杂,需要长时间占有服务器。
  • 创建流程

    • 创建网络套接字
    • 等待客户端连接
    • 有客户端连接,则创建新的进程/线程具体处理客户端请求
    • 主进程/线程继续等待处理其他客户端连接
    • 如果客户端退出,则销毁对应的进程/线程

多进程并发模型示例:

# 多进程并发模型示例:"""
基于多进程的网络并发模型创建tcp套接字
等待客户端连接
有客户端连接,则创建新的进程具体处理客户端请求
父进程继续等待处理其他客户端连接
如果客户端退出,则销毁对应的进程
"""
from socket import *
from multiprocessing import Process
import sys# 地址变量
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)# 处理客户端具体请求
def handle(connfd):while True:data = connfd.recv(1024)if not data:breakprint(data.decode("utf-8"))resp_str = "收到了你的信息:" + data.decode("utf-8")connfd.send(resp_str.encode("utf-8"))connfd.close()# 服务入口函数
def main():# 创建tcp套接字tcp_socket = socket()tcp_socket.bind(ADDR)tcp_socket.listen(5)print("Listen the port %d" % PORT)# 循环连接客户端while True:try:connfd, addr = tcp_socket.accept()print("Connect from", addr)except KeyboardInterrupt:tcp_socket.close()sys.exit("服务结束")# 创建进程 处理客户端请求p = Process(target=handle, args=(connfd,), daemon=True)p.start()if __name__ == '__main__':main()

以上程序可以同时运行两个TCP_client.py,可以同时对多个客户端完成信息收发。

多线程并发示例:

# 多线程并发模型示例:
"""
基于多线程的网络并发模型
思路: 网络构建    线程搭建    /   具体处理请求
"""
from socket import *
from threading import Thread# 处理客户端具体请求
class Handle:# 具体处理请求函数 (逻辑处理,数据处理)def request(self, data):print(data)# 创建线程得到请求
class ThreadServer(Thread):def __init__(self, connfd):self.connfd = connfdself.handle = Handle()super().__init__(daemon=True)# 接收客户端的请求def run(self):while True:data = self.connfd.recv(1024).decode("utf-8")if not data:breakself.handle.request(data)resp_str = "收到了你的信息:" + dataself.connfd.send(resp_str.encode("utf-8"))self.connfd.close()# 网络搭建
class ConcurrentServer:"""提供网络功能"""def __init__(self, *, host="", port=0):self.host = hostself.port = portself.address = (host, port)self.sock = self.__create_socket()def __create_socket(self):tcp_socket = socket()tcp_socket.bind(self.address)return tcp_socket# 启动服务 --> 准备连接客户端def serve_forever(self):self.sock.listen(5)print("Listen the port %d" % self.port)while True:connfd, addr = self.sock.accept()print("Connect from", addr)# 创建线程t = ThreadServer(connfd)t.start()if __name__ == '__main__':server = ConcurrentServer(host="0.0.0.0", port=8888)server.serve_forever()  # 启动服务

以上程序可以同时运行两个TCP_client.py,可以同时对多个客户端完成信息收发。

4. web服务

4.1 HTTP协议

4.1.1 协议概述

  • 用途 : 网页获取,数据的传输
  • 特点
    • 应用层协议,使用tcp进行数据传输
    • 简单,灵活,很多语言都有HTTP专门接口
    • 有丰富的请求类型
    • 可以传输的数据类型众多

4.1.2 网页访问流程

  1. 客户端(浏览器)通过tcp传输,发送http请求给服务端

  2. 服务端接收到http请求后进行解析

  3. 服务端处理请求内容,组织响应内容

  4. 服务端将响应内容以http响应格式发送给浏览器

  5. 浏览器接收到响应内容,解析展示

    在这里插入图片描述

4.1.2 HTTP请求

  • 请求行 : 具体的请求类别和请求内容
	GET         /        HTTP/1.1请求类别   请求内容     协议版本
	  请求类别:每个请求类别表示要做不同的事情 
    GET : 获取网络资源POST :提交一定的信息,得到反馈HEAD : 只获取网络资源的响应头PUT : 更新服务器资源DELETE : 删除服务器资源
  • 请求头:对请求的进一步解释和描述
	Accept-Encoding: gzip
  • 空行
  • 请求体: 请求参数或者提交内容

在这里插入图片描述

4.1.3 HTTP响应

  • 响应行 : 反馈基本的响应情况
    HTTP/1.1     200       OK版本信息    响应码   附加信息
响应码 : 
    1xx  提示信息,表示请求被接收2xx  响应成功3xx  响应需要进一步操作,重定向4xx  客户端错误5xx  服务器错误
  • 响应头:对响应内容的描述
    Content-Type: text/html
  • 空行
  • 响应体:响应的主体内容信息

在这里插入图片描述

HTTP服务器示例一:

# HTTP协议示例:
"""
http请求和响应 演示
"""
from socket import *sock = socket()
sock.bind(("0.0.0.0",8000))
sock.listen(5)# 等待浏览器连接
connfd,addr = sock.accept()
print("Connect from",addr)# 接收HTTP请求
request = connfd.recv(1024)
print(request.decode())# 组织响应
response = """HTTP/1.1 200 OK
Content-Type:text/htmlhello world
"""
connfd.send(response.encode())connfd.close()
sock.close()

以上代码在浏览器中输入http://127.0.0.1:8000,返回页面中显示“hello world”

HTTP服务器示例二:

# 随堂练习:将网页 一个图片 通过浏览器访问显示出来
# 提示 : Content-Type:image/jpegfrom socket import *# 处理http请求
def handle(connfd):# 接收http请求request = connfd.recv(1024).decode()if not request:return# 组织响应response = "HTTP/1.1 200 OK\r\n"response += "Content-Type:image/jpeg\r\n"response += "\r\n"with open("abc.jpeg",'rb') as f:response =response.encode() +  f.read()connfd.send(response # 发送响应def main():sock = socket()sock.bind(("0.0.0.0", 8000))sock.listen(5)# 等待浏览器连接while True:connfd, addr = sock.accept()print("Connect from", addr)handle(connfd) # 处理请求connfd.close()if __name__ == '__main__':main()

以上代码在浏览器中输入http://127.0.0.1:8000,返回页面中显示一张图片。

5. 高并发技术探讨

5.1 高并发问题

  • 衡量高并发的关键指标

    • 响应时间(Response Time) : 接收请求后处理的时间

    • 同时在线用户数量:同时连接服务器的用户的数量

    • 每秒查询率QPS(Query Per Second): 每秒接收请求的次数

    • 每秒事务处理量TPS(Transaction Per Second):每秒处理请求的次数(包含接收,处理,响应)

    • 吞吐量(Throughput): 响应时间+QPS+同时在线用户数量

  • 多大的并发量算是高并发

    • 没有最高,只要更高

      比如在一个小公司可能QPS2000+就不错了,在一个需要频繁访问的门户网站可能要达到QPS5W+

    • C10K问题

      早先服务器都是单纯基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程占用操作系统资源多,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么单机而言操作系统是无法承受的,这就是著名的C10k问题。创建的进程线程多了,数据拷贝频繁, 进程/线程切换消耗大, 导致操作系统崩溃,这就是C10K问题的本质!

5.2 更高并发的实现

为了解决C10K问题,现在高并发的实现已经是一个更加综合的架构艺术。涉及到进程线程编程,IO处理,数据库处理,缓存,队列,负载均衡等等,这些我们在后面的阶段还会学习。此外还有硬件的设计,服务器集群的部署,服务器负载,网络流量的处理等。

在这里插入图片描述

实际工作中,应对更庞大的任务场景,网络并发模型的使用有时也并不单一。比如多进程网络并发中每个进程再开辟线程,或者在每个进程中也可以使用多路复用的IO处理方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/14142.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【全开源】智能名片系统源码(Fastadmin+ThinkPHP和Uniapp)

数字时代的新名片&#xff0c;连接未来的桥梁 引言 在数字化浪潮的推动下&#xff0c;传统名片已经逐渐淡出人们的视线。取而代之的是智能名片系统&#xff0c;它以其高效、便捷和智能化的特点&#xff0c;成为了商务交流的新宠。而智能名片系统源码&#xff0c;作为其核心驱…

SAP销售手工发票录入

销售手工发票录入用于处理未启用 SD 模块标准处理流程的零星销售业务。 科目设置 收入类科目&#xff1a;设置税务类型&#xff0c;允许含税/不含税过账应收账款: 留空。其他应收款的设置类似 编辑选项设置 在中国&#xff0c;编辑选项一般设置为基于总额计税。使用事务码 FB…

操作系统课程实验3-可变分区存储管理

操作系统课程实验3-可变分区存储管理 一、实验介绍 1.1 实验目的 加深对可变分区存储管理的理解&#xff1b;提高用C语言编制大型系统程序的能力&#xff0c;特别是掌握C语言编程的难点&#xff1a;指针和指针作为函数参数&#xff1b;掌握用指针实现链表和在链表上的基本操作…

如何设计足够可靠的分布式缓存体系,以满足大中型移动互联网系统的需要?no.31

传统 CAP 的突破 随着分布式系统的不断演进&#xff0c;会不断遇到各种问题&#xff0c;特别是当前&#xff0c;在大中型互联网系统的演进中&#xff0c;私有云、公有云并行发展且相互融合&#xff0c;互联网系统的部署早已突破单个区域&#xff0c;系统拓扑走向全国乃至全球的…

新书推荐:6.2 else if语句

本节必须掌握的知识点&#xff1a; 示例代码二十 代码分析 汇编解析 ■if语句表达形式3 if(表达式1) statement1 else if(表达式2) statement2 else if(表达式3) statement3 …… else statementN 解析&#xff1a; 如果表达式1非0&#xff0c;则执行statement1&#…

记录github小程序短视频系统的搭建过程

GitHub - lkmc2/AwesomeVideoWxApp: 《倾心短视频》微信小程序 这个项目按readme中的来可以部署成功&#xff0c;但是会发现图片、视频全是空的&#xff0c;如下图&#xff1a; 修改源代码&#xff0c;更换图片上传与保存地址 大概涉及到这些代码块&#xff0c;进行更改即可。…

什么样的数据摆渡设备,可以满足不同网间数据的安全传输需求?

数据摆渡设备是用来在不同的网络环境间安全地传输数据的硬件或软件解决方案。它们通常用于确保在具有不同安全级别的网络&#xff08;如内网和外网&#xff09;之间进行数据交换时的安全性和合规性。以下是一些常见的数据摆渡设备和方法&#xff1a; 移动介质拷贝&#xff1a;使…

生产制造边角料核算说明及ODOO演示

今天群里有伙伴提到边角料的处理问题&#xff0c;我们梳理了一下&#xff0c;在生产过程中&#xff0c;如果产生了边角料&#xff0c;核算产成品的投料成本时需要考虑边角料的价值&#xff0c;以确保成本核算的准确性。以下是注意的几点&#xff1a; 一、边角料的入账价值 在生…

OSPF路由聚合

原理概述 与RIP不同&#xff0c;OSPF不支持自动路由聚合&#xff0c;仅支持手动路由聚合。OSPF的路由聚合有两种机制&#xff1a;区域间路由聚合和外部路由聚合。区域间路由聚合必须配置在ABR路由器上&#xff0c;指的是ABR在把与自己直接相连区域&#xff08;Area&#xff09…

K8s 二进制部署---下篇(多master节点 负载均衡 高可用)

一 master02 节点部署 master01192.168.11.5kube-apiserver&#xff0c;kube-controller-manager&#xff0c;kube-scheduler&#xff0c;etcdmaster02192.168.11.12kube-apiserver&#xff0c;kube-controller-manager&#xff0c;kube-scheduler&#xff0c;etcdnode01192.1…

RHEL7及之后系统 系统服务脚本(Rocky 9.4)

目录 源码安装 准备工作 步骤1: 下载软件 步骤2: 安装apr 步骤3: 安装apr-util 步骤4: 安装Apache HTTP Server 总结步骤 后续步骤 源码安装 准备环境&#xff1a;首先&#xff0c;确保你的系统中安装了必要的编译工具和依赖库。对于C/C程序&#xff0c;这通常包括编译器&#…

一阶数字高通滤波器

本文的主要内容包含一阶高通滤波器公式的推导和数字算法的实现以及编程和仿真 1 计算公式推导 1.1.2 算法实现及仿真 利用python实现的代码如下&#xff1a; import numpy as np # from scipy.signal import butter, lfilter, freqz import matplotlib.pyplot as plt #2pifW…

从原理上解决 uniapp (含第三方插件)打包 iOS APP 失败的问题

最近一段时间&#xff0c;我的团队基于uniapp开发的平台型APP因平台资金合规的要求&#xff0c;需要对接中金支付&#xff0c;uniapp的插件市场有一个别人做好的中金支付插件&#xff0c;但前端开发同事在引用这个 插件时&#xff0c;出现了 iOS APP 打包不成功的情况&#xff…

Pantera 合伙人简谈 Morpho:更高效、适应性更强的 DeFi 解决方案

原文标题&#xff1a;《Pioneering Peer-to-Peer Lending in the DeFi Revolution》撰文&#xff1a;Pantera Capital 合伙人 Paul Veradittakit编译&#xff1a;Chris&#xff0c;Techub News 文章来源&#xff1a;香港Web3媒体Techub News Morpho 正在超越 Compound 等传统…

Redis主从、哨兵、cluster集群的部署和细节

目录 1. 主从模式 为什么需要主从&#xff1f; 搭建主从架构 2. Sentinel(哨兵)模式 为什么需要哨兵模式&#xff1f; 搭建哨兵集群 哨兵集群 Go语言编程redis哨兵模式 有了哨兵&#xff0c;客户端连接谁&#xff1f; test1&#xff1a;redis节点主从切换 test2&am…

webgl入门-矩阵变换

矩阵变换 前言 变换有三种状态&#xff1a;平移、旋转、缩放。 当我们变换一个图形时&#xff0c;实际上就是在移动这个图形的所有顶点。 课堂目标 掌握图形变换的三种方式。可以对图像进行复合变换。 知识点 平移旋转缩放 第一章 平移 对图形的平移就是对图形所有顶点…

如何快速从手动测试转向自动化测试

寻求具有无缝持续集成和持续交付 (CI/CD) 的高效 DevOps 管道比以往任何时候都更加重要。想象一下这样一个场景&#xff1a;您的软件组织显著减少了人工工作量、降低了成本&#xff0c;并更加自信地发布了软件更新。换句话说&#xff0c;通过将 Web UI 和 API 测试结合在一起&a…

【小白课程】如何在openKylin上个性化定制开关机动画

开关机动画是Linux系统的重要组成部分&#xff0c;其主要功能是在Linux内核启动的早期遮盖内核打印日志&#xff0c;并在内核刷新屏幕分辨率时保证屏幕显示的流畅性。 其中&#xff0c;openKylin操作系统使用plymouth组件作为开关机动画显示程序。Linux系统在启动时&#xff0…

计算机SCI期刊,中科院2区,收稿范围非常广泛!

一、期刊名称 Journal of Web Semantics 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;计算机科学 影响因子&#xff1a;2.5 中科院分区&#xff1a;2区 出版方式&#xff1a;开放出版 版面费&#xff1a;$1600 三、期刊征稿范围 《网络语义学杂志》…

【软件测试】5.测试用例

目录 1.测试用例 1.1概念 1.2测试的要素 2.测试用例的万能公式 2.1常规思考逆向思维发散性思维 2.2万能公式 2.2.1功能测试 2.2.2界面测试 2.2.3性能测试 2.2.4兼容性测试 2.2.5易用性测试 2.2.6安全测试 2.3弱网测试 1.测试用例 1.1概念 什么是测试用例&#xf…