python进阶:服务端实现并发的八种方式

2,051 阅读6分钟
原文链接: zhuanlan.zhihu.com

【本文导读】文中有许多不妥之处,敬请批评指正!python编写的服务端,有八种实现并发的方式,如阻塞(对等)套接字实现并发、非阻塞套接字实现并发、epoll实现并发、多进程实现并发、多线程实现并发、进程池实现并发、线程池实现并发、协程实现并发等。

一、什么是并发?

1、套接字:是python与操作系统两者间的一个接口,通过设置对等的IP与端口,实现数据的发送与接收。套接字有三种:服务端监听套接字、服务端对等套接字、客户端(对等)套接字,其中服务端对等套接字与客户端套接字实现数据的传输。

2、阻塞:套接字传输数据时,会出现accept与recv两种阻塞,服务端在没有新的套接字来之前,不能处理已经建立连接的套接字的请求,此时出现accept阻塞;服务端在没有接受到客户端请求数据之前,不能与其他客户端建立连接,此时出现recv阻塞,客户端也会出现类似recv阻塞。

3、进程与并行:多进程实现并行,遵循轮巡调度机制,由多个cpu同时运行,有效解决阻塞问题。并行属于并发。

4、线程与并发:多线程实现并发,遵循轮巡调度机制,一个cpu在一定时间内的并行,由python解释器调度。

二、实现并发的八种方式

(一)阻塞(对等)套接字实现并发的服务端

1、阻塞(对等)套接字实现并发的服务端

# 阻塞(对等)套接字实现并发的服务端
import socket                       # 导入模块socket(API接口)
server = socket.socket()            # 创建服务端
server.bind(('0.0.0.0',8881))       # 绑定ip与端口
server.listen(5)                    # 开始监听
while True:
    conn, address = server.accept() # 获取服务端对等套接字
    while True:                     # 向客户端接收发送数据
        client_data = conn.recv(1000)
        if client_data:
            print("客户端发来的数据",client_data.decode())
            conn.send(client_data)
        else:
            con.close()              # 关闭服务端
            break


(二)非阻塞套接字实现并发的服务端

1、非阻塞套接字实现并发的服务端:

# 非阻塞套接字实现并发的服务端
import socket                           # 导入模块socket
server = socket.socket()                # 创建服务端
server.setblocking(False)               # 套接字设成非阻塞
server.bind(('0.0.0.0',8882))           # 绑定ip与端口
server.listen(5)                        # 开始监听
conn_list = []                          # 套接字列表
while True:
    try:
        conn,address = server.accept()  # 获取服务端对等套接字
        conn.setblocking(False)         # 套接字设置成非阻塞
        conn_list.append(conn)          # 套接字放入列表
    except BlockingIOError as error:    # 捕获异常
        pass
    for i in conn_list:
        try:                             # 向客户端接收发送数据
            recv_data = conn.recv(1024)
            if recv_data:
                print("客户端发来的数据",recv_data.decode())
                conn.send(recv_data)
            else:
                conn.close()              # 关闭服务端
                conn_list.remove(conn)    # 移除套接字
        except BlockingIOError as error:  #捕获异常
            pass


(三)epoll实现并发的服务端

1、通过epoll实现并发的服务端

# 通过epoll(io多路复用)实现并发的服务端
import socket                              # 导入模块
import selectors                           # 导入模块
epoll_selector = selectors.EpollSelector() # 实例化epoll(liunx)
server = socket.socket()                   # 创建服务端
server.bind(('0.0.0.0',8888))              # 绑定ip与端口
server.listen(1000)                        # 开始监听
def recv(conn):                            # 向客户端接收或发送数据
    recv_data = conn.recv(1024)
    if recv_data:
        print("客户端发来的数据",recv_data.decode())
        conn.send(recv_data)
    else:
        epoll_selector.unregister(conn)    # 取消事件
        conn.close()                       # 关闭对等套接字
def accept(server):
    conn,addr = server.accept()            # 获取服务端对等套接字
    # 回调函数处理第二个recv阻塞,注册事件:操作系统监控已获取conn、数据等
    epoll_selector.register(conn,selectors.EVENT_READ,recv)
# 回调函数处理第一个accept阻塞,注册事件:操作系统监控已获取server、数据等
epoll_selector.register(server,selectors.EVENT_READ,accept)
while True:                            # 事件循环
    events = epoll_selector.select()   # 查询准备好的事件,返回列表
                                       # 用print(events)查询事件内容
    for key ,mask in events:           # 拆包,获取socket、
        callback = key.data            # 获取accept
        sock = key.fileobj             # 获取准备好的客户端套接字
        callback(sock)                 # 调用accept函数


