Python网络编程和多线程

第14章 网络编程开发

14.1 socket套接字模块

socket套接字模块可以实现低级别的网络服务,它提供了标准的BSD socket API,可以访问底层操作系统 socket接口的全部方法,其主要函数及用法请查找菜鸟教程,仅演示代码示例。

1. 用socket建立TCP “客户端/服务端”连接

  1. 创建【server.py】文件,功能是以TCP连接方式建立一个服务端程序,能够将接收到的信息直接发回到客户端。

    import socket                  # 引入socket模块
    HOST = ''                      # 定义主机名HOST的初始值
    PORT = 10000                   # 定义端口号PORT的初始值
    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                                   # 创建socket对象sk,实参是地址和协议类型
    sk.bind((HOST,PORT))           # 将套接字与地址绑定
    sk.listen(1)                   # 设置监听连接
    conn, addr = sk.accept()       # 接受客户端连接
    print("客户端地址:", addr)      # 显示客户端地址
    while True:
        data = conn.recv(1024)     # 实行对话操作(接收/发送)
        print("获取信息:", data.decode("utf-8"))   # 显示获取的信息
        if not data:               # 如果没有数据了,则结束循环
            break
        conn.sendall(data)         # 发送数据回客户端
    conn.close()                   # 关闭连接
    

上述代码,建立TCP连接后使用while语句多次与客户端进行数据交换,直到收到数据为空时会终止服务端的运行。运行后等待客户端连接后可看见交互结果。

  1. 创建【client.py】文件,功能是建立客户端程序,创建socket实例,实例调用connect()函数来连接服务器。

    import socket                    # 导入socket模块
    HOST = "localhost"               # 定义主机号初始值
    PORT = 10000                     # 定义端口号初始值
    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                                              # 创建socket对象
    sk.connect((HOST, PORT))                  # 建立与服务端的连接
    data ="Hello!"                           # 信息 
    while data:
        sk.sendall(data.encode("utf-8"))      # 发送数据
        data = sk.recv(512)                   # 实行对话操作(接收/发送)
        print("获取服务端信息:\n", data.decode("utf-8"))  # 打印收到的信息
        data = input("请输入信息:\n")          # 信息输入
    s.close()                                 # 关闭连接
    

先运行server程序,后运行client程序。

2. 用socket建立UDP “客户端/服务端”连接

  1. 创建【udpserver.py】文件:

    import socket                  # 引入socket模块
    HOST = ''                      # 定义主机名HOST的初始值
    PORT = 10000                   # 定义端口号PORT的初始值
    sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                                   # 创建socket对象sk,实参是地址和协议类型
    sk.bind((HOST,PORT))           # 将套接字与地址绑定
    data = True                    # 数据容器
    while data:
        data, address = sk.recvfrom(1024)     # 实行对话操作(接收/发送),接收信息和发送地址。
        if data == "再见":
            break                             # 收到信息为“再见”时,结束循环。
        print("获取信息:", data.decode("utf-8"))   # 显示获取的信息
        sk.sendto(data, address)         # 发送数据回客户端地址
    sk.close()                           # 关闭连接
    
  2. 创建【udpclient.py】文件:

    import socket                    # 导入socket模块
    HOST = "localhost"               # 定义主机号初始值
    PORT = 10000                     # 定义端口号初始值
    sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                                              # 创建socket对象
    data ="Hello!"                           # 信息 
    while data:
        sk.sendto(data.encode("utf-8"), (HOST,PORT))      # 发送数据到指定主机端口
        if data = "再见":
            break                             # 收到信息为“再见”时,结束循环
        data, address = sk.recvfrom(512)                   # 实行对话操作(接收/发送)
        print("获取服务端信息:\n", data.decode("utf-8"))     # 打印收到的信息
        data = input("请输入信息:\n")          # 信息输入
    s.close()                                 # 关闭连接
    

14.2 socketserver模块

  • Python提供的高级网络服务模块,里面包含了服务器中心类,目的是简化网络服务器的开发步骤。

  • socketserver框架中包含“服务器类”和“请求处理类”两部分,“服务器类”用于处理通信问题,“请求处理类”用于数据交互。

  • 服务器类:TCPServer、UDPServer、ThreadingTCPServer、ThreadingUDPServer、ForkingTCPServer、ForkingUDPServer。

  • Threading前缀是多线程服务器类,Forking前缀是多进程服务器类。

  • 只需实例化或继承服务器类,就能创建不同的服务端程序,然后调用实例方法serve_forever()即可。

