资源简介 单元9 网络编程与进程控制课程名称 Python程序设计任务驱动式教程 章名 网络编程与进程控制教学内容 网络编程与进程控制 课时 6项目性质 □演示性 □验证性 □设计性 √综合性授课班级 授课日期 授课地点教学目标 了解TCP/IP、UDP、进程等基础知识 了解Socket()方法、Socket对象 掌握创建TCP服务器程序与客户端程序 掌握创建UDP服务器程序和客户端程序 掌握创建与使用进程 掌握创建与使用线程教学内容 认知Socket 创建TCP服务器程序与客户端程序 创建UDP服务器程序和客户端程序 创建与使用进程 创建与使用线程教学重点 创建TCP服务器程序与客户端程序教学难点 创建TCP服务器程序与客户端程序教学准备 装有Python的计算机 教学课件PPT 教材:《Python程序设计任务驱动式教程(微课版)》作业设计18教学过程教学环节 教学内容与过程 (教学内容、教学方法、组织形式、教学手段)课前组织 做好上课前的各项准备工作(打开计算机、打开课件、打开软件、打开授课计划、教案等),吸引学生注意力。课程说明 【课前说明】 分别从TCP/IP、UDP、进程等知识点进行初步的了解。 【目的】 使学生从了解本节课的学习目标、学习重点、考评方式等方面明确课程学习的要求和目标。课程内容描述 9.1 认知Socket Python提供了两个级别的网络服务:低级别的网络服务支持基本的Socket,它提供了标准的BSD Sockets API,可以访问操作系统底层Socket接口的全部方法;高级别的网络服务支持模块SocketServer,它提供了服务器中心类,可以简化网络服务器的开发过程。 Socket又称“套接字”,网络应用程序通常通过套接字向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通信。套接字用于描述IP地址和端口,可以用来实现不同计算机之间的通信。Internet上的主机中一般运行了多个软件,同时提供多种服务,每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应不同的服务。Socket通信模型如图所示。 Socket通信模型 1.socket()方法 Python中,使用socket模块的socket()方法来创建套接字,基本语法格式如下。 socket.socket( [Addressfamily[, type [ protocol=0 ]]] ) 参数说明如下。 Addressfamily:Internet进程间通信使用AF_INET,同一台机器进程间通信使用AF_UNIX,通常使用AF_INET。 type:套接字类型,可以根据是面向连接还是非连接分为SOCK_STREAM(主要用于TCP)和SOCK_DGRAM(主要用于UDP)。 protocol:该参数一般不赋值,默认值为0。 例如,创建TCP/IP套接字,可以采用以下形式。 tcpSock=socket.socket(socket.AF_INET , socket.SOCK_STREAM ) 创建UDP\IP套接字,可以采用以下形式。 udpSock=socket.socket(socket.AF_INET , socket.SOCK_DGRAM ) 使用socket()方法创建Socket,创建完成后生成Socket对象。 2.Socket对象的主要方法 (1)Socket对象公共用途的套接字方法。 Socket对象公共用途的套接字方法如电子活页9-1所示。 【实例9-1】演示socket模块的gethostname()、gethostbyname()方法的应用 实例9-1的代码如下所示。 import socket #导入socket模块 hostname = socket.gethostname() #获取本地主机名 print("计算机名称为:"+hostname ) hostip = socket.gethostbyname(hostname) print("计算机IP地址为:" + hostip ) hostname = "www." hostip = socket.gethostbyname(hostname) print("www.网站的IP地址为:"+hostip ) hostname = "www." hostip = socket.gethostbyname(hostname) print("www.网站的IP地址为:"+hostip) 实例9-1的运行结果如下。 计算机名称为:MS-201705281819 计算机IP地址为:192.168.1.4 www.网站的IP地址为:183.60.141.1 www.网站的IP地址为:14.215.177.39 (2)服务器端的套接字方法。 服务器端的套接字方法如表所示。 服务器端的套接字方法 方法说明bind()绑定地址到套接字,在AF_INET下,以元组(host,port)的形式表示地址listen()开始TCP监听accept()被动接受TCP客户端连接(阻塞式),等待连接的到来(3)客户端的套接字方法。 客户端的套接字方法如表所示。 客户端的套接字方法 方法说明connect(address)主动初始化TCP服务器连接,一般address的格式为元组(hostname , port),如果连接出错,返回socket.error错误connect_ex()connect()的扩展版本,出错时返回出错码,而不是抛出异常【实例9-2】演示Socket对象的使用,通过客户端向浏览器和本地服务器发起请求,服务器接到请求,向浏览器发送“I wish you good health.”文本内容 实例9-2的代码如下所示。 import socket #导入socket模块 host = "127.0.0.1" #主机IP port = 8080 #端口号 sock = socket.socket() #创建Socket对象 sock.bind((host,port)) #绑定端口 sock.listen(5) #设置最多连接数 print ("服务器等待客户端连接……") #开启死循环 while True: conn,addr = sock.accept() #建立客户端连接 data = conn.recv(1024) #获取客户端请求数据 print(data) #输出接收到的数据 conn.sendall(b"HTTP/1.1 200 OK\r\n\r\n I wish you good health.") conn.close() #关闭连接 实例9-2的程序代码开始运行后,在浏览器地址栏中输入127.0.0.1:8080,即服务器IP地址为127.0.0.1,端口号为8080,成功连接服务器后,浏览器中显示文本内容“I wish you good health.”。实例9-2的运行结果如图所示。 实例9-2的运行结果 浏览器中显示的文本内容如图所示。 浏览器中显示的文本内容 实例9-2的程序代码中调用sendall()方法向浏览器发送数据的格式如下。 conn.sendall(b"HTTP/1.1 200 OK\r\n\r\n I wish you good health.") 其中,“ HTTP/1.1 200 OK”为HTTP的响应信息,数据格式必须完整,“\r\n\r\n”不能缺少,“I wish you good health.”为用户定义的字符串,即向浏览器发送的文本内容。 “HTTP/1.1”表示当前使用的协议为HTTP,协议的版本为1.1。“200”表示成功。 只需要发送相应格式的数据,就可以在客户端的浏览器中显示了,而数据格式应该类似于“b"HTTP/1.1 200 OK\r\n\r\n I wish you good health."”。 9.2 创建TCP服务器程序与客户端程序 1.创建TCP服务器 由于TCP连接具有安全、可靠的特性,因此TCP程序被广泛应用。在TCP程序中,实现TCP服务器功能的流程如下。 (1)导入socket模块。 (2)使用socket()方法创建一个套接字对象。 (3)使用bind()方法绑定IP地址和端口。 (4)使用listen()方法监听端口,进入侦听过程。 (5)使用accept()方法等待并接收来自客户端的连接请求。 (6)使用recv()方法读取客户端的数据,使用send()方法向客户端发送数据。 (7)调用close()方法关闭连接,释放Socket连接所占用的资源。 2.创建TCP客户端 创建TCP客户端程序比较简单,对一个客户端程序而言,实现客户端功能的流程如下。 (1)导入socket模块。 (2)调用socket()方法生成一个Socket对象。 (3)通过connect()方法连接服务器程序,当客户端连接到服务器后,一直等待的服务进程会被唤醒,并处理此连接。 (4)使用send()方法向服务器发送数据。 (5)客户端直接调用recv()方法获取服务器发送过来的数据。 (6)调用close()方法将连接关闭。 根据服务器和客户端的执行流程,可以总结出TCP客户端和服务器之间的通信模型,如图9-6所示。 3.字符串在网络中的传输 网络上读写的数据本质上是连续的二进制数据流,要把字符串进行网络传输,就必须把字符串转换为二进制数据流写入网络,然后在网络的另一端读取二进制数据流并把它反向变回字符串数据即可。 网络中要传输一个字符串时必须告知对方该字符串有多少字节,后面跟着该字符串的字节数据。根据这一原理,编写通过Socket套接字在网络中写字符串的函数writeString()和从网络中读出一个字符串的函数readString(),代码如下所示。 def readString(socket): size=struct.calcsize("@i") info=socket.recv(size) unp=struct.unpack("@i",info)[0] data=socket.recv(unp) return data.decode("utf-8") def writeString(socket,str): data=str.encode("utf-8") size=len(data) pac=struct.pack("@i",size) socket.send(pac) socket.send(data) TCP客户端和服务器之间的通信模型 要发送字符串就必须先规定字符串的格式,字符串的格式如下所示。 整数字符串的二进制数据即使用一个整数来引导字符串,整数表示字符串的字节数。 读取格式化字符串时先读取一个整数,然后按这个整数指示的数目读出二进制数据,接着再把这个二进制数据转换为字符串。 写字符串时先把字符串转换为二进制数据,然后写这个字符串的长度整数,再写字符串的二进制数据。读字符串时先计算整数的字节数size,然后从网络中读取size个字节,把它转换为整数n即字符串的长度,接着再读取n个字节,并且把读取的二进制数据转换为字符串输出。 【实例9-3】演示TCP客户端与服务器之间发送与接收数据的实现方法 实例9-3包括TCP服务器程序文件p9-3server.py和TCP客户端程序文件p9-3client.py,TCP服务器程序文件p9-3server.py的代码如下所示。 import socket #导入socket模块 host = socket.gethostname() #获取本地主机名 port = 8080 #端口号 #创建 Socket对象 serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversock.bind((host,port)) #绑定端口 serversock.listen(5) #设置最多连接数 print ("服务器等待客户端连接……") #开启死循环 while True: clientsocket,addr = serversock.accept() #建立客户端连接 print("当前连接地址为:",str(addr)) data = clientsocket.recv(1024) #获取客户端请求数据 print(data) #输出接收到的数据 msg="Nice to hear from you" clientsocket.sendall(msg.encode('utf-8')) clientsocket.close() #关闭连接 p9-3server.py中代码的第一行导入了socket模块,这是任何使用套接字对象都需要的。接着调用socket模块中的socket()方法来生成Socket对象。socket.socket(socket.AF_INET, socket.SOCK_STREAM)也可以简写为以下形式。 socket.socket() 在生成套接字对象后,通过调用bind()方法来绑定一个套接字地址,这里bind()方法的参数为(host,port)。host为当前主机地址,调用gethostname()方法获取;port为8080。 接着使用listen()方法来使服务器进行侦听,listen()方法有一个参数,用来设置最多的连接数。 之后使用while循环,由于条件表达式设置为True,使得服务器进入死循环。也就是说,服务器将一直处于侦听状态。使用accept()方法可以接收客户端的一个连接,此对象返回一个元组,该元组有2个值:第1个值为生成的连接对象,可以使用该对象来发送和接收数据;第2个值为建立Socket连接的客户端地址。 接着使用sendall()方法向客户端发送一个字符串数据。最后调用close()方法关闭此连接,从而释放该Socket连接占用的资源。 TCP客户端程序文件p9-3client.py的代码如下所示。 import socket #导入socket模块 #创建TCP/IP套接字 sock= socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = socket.gethostname() #获取本地主机名 port = 8080 #设置端口号 sock.connect((host,port)) #主动初始化TCP服务器连接 sendData = input("请输入要发送的数据:") #提示用户输入数据 sock.send(sendData.encode()) #发送TCP数据 #接收对方发送过来的数据,最大接收1024个字节 recvData = sock.recv(1024) print('接收到的数据为:',recvData.decode('utf-8')) #关闭套接字 sock.close() p9-3client.py中代码第1行将socket模块导入,然后调用socket模块中的socket()方法生成一个Socket对象,并使用gethostname()方法获取服务器地址,之后再通过connect()方法连接服务器程序。当客户端连接服务器时,一直等待的服务进程会被唤醒,并处理此连接。这里的客户端直接调用recv()方法获取服务器发送过来的数据。最后调用close()方法关闭连接。 运行服务器程序和客户端程序之前,先打开2个Windows【命令提示符】窗口,第1个【命令提示符】窗口模拟TCP服务器,第2个【命令提示符】窗口模拟TCP客户端。在第1个【命令提示符】窗口的提示符“>”后输入命令“python D:\PycharmProject\Practice\Unit09\p9-3server.py”,开始运行p9-3server.py程序,此时该窗口中会出现“服务器等待客户端连接……”的提示文字。 然后在第二个【命令提示符】窗口的提示符“>”后输入命令“python D:\PycharmProject\ Practice\Unit09\p9-3client.py”,开始运行p9-3client.py程序,此时该窗口中出现“请输入要发送的数据:”提示文字,输入“Hi, hello.”后按【Enter】键,此时运行p9-3server.py程序的窗口会接收到客户端发来的数据,出现“b'Hi, hello.'”和“当前连接地址为:('192.168.1.4', 54523)”提示文字。模拟TCP服务器的【命令提示符】窗口中出现的信息如图所示。 模拟TCP服务器的【命令提示符】窗口中出现的信息 并且服务器会向客户端发送“Nice to hear from you”,客户端也会收到服务器发来的信息,模拟TCP客户端的【命令提示符】窗口中出现的信息如图所示。 模拟TCP客户端的【命令提示符】窗口中出现的信息 实例9-3中,服务器使用socket模块的socket()方法来创建一个Socket对象,Socket对象通过调用其他方法来设置一个Socket服务。可以通过调用bind方法来指定服务的端口。接着,调用Socket对象的accept()方法,该方法等待客户端的连接,并返回connection对象,表示已连接到客户端。 客户端程序连接到以上创建的服务,端口号为8080。使用socket.connect(hostname, port )方法打开一个TCP连接到主机为hostname、端口号为port的服务。连接后就可以从服务器获取数据,操作完成后需要关闭连接。 【任务9-1】实现客户端与服务器之间通信 【任务描述】 (1)在PyCharm集成开发环境中创建项目Unit09。 (2)在项目Unit09中创建9-1server.py和9-1client.py两个Python程序文件。 (3)编写程序使用Socket实现TCP服务器与客户端之间的通信,即客户端向服务器发送文字,服务器接到消息后,显示消息内容并且输入文字返回给客户端。客户端接收到响应,显示该文字内容,然后继续向服务器发送消息,这样就可以实现一个简易的聊天程序。当输入“exit”时,则退出系统,中断聊天。【任务实施】 1.创建PyCharm项目Unit09 成功启动PyCharm后,在指定位置“D:\PycharmProject\”创建PyCharm项目Unit09。 2.创建Python程序文件9-1server.py 在PyCharm项目Unit09中新建Python程序文件9-1server.py。 3.编写Python程序代码 在新建文件9-1server.py的代码编辑窗口输入程序代码,程序文件9-1server.py的代码如电子活页9-2所示。 4.创建Python程序文件9-1client.py 在PyCharm项目Unit09中新建Python程序文件9-1client.py。 5.编写Python程序代码 在新建文件9-1client.py的代码编辑窗口输入程序代码,程序文件9-1client.py的代码如电子活页9-3所示。 单击工具栏中的【保存】按钮,分别保存9-1server.py和9-1client.py两个程序文件。 6.运行Python程序 运行程序之前,先打开2个Windows【命令提示符】窗口,第1个【命令提示符】窗口模拟TCP服务器,第2个【命令提示符】窗口模拟TCP客户端。在第1个【命令提示符】窗口的提示符“>”后输入命令“python D:\PycharmProject\ Unit09\9-1server.py”,开始运行9-1server.py程序,此时该窗口中会出现“MS-201705281819 在监听…”的提示文字。 然后在第2个【命令提示符】窗口的提示符“>”后输入命令“python D:\Pycharm Project\Unit09\9-1client.py”,开始运行9-1client.py程序,此时该窗口中出现“按任意键开始连接服务器...”提示文字,按【Enter】键或其他任意键后第1个【命令提示符】窗口出现“连接已经建立”的提示文字,第2个【命令提示符】窗口出现“成功连接服务器”的提示文字。 在第2个【命令提示符】窗口提示文字“客户端请求的内容:”后输入“Hi, hello.”,再按【Enter】键,此时第1个【命令提示符】窗口中会接收到客户端发来的数据,出现“收到客户端请求的内容:Hi, hello.”的内容,程序运行时分别在模拟TCP服务器和模拟TCP客户端发送或收到的信息如表所示。 程序运行时分别在模拟TCP服务器和模拟TCP客户端发送或收到的信息 客户端收到与发送的信息服务器发送与收到的信息发送的信息Hi, hello.收到的信息Hi, hello.收到的信息Nice to hear from you发送的信息Nice to hear from you发送的信息Wishes you to be happy daily!收到的信息Wishes you to be happy daily!收到的信息Thank you! Happy every day发送的信息Thank you! Happy every day发送的信息byebye收到的信息byebye收到的信息byebye发送的信息byebye发送的信息exit收到的信息exit发送的信息exit【任务9-1】模拟TCP服务器的【命令提示符】窗口中出现的信息如图所示。 【任务9-1】模拟TCP服务器的【命令提示符】窗口中出现的信息 模拟TCP客户端的【命令提示符】窗口中出现的信息如图所示。 【任务9-1】模拟TCP客户端的【命令提示符】窗口中出现的信息 【任务9-2】TCP服务器与客户端之间传输字符串数据 【任务描述】 (1)在项目Unit09中创建9-2server.py和9-2client.py两个Python程序文件。 (2)编写程序实现服务器与客户端之间传输字符串数据,即客户端连接服务器后向服务器发送一个字符串,服务器接收到字符串后再次返回这个字符串给客户端。 【任务实施】 1.创建Python程序文件9-2server.py 在PyCharm项目Unit09中新建Python程序文件9-2server.py。 2.编写Python程序代码 在新建文件9-2server.py的代码编辑窗口输入程序代码,程序文件9-2server.py的代码如电子活页9-4所示。 3.创建Python程序文件9-2client.py 在PyCharm项目Unit09中新建Python程序文件9-2client.py。 4.编写Python程序代码 在新建文件9-2client.py的代码编辑窗口输入程序代码,程序文件9-2client.py的代码如电子活页9-5所示。 单击工具栏中的【保存】按钮,分别保存9-2server.py和9-2client.py两个程序文件。 5.运行Python程序 运行程序之前,先打开2个Windows【命令提示符】窗口,第1个【命令提示符】窗口模拟TCP服务器,第2个【命令提示符】窗口模拟TCP客户端。在第1个【命令提示符】窗口的提示符“>”后输入命令“python D:\Pycharm Project\Unit09\9-2server.py”,开始运行9-2server.py程序,此时该窗口中会出现“MS- 201705281819 在监听…”的提示文字,服务器处于监听状态。 然后在第2个【命令提示符】窗口的提示符“>”后输入命令“python D:\Pycharm Project\Unit09\9-2client.py”,开始运行9-2client.py程序,客户端将字符串“Good luck.”发送给服务器,服务器收到这个字符串,第1个【命令提示符】窗口出现“客户端连接已建立”的提示文字和传输的字符串“Good luck.”。 服务器再次把字符串“Good luck.”返回给客户端,客户端又收到该字符串,第2个【命令提示符】窗口出现“成功连接服务器”的提示文字和字符串“Good luck.”。 通信结束时,服务器和客户端都关闭套接字。 【任务9-2】模拟TCP服务器的【命令提示符】窗口中出现的信息如图所示。 【任务9-2】模拟TCP服务器的【命令提示符】窗口中出现的信息 模拟TCP客户端的【命令提示符】窗口中出现的信息如图所示。 【任务9-2】模拟TCP客户端的【命令提示符】窗口中出现的信息 9.3 创建UDP服务器程序和客户端程序 UDP是面向消息的协议。如果通信时不需要建立连接、数据传输的可靠性要求不强,那么UDP便可以满足要求。UDP一般应用于多点通信和实时的数据业务,例如语音广播、聊天软件、简单网络管理协议(SNMP)、路由信息协议(RIP)、域名系统(DNS)等。 1.创建UDP服务器 在UDP程序中,实现UDP服务器功能的流程如下。 (1)导入socket模块。 (2)使用socket()方法创建一个套接字对象,其中两个参数分别设置为socket.AF_INET和socket.SOCK_DGRAM,SOCK_DGRAM表示创建的是UDP套接字。 (3)使用recvfrom()方法接收客户端的数据,recvfrom()方法生成的data数据类型是字节类型,将其转换为字符串类型。 (4)使用sendto()方法向客户端发送数据,发送的数据必须是字节类型,需要使用encode()方法将字符串转换为字节类型。 (5)调用close()方法关闭连接,释放Socket连接所占用的资源。 2.创建UDP客户端 创建UDP客户端程序比较简单,对一个客户端程序而言,实现客户端功能的流程如下。 (1)导入socket模块。 (2)调用socket()方法生成一个Socket对象,两个参数为socket.AF_INET、socket.SOCK_DGRAM。 (3)使用sendto()方法向服务器发送数据,发送的数据必须是字节类型,需要使用encode()方法将字符串转换为字节类型。 (4)客户端直接调用recv()方法获取服务器发送过来的数据,收到的数据是字节类型,使用decode()方法将字节类型的数据转换为字符串,方便阅读。 (5)调用close()方法将连接关闭。 根据服务器和客户端的执行流程,可以总结出UDP客户端和服务器之间的通信模型,如图所示。在UDP通信模型中,在通信开始之前,不需要建立相关的连接,只需要发送数据即可。 UDP客户端和服务器之间的通信模型 【任务9-3】建立UDP通信获取客户购物数量 【任务描述】 (1)在项目Unit09中创建9-3server.py和9-3client.py两个Python程序文件。 (2)编写程序建立UDP通信获取客户购物数量,即在客户端输入购物数量,然后发送给服务器,服务器收到数据后,再发送给客户端输出。 【任务实施】 在PyCharm项目Unit09中创建Python程序文件9-3server.py。在程序文件9-3server.py中编写程序代码,实现所需功能,程序文件9-3server.py的代码如下所示。 import socket # 导入socket模块 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建UDP套接字 sock.bind(("127.0.0.1", 6688)) # 绑定地址(host,port)到套接字 print("绑定UDP到6688端口") data, addr = sock.recvfrom(1024) # 接收数据 sendData = "购买数量为:"+str(data) print(sendData) print("Received from :" , addr) sock.sendto(sendData.encode(), addr) # 发送给客户端 sock.close() # 关闭服务器套接字 程序文件9-3server.py中使用socket.socket()方法创建套接字,其中参数设置为AF_INET和SOCK_DGRAM,表明创建的是UDP套接字;recvfrom()方法生成的data数据类型是byte类型。使用sendto()方法发送数据时,发送的数据必须是字节类型,所以需要使用encode()方法将字符串转换为字节类型。 在PyCharm项目Unit09中创建Python程序文件9-3client.py。在程序文件9-3client.py中编写程序代码,实现所需功能,程序文件9-3client.py的代码如下所示。 import socket # 导入socket模块 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建UDP套接字 data = input("请输入购买数量:") sock.sendto(data.encode(), ("127.0.0.1", 6688)) # 发送数据 print(sock.recv(1024).decode()) # 打印接收的数据 sock.close() # 关闭套接字 由于接收的数据和发送的数据其类型都是字节类型,因此程序文件9-3client.py的代码中发送数据时,使用encode()方法将字符串转换为字节类型。而输出数据时,使用decode()方法将byte类型的数据转换为字符串,方便用户阅读。 运行程序之前,先打开2个Windows【命令提示符】窗口,第1个【命令提示符】窗口模拟UDP服务器,第2个【命令提示符】窗口模拟UDP客户端。在第1个【命令提示符】窗口的提示符“>”后输入命令“python D:\PycharmProject\Unit09\9-3server.py”,开始运行9-3server.py程序,此时该窗口中会出现“绑定UDP到6688端口”的提示文字。 然后在第2个【命令提示符】窗口的提示符“>”后输入命令“python D:\Pycharm Project\Unit09\ 9-3client.py”,开始运行9-3client.py程序,此时该窗口中出现“请输入购买数量:”提示文字,接着输入购买的数量,这里输入“5”,然后按【Enter】键,此时第1个【命令提示符】窗口出现“购买数量为:b'5'”和“Received from :('127.0.0.1', 50003)”两行提示文字,第2个【命令提示符】窗口出现“购买数量为:b'5'”的提示文字。 【任务9-3】模拟UDP服务器的【命令提示符】窗口中出现的信息如图所示。 【任务9-3】模拟UDP服务器的【命令提示符】窗口中出现的信息 模拟UDP客户端的【命令提示符】窗口中出现的信息如图所示。 【任务9-3】模拟UDP客户端的【命令提示符】窗口中出现的信息 9.4 创建与使用进程 Python中有多个模块可以创建进程,较常见的是使用multiprocessing模块创建进程。 9.4.1 使用multiprocessing模块的Process类创建进程 multiprocessing模块提供了Process类创建进程对象,基本语法格式如下。 Process( [group [ , target [ , name [ , args [ , kwargs ]]]]] ) Process类的参数说明如下。 group:值为None,为以后版本而保留。 target:表示当前进程启动时执行的可调用对象。 name:表示当前进程实例的别名。 args:表示传递给target的参数元组。 kwargs:表示传递给target的参数字典。 Process类的常用方法与常用属性如表所示。 Process类的常用方法与常用属性 序号方法或属性说明1is_alive()判断进程实例是否在执行2join( [ timeout ] )是否等待进程实例执行结束,或等待多少秒3start()启动进程实例(创建子进程)4run()如果没有给定target参数,对这个对象调用start()方法时,就将执行run()方法5terminate()不管任务是否完成,立即终止6name当前进程实例的别名,默认为Process-n,n为从1开始递增的整数7pid当前进程实例的PID值【实例9-4】演示使用multiprocessing模块的Process类创建进程与执行进程 实例9-4的代码如下所示。 from multiprocessing import Process # 导入模块 # 执行子进程代码 def test(interval): print("No.3我是子进程") # 执行主程序 def main(): print("No.1主进程开始") p = Process(target=test,args=(1,)) # 实例化Process进程类 p.start() # 启动子进程 print("No.2主进程结束") if __name__ == "__main__": main() 实例9-4的运行结果如下。 No.1主进程开始 No.2主进程结束 No.3我是子进程 实例9-4中的代码先实例化Process类,然后使用p.start()方法启动进程,开始执行自定义的test()函数。 9.4.2 使用Process子类创建进程 对于一些简单的任务,通常使用Process类实现多进程。但是如果要处理复杂任务的进程,通常会定义一个类,使其继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象。 【实例9-5】演示使用Process的子类创建两个子进程,分别输出父进程和子进程的PID以及每个子进程的状态和运行时间 实例9-5的代码如下所示。 from multiprocessing import Process import time import os # 继承Process类 class SubProcess(Process): # 重写了父类的__init__()初识化方法 def __init__(self,interval,name=""): Process.__init__(self) # 调用Process父类的初始化方法 self.interval = interval # 接收参数interval if name: # 判断传递的参数name是否存在 # 如果传递参数name,则为子进程创建name属性,否则使用默认属性 self.name = name #重写了Process类的run()方法 def run(self): print("No.10-"+"子进程({0})开始执行,父进程为({1})"\ .format(os.getpid(),os.getppid())) t_start = time.time() time.sleep(self.interval) t_stop = time.time() print("No.11-"+"子进程({0})执行结束,耗时{1:0.2f}秒"\ .format(os.getpid(),t_stop-t_start)) if __name__=="__main__": print("No.01------父进程开始执行-------") print("No.02-父进程PID:" , os.getpid()) # 输出当前程序的PID p1 = SubProcess(interval=1,name="NewName") p2 = SubProcess(interval=2) p1.start() # 启动进程p1 p2.start() # 启动进程p2 # 输出p1和p2进程的执行状态,如果真在进行,返回True,否则返回False print("No.03-p1.is_alive=" , p1.is_alive()) print("No.04-p2.is_alive=" , p2.is_alive()) # 输出p1和p2进程的别名和PID print("No.05-p1.name=" , p1.name) print("No.06-p1.pid=" , p1.pid) print("No.07-p2.name=" , p2.name) print("No.08-p2.pid" , p2.pid) print("No.09------等待子进程-------") p1.join() # 等待p1进程结束 p2.join() # 等待p2进程结束 print("No.12-"+"------父进程执行结束-------") 实例9-5中的代码定义了一个SubProcess子类,该子类继承multiprocessing.Process父类,SubProcess子类中定义了两个方法:__init__()初始化方法和run()方法。在__init__()初始化方法中,调用multiprocessing.Process父类的__init__()初始化方法,否则父类初始化方法会被覆盖,无法开启进程。此外,在SubProcess子类中并没有显式定义start()方法,但在主进程中却调用了start()方法,此时就会自动执行SubProcess子类的run()方法。 实例9-5的运行结果如下。 No.01------父进程开始执行------- No.02-父进程PID:1812 No.03-p1.is_alive= True No.04-p2.is_alive= True No.05-p1.name= NewName No.06-p1.pid= 6348 No.07-p2.name= SubProcess-2 No.08-p2.pid 3056 No.09------等待子进程------- No.10-子进程(6348)开始执行,父进程为(1812) No.10-子进程(3056)开始执行,父进程为(1812) No.11-子进程(6348)执行结束,耗时1.00秒 No.11-子进程(3056)执行结束,耗时2.00秒 No.12-------父进程执行结束------- 9.4.3 验证进程之间能否直接共享数据 在多进程中,每个进程都有自己的地址空间、内存、数据栈以及其他记录其运行状态的辅助数据,进程之间无法直接共享信息。 【实例9-6】验证进程之间能否直接共享数据 实例9-6中定义了1个全局变量gNum,分别创建2个子进程对该全局变量gNum执行不同的操作,并输出操作后的结果,其代码如电子活页9-6所示。 实例9-6的代码定义了1个全局变量gNum,分别创建了2个子进程,子进程1令全局变量gNum增加20,子进程2令全局变量gNum减去30。但是从运行结果可以看出,全局变量gNum在父进程和2个子进程中的初始值都是100。也就是全局变量gNum在一个进程中的结果,没有传递到下一个进程中,即进程之间没有共享数据。 实例9-6的运行结果如下。 No.01-------主进程开始------ No.02主进程开始时:i=1,gNum=100 No.03-------子进程1开始------ No.04子进程1中:i=1,gNum=120 No.05-------子进程1结束------ No.06-------子进程2开始------ No.07子进行2中:i=1,gNum=70 No.08-------子进程2结束------ No.09-------主进程结束------ No.10主进程结束时:i=1,gNum=100 Python的multiprocessing模块提供了队列(Queue)、管道(Pipes)等多种方式实现多进程之间的数据传递,从而实现进程之间的通信。使用multiprocessing.Process可以创建多进程,使用multiprocessing.Queue可以实现队列操作,结合Process和Queue就可以实现进程间的通信。 9.5 创建与使用线程 9.5.1 Python 3的多线程 多线程类似于同时执行多个不同程序,多线程运行有如下优点。 (1)使用线程可以把运行时间长的程序中的任务放到后台去处理。 (2)用户界面可以更加吸引人,例如对用户单击一个按钮去触发某些事件进行处理,可以弹出一个进度条来显示处理的进度。 (3)程序的运行速度可能加快。 (4)在一些等待的任务的实现方面(例如用户输入、文件读写和网络收发数据等),线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源,如内存占用资源等。 (5)每个独立的线程有一个程序运行的入口、顺序执行序列和程序运行的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程可以分为内核线程(由操作系统内核创建和撤销)和用户线程(不需要内核支持而在用户程序中实现的线程)。 9.5.2 Python 3支持的线程模块 Python 3通过_thread和threading模块提供对线程的支持,推荐使用threading模块。 Python 3中,thread模块已被废弃,用户可以使用threading模块代替。所以,在Python 3中不能再使用thread模块。为了兼容性,Python 3将thread重命名为“_thread”。 _thread模块提供了低级别的、原始的线程以及一个简单的锁,它的功能相比于threading模块的功能还是比较有限的。threading 模块除了包含_thread模块中的所有方法外,还提供了以下方法。 (1)threading.currentThread():用于返回当前的线程变量。 (2)threading.enumerate():用于返回一个包含正在运行线程的列表。所谓“正在运行线程”是指启动后、结束前的线程,不包括启动前和终止后的线程。 (3)threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 threading模块同样提供了Thread类来处理线程,Thread类提供了以下方法。 (1)run():用以表示线程活动的方法。 (2)start():启动线程的方法。 (3)join( [timeout] ):阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。其主要的功能就是实现多线程的线程独占,让只有一个线程运行。join()方法可用于阻塞另一个线程,让当前线程获得另一个线程的处理结果。 (4)isAlive():返回线程的活动状态。 (5)getName():返回线程名称。 (6)setName():设置线程名称。 9.5.3 Python中使用线程的方式 Python中,创建线程的方式有多种,可以使用threading模块的Thread类创建线程、使用threading模块的Thread类的子类创建线程、调用_thread模块中的start_new_thread()函数产生新线程。 1.使用threading模块的Thread类创建线程 threading模块提供了一个Thread类来创建线程,基本语法格式如下。 Thread( [group [ , target [ , name [ , args [ , kwargs ]]]]] ) Thread类的参数说明如下。 group:值为None,为以后版本而保留。 target:表示一个可调用对象,线程启动时,run()方法调用此对象,默认值为None,表示不调用任何内容。 name:表示当前线程名称,默认创建一个“Thread-n”名称的线程。 args:表示传递给target的参数元组。 kwargs:表示传递给target的参数字典。 【实例9-7】演示创建3个线程,然后分别使用for循环执行start()和join()方法,每个子线程分别执行输出2次 实例9-7的代码如下所示。 import threading,time def process(): global k for i in range(2): k+=1 time.sleep(1) print("线程名称:{0},i={1}" .format(threading.current_thread().name,k)) k=0 if __name__ == "__main__": print("-----主线程开始-----") threads = [threading.Thread(target=process) for i in range(3)] # 创建3个线程 for t in threads: t.start() # 开启线程 for t in threads: t.join() # 等待子线程结束 print("-----主线程结束-----") 实例9-7的代码运行时,某一次的运行结果如下。 -----主线程开始----- 线程名称:Thread-1,i=3 线程名称:Thread-3,i=4 线程名称:Thread-2,i=5 线程名称:Thread-1,i=6 线程名称:Thread-2,i=6 线程名称:Thread-3,i=6 -----主线程结束----- 从程序运行结果可以看出,线程的执行顺序是不确定的。 2.使用threading模块的Thread类的子类创建线程 可以通过直接从threading.Thread继承定义一个新的子类,并实例化后调用start()方法启动新线程,即它调用了线程的run()方法。 【实例9-8】演示使用threading模块的Thread类的子类创建线程的方式 实例9-8的代码如电子活页9-7所示。 实例9-8中的代码创建了一个子类SubThread,继承自threading.Thread线程类,并定义了一个run()方法,实例化SubThread类创建1个线程,并且调用start()方法开启线程,程序会自动调用run()方法。 实例9-8的代码运行时,某一次的运行结果如下。 开始线程:Thread-1 开始线程:Thread-2 5 - Thread-1 Sun Apr 19 07:06:27 2020 5 - Thread-2 Sun Apr 19 07:06:28 2020 4 - Thread-1 Sun Apr 19 07:06:28 2020 3 - Thread-1 Sun Apr 19 07:06:29 2020 4 - Thread-2 Sun Apr 19 07:06:30 2020 2 - Thread-1 Sun Apr 19 07:06:30 2020 1 - Thread-1 Sun Apr 19 07:06:31 2020 退出线程:Thread-1 3 - Thread-2 Sun Apr 19 07:06:32 2020 2 - Thread-2 Sun Apr 19 07:06:34 2020 1 - Thread-2 Sun Apr 19 07:06:36 2020 退出线程:Thread-2 退出主线程 3.调用_thread模块中的start_new_thread()函数产生新线程 调用_thread模块中的start_new_thread()函数也能产生新线程,基本语法格式如下。 _thread.start_new_thread( function , args [, kwargs] ) 参数说明如下。 function:线程函数。 args:传递给线程函数的参数,必须是元组类型。 kwargs:可选参数。 【实例9-9】演示调用_thread模块中的start_new_thread()函数产生新线程的方式 实例9-9的代码如下所示。 import _thread import time # 为线程定义一个函数 def print_time( threadName, delay): count = 0 while count < 5: time.sleep(delay) count += 1 print(count, "-", threadName, time.ctime(time.time())) # 创建两个线程 try: _thread.start_new_thread( print_time, ("Thread-1", 2, ) ) _thread.start_new_thread( print_time, ("Thread-2", 4, ) ) except: print ("Error: 无法启动线程") while 1: pass 实例9-9的代码运行时,某一次的运行结果如下。 1 - Thread-1 Sun Apr 19 07:07:59 2020 1 - Thread-2 Sun Apr 19 07:08:01 2020 2 - Thread-1 Sun Apr 19 07:08:01 2020 3 - Thread-1 Sun Apr 19 07:08:03 2020 2 - Thread-2 Sun Apr 19 07:08:05 2020 4 - Thread-1 Sun Apr 19 07:08:05 2020 5 - Thread-1 Sun Apr 19 07:08:07 2020 3 - Thread-2 Sun Apr 19 07:08:09 2020 4 - Thread-2 Sun Apr 19 07:08:13 2020 5 - Thread-2 Sun Apr 19 07:08:17 2020 以上程序处于运行状态时,可以按【Ctrl+C】组合键退出程序运行状态。 9.5.4 验证线程之间能否直接共享数据 进程之间不能直接共享数据,只有借助Queue才能实现进程之间的通信。在一个进程内的所有线程可以共享数据,能够在不使用其他方式的前提下完成多线程之间的数据共享。 【实例9-10】验证线程之间能否直接共享数据 实例9-10中定义了一个全局变量gNum,分别创建两个子线程对全局变量gNum执行不同的操作,并输出操作后的结果,其代码如电子活页9-8所示。 实例9-10的代码中,定义一个全局变量gNum,其初始值为100,然后创建两个线程。一个线程将全局变量gNum增加20,另一个线程将全局变量gNum减少30。如果gNum的最终结果为90,则说明线程之间可以共享数据。 实例9-10的运行结果如下。 No.01-------主进程开始------ No.02主进程开始时:i=1,gNum=100 No.03-------子进程1开始------ No.04子进程1中:i=2,gNum=120 No.05-------子进程1结束------ No.06-------子进程2开始------ No.07子进行2中:i=3,gNum=90 No.08-------子进程2结束------ No.09-------主进程结束------ No.10主进程结束时:i=3,gNum=90 从实例9-10的运行结果可以看出,在一个进程内的所有线程共享全局变量,能够在不使用其他方式的前提下完成多线程的数据共享。 【任务9-4】使用多线程模拟生成与读取日志文件 【任务描述】 (1)在项目Unit09中创建Python程序文件9-4.py。 (2)编写程序代码,模拟生成10个日志文件,然后创建4个线程分别读取这些日志文件,将读取结果保存到logText.txt文件中。 【任务实施】 在PyCharm项目Unit09中创建Python程序文件9-4.py。在程序文件9-4.py中编写程序代码,实现所需功能,程序文件9-4.py的代码如电子活页9-9所示。 程序文件9-4.py的运行结果如下。 -----主线程开始----- -----主线程结束----- 程序文件9-4.py运行一次后,文本文件logText.txt的内容如下。 1-2020-04-19 06:46:47 11111 2-2020-04-19 06:46:47 22222 3-2020-04-19 06:46:47 33333 4-2020-04-19 06:46:47 44444 3-2020-04-19 06:46:47 33333 4-2020-04-19 06:46:47 44444 5-2020-04-19 06:46:47 55555 6-2020-04-19 06:46:47 66666 5-2020-04-19 06:46:47 55555 6-2020-04-19 06:46:47 66666 7-2020-04-19 06:46:47 77777 8-2020-04-19 06:46:47 88888 7-2020-04-19 06:46:47 77777 8-2020-04-19 06:46:47 88888 9-2020-04-19 06:46:47 99999 10-2020-04-19 06:46:47 1010101010总结评价 本单元主要学习认知Socket、创建TCP服务器程序与客户端程序、创建UDP服务器程序和客户端程序、创建与使用进程、创建与使用线程。 展开更多...... 收起↑ 资源预览