(四)多进程实现并发的服务端

1、多进程实现并发的服务端

# 多进程实现并发的服务端
import socket                         # 导入模块
from multiprocessing import Process   # 导入多进程模块
server = socket.socket()              # 创建服务端
server.bind(('0.0.0.0',9091))         # 绑定ip与端口
server.listen(1000)                   # 开始监听
def fun(conn):                        # 向客户端接收或发送数据
    while True:
        recv_data = conn.recv(1024)
        if recv_data:
            print("客户端发来的数据",recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
            break
while True:                              # 主进程
    conn,addr = server.accept()          # 获取服务端对等套接字
    p = Process(target=fun,args=(conn,)) # 实例化子进程(连接客户端)
    p.start()                            # 开启子进程


(五)多线程实现并发的服务端

1、多线程实现并发的服务端:

# 多线程实现并发的客户端
import socket                     # 导入模块
import threading                  # 导入多线程模块
server = socket.socket()          # 创建服务端
server.bind(('0.0.0.0',6668))     # 绑定ip与端口
server.listen(1000)               # 开始监听
def fun(conn):                    # 向客户端接收或发送数据
    while True:
        recv_data = conn.recv(1024)
        if recv_data:
            print("客户端发来的数据",recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
while True:                                           # 主线程
    conn,address = server.accept()                    # 获取服务端对等套接字
    t = threading.Thread(target=fun,args=(conn,))     # 实例化子线程(连接客户端)


(六)使用进程池(进程池)实现并发的服务端

1、使用进程池(进程池)实现并发的服务端

# 使用进程池(进程池)实现并发的服务端
import socket                               # 导入模块
from multiprocessing import Pool,cpu_count  # 导入进程池
from multiprocessing.pool import ThreadPool # 导入线程池
server = socket.socket()                    # 创建服务端
server.bind(('0.0.0.0',5678))               # 绑定ip与端口
server.listen(1000)                         # 开始监听
def work_thread(conn):                      # 向客户端接收或发送数据
    while True:
        recv_data = conn.recv(1000)
        if recv_data:
            print("客户端发来的数据",recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
            break
def work_process(server):
    t_pool = ThreadPool(2*cpu_count())       # 准备线程
    while True:
        conn,address = server.accept()       # 获取服务端对等连接套接字
        t_pool.apply_async(work_thread,args=(conn,))# 进程池(客户端收发数据)
n = cpu_count()                               # CPU的核数
p = Pool(n)                                   # 实例化进程池
for i in range(n):
    p.apply_async(work_process,args=(server,)) #进程池(accept阻塞)
p.close()
p.join()

(七)使用进程池(多线程)实现并发的服务端

1、使用进程池(多线程)实现并发的服务端

# 使用进程池(多线程)实现并发的服务端
import socket                              # 导入模块
from multiprocessing import Pool,cpu_count
from threading import Thread               # 导入多线程
server = socket.socket()                   # 创建服务端
server.bind(('0.0.0.0',7777))              # 绑定ip与端口
server.listen(1000)                        # 开始监听
def work_thread(conn):                     # 向客户端接收发送数据
    while True:
        recv_data = conn.recv(1000)
        if recv_data:
            print("客户端发来的数据",recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
            break
def work_process(server):
    while True:
        conn,addr = server.accept()                 # 获取服务端对等套接字
        t = Thread(target=work_thread,args=(conn,)) # 多线程
        t.start()
n = cpu_count()
p = Pool(n)
for i in range(n):
    p.apply_async(work_process,args=(server,))     # 进程池(accept阻塞)
p.close()
p.join()


(八)协程实现并发的服务端

1、使用协程实现并发的服务端

# 使用协程实现并发的服务端
from gevent import monkey;monkey.patch_socket() # 替换python自带的socket
import gevent                       # 导入协程
import socket                       # 导入模块
server = socket.socket()            # 创建服务端
server.bind(('0.0.0.0',8989))       # 绑定ip与端口
server.listen(1000)                 # 开始监听
def worker(conn):                   # 向客户端接收发送数据
    while True:
        recv_data = conn.recv(1000)
        if recv_data:
            print("客户端发来的数据",recv_data.decode())
            conn.send(recv_data)
        else:
            conn.close()
            break
while True:
    conn,address = server.accept()   # 获取服务端对等套接字
    gevent.spawn(worker,conn)        # 实例化协程,并将conn作为参数传入


三、实现并发的具体操作:

1、先启动服务端(以协程并发为例),端口自己设置。

2、再启动两个客户端,输入数据,与服务端实现数据传输。