示例:用socketserver创建TCP “C/S” 程序

  1. 创建【skserver.py】文件,功能是创建基于TCP协议的服务端程序,能够与客户端建立通信。

    import socketserver
    # 1. 继承请求处理类StreamRequestHandler,自定义一个处理类
    class MyTcpHandler(socketserver.StreamRequestHandler):
        def handle(self):
            while True:
                data = self.request.recv(1024)
                if not data:
                    Server.shutdown()
                    break
                print("接收到信息:", data.decode("utf-8"))
                self.request.send(data)
             return
        
    # 2. 创建TCPServer服务器实例
    Server = socketserver.TCPServer((HOST, PORT), MyTcpHandler)
    Server.server_forever()
    

14.3 select模块

  • select模块可以实现I/O多路复用,是指通过一种机制可以监视多个socket描述符,一旦某个描述符读写就绪,能够通知程序进行相应的读写操作。
  • 传统的connect(), accept(), recv(), recvfrom() 之类的程序是阻塞程序block,即进程或线程执行到这些函数时必须等待某个事件的发生,若事件没有发生,进程或线程就被一直阻塞,函数无法立即返回。
  • 使用select模块可以实现非阻塞non-block方式运行的程序,即进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没发生则返回值告知事件未发生,二进程或线程继续执行,所有效率较高,它能够监视文件描述符的变化情况,读写还是异常。
  • 待完善,详情查找手册。

14.4 urllib包

  • Python内置的urllib包可以完成http协议层程序的开发工作,主要用于处理URL,使操作URL像打开和使用本地文件一样简单。

  • urllib包中主要包含的模块:

    模块 功能
    urllib.request 用于打开URL网址,且定义了身份验证、重定向、cookies等方法。
    urllib.error 用于定义request模块引发的异常。
    urllib.parse 用于解析URL,提供了一些处理URL字符串的函数。
    urllib.robotparser 用于解析robots.txt文件。

14.5 收发电子邮件

14.5.1 POP3协议的邮件程序

  • POP3是一种可以登录Email服务器收取邮件的协议,现在大多数邮箱服务器都提供POP3收取邮件的方式,如outlook邮箱。

  • Python中调用内置模块poplib 可以支持POP3协议,开发出客户端脚本程序。

  • 通过两个类实现POP3功能:

    poplib模块的类 功能
    poplib.POP3(host, port=POP3_PORT, timeout) 创建POP3连接,初始化实例时返回一个连接对象。
    poplib.POP3_SSL(host,port,keyfile,certfile,timeout,context) 时POP3的子类,以SSL加密的socket连接服务器。

14.5.2 SMTP协议的邮件程序

  • SMTP是简单邮件传输协议,基于由源地址到目标地址的规则,用于控制邮件的中转方式。
  • 调用smtplib模块可以支持该协议的功能。
  • 创建SMTP连接对象:smtpConn = smtplib.SMTP(host, port, localhost)

第15章 多线程开发

15.1 threading模块

Python3提供了两个支持多线程的标准库(_thread和threading),其中_thread提供了低级别的、原始的线程以及一个简单的锁,功能有效,一般不推荐,threading模块有更完善的性能。

15.1.1 threading模块的常用函数

threading模块除了包含“_thread”模块中的所有方法,还提供了以下核心函数。

函数 功能
currentThread() 返回当前的Thread线程对象。
enumerate() 返回一个包含正在运行的线程的list。
activeCount() 返回正在运行的线程的数量,也可以len(threading.enumerate)。
main_thread() 返回主线程Thread对象,一般是从Python解释器启动的线程。
settrace(func) 为所有从threading模块启动的线程设置一个跟踪函数,每个run()方法调用前,func将传递给sys。settrace()。
setprofile(func) 为所有从threading模块启动的线程设置profile()方法,用于设置全局配置函数。

15.1.2 Thread类

Thread对象用于处理线程,有两种方法创建线程对象,一是直接用threading.Thread()构造器创建对象(初始化),二是继承Thread类,重写其run()方法。Thread类包括以下成员方法和变量:

成员方法和变量 功能
start() 用于启动线程。每个线程只能调用一次,可以作为run()方法的前置准备。
run() 执行线程活动,可以继承时重写。
join(timeout) 使线程等待,会阻塞线程直至join()中止或结束,或timeout超时。
setName() 设置线程名。
getName() 获取线程名。
isAlive() 判断线程是否活动。
name 线程名,多个线程可以赋予相同的名。
ident 线程的ID,为启动时为None
daemon 标记此线程是否为守护线程,布尔值。

15.1.3 代码示例

  1. 使用_thread模块创建线程

    import _thread
    import time
    def print_time(threadName, delay):
        count = 0                          # 计数器
        while count < 5:                   # 5次输出
            time.sleep(delay)              # 线程睡眠
            count += 1
            print(threadName, time.ctime(time.time()))
    try:
        _thread.start_new_thread(print_time, ("Thread_1", 2))  # 创建线程1
        _thread.start_new_thread(print_time, ("Thread_2", 5))  # 创建线程2
    except:
        print ("error: 线程无法启动。")
    
  2. 直接实例化Thread类创建线程:thread-1 = threading.Thread(target, args, kwargs, name, group)

    • target:被执行的对象,会被run()调用。
    • args:是被执行对象的参数表,是元祖类型。
    • kwargs:是被执行对象的关键字参数表,是字典类型。
    • name:设置线程名,一般以“Thread-N”的形式。
    • group:用于作为ThreadGroup类的扩展。
    import threading
    import time
    def print_time(threadName, delay):
        count = 0                          # 计数器
        while count < 5:                   # 5次输出
            time.sleep(delay)              # 线程睡眠
            count += 1
            print(threadName, time.ctime(time.time()))
        print("线程%s结束!" % (threadName))
    t1 = threading.Thread(target=print_time,args=("Thread-1",2))
    t2 = threading.Thread(target=print_time,args=("Thread-2",5))
    t1.start()
    t2.start()
    
  3. 通过继承Thread类创建线程

    import threading
    import time
    class myThread(threading.Thread):
        def __init__(self,threadName,delay):
            super().__init__()
            self.threadName = threadName
            self.delay = delay
        def run(self):
            count = 0                          # 计数器
            while count < 5:                   # 5次输出
                time.sleep(self.delay)         # 线程睡眠
                count += 1
                print(self.threadName, time.ctime(time.time()))
            print("线程%s结束!" % (self.threadName))
    t1 = myThread("Thread-1", 2)
    t2 = myThread("thread-2", 4)
    t1.start()
    t2.start()
    
  4. 使用join()方法实现线程等待

    import threading
    import time
    def print_time(threadName, x, thd=None):
        for i in range(x):
            if thd:                # 当另一个线程活动时,会触发本线程等待,直到另一线程结束。
                thd.join()
            time.sleep(2)
            print(threadName, i)
        print("线程%s结束!" % (threadName))
    t1 = threading.Thread(target=print_time,args=("Thread-1", 5))
    t2 = threading.Thread(target=print_time,args=("Thread-2", 5, t1))
    t1.start()
    t2.start()
    

15.2 Lock和RLock对象

  • Lock和RLock对象可以实现简单的线程同步功能,两个对象都要acquire()和release()方法,对于那些需要每次只允许一个线程操作的数据,可以将其放到acquire()和release()之间。

  • RLock是可重入锁,可以在同一锁中多次请求而不会死锁。

  • 在多线程程序中,出现锁死的常见原因是线程尝试一次性获得多个锁。如一个线程获得一个锁后,在尝试获得第2个锁时阻塞了,那么该线程的第一个锁可能也会阻塞其他线程执行。

  • 避免出现锁死的一种解决方案是给程序的每一个锁分配一个唯一的数字编码,利用上下文管理器使线程只能按照编号来获取锁。

15.3 multiprocessiong多进程库

是Python的多进程管理包,可以像Thread类一样,利用multiprocessing.Process对象创建一个进程。

import os
import threading
import multiprocessing

# 定义函数worker()
def worker(sign, lock):
    lock.acquire()
    print(sign, os.getpid())   
    lock.release()

# 打印输出线程的PID
print("Main: ",os.getpid)

# 多线程处理
record = []
lock = threading.Lock()
for i in range(5):
    thread = threading.Thread(target=worker,args=("thread", lock))
    thread.start()
    record.append(thread)
for thread in record:
    thread.join()
    
# 多进程处理
record = []
lock = multiprocessing.Lock()
for i in range(5):
    process = multiprocessing.Process(target=worker,args=("process",lock))
    process.start()
    record.append(process)
for process in record:
    process.join()

Thread对象与Process对象虽然用法相似,但在输出结果上有所不同,都是打印PID,避免多个任务同时向终端输出,使用lock同步。所有Thread的PID都与主线程相同,而每个Process都有不同的PID。

posted @ 2021-11-01 16:40  DvLopr_Jarjack  阅读(267)  评论(0)    收藏  举报