IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python socket 编程中 accept 阻塞问题的一种解决方法 -> 正文阅读

[Python知识库]Python socket 编程中 accept 阻塞问题的一种解决方法

Python socket 编程中 accept 阻塞问题的一种解决方法

? ? ? ? 在进行 Python socket TCP server 端编程时,需要在其运行时接收停止命令事件,停止整个服务程序。虽然这是不常见的需求,但实现起来颇有些周折,其中 accept 执行时的阻塞问题是关键所在。

? ? ? ? 一般情况下,Python Socket 的 accept 是阻塞执行的,它的阻塞能够屏蔽程序对CTRL-C的接收,也会阻止程序的退出。虽然可以用 settimeout 方法使所有操作进入超时或非阻塞模式(根据官方文档,这与操作系统的相关特性还有关系),但超时时间的选择也是比较两难的问题,时间短了不仅会影响其他操作(如recv),还会使程序在一定程度上变得复杂,处理量增加;时间长了又会使退出操作费时过长。CSDN有文章 给出了一种方法:建立一个主线程,生成一个 Socket 接受连接的子线程,主线程接收CTRL-C以后退出,子线程也随之退出,但经过测试,该方法对于我们接收命令事件退出的方式并不起作用。

? ? ? ?为了解决这一问题,我们采用了一种方法,要点如下:

  1. 设置一个全局循环变量(下面程序中的 local_var.server_on),控制服务端的 accept 循环,当它为 False 时,退出循环;
  2. 在程序中定义一个 TCP Socket 客户端的函数(下面程序中的 run_client() 函数),该函数连接本服务端一次,然后关闭连接;
  3. 程序启动时,该变量初始化为True,使循环得以进行;
  4. 在接收到退出命令事件后,首先将上述循环变量置为False;
  5. 设置循环变量以后,调用一次上述 run_client 函数,作用是使服务端的 accept 退出阻塞;

? ? ? ?这种方法对阻塞或非阻塞方式都是有效的。如果不启动模仿接收停止命令事件的 control_timer 线程,它就是普通的永远运行的 TCP 服务端程序。

? ? ? ?下面的程序是一个可以实际运行的程序,用延时仿真停止命令的处理,关键的处理方法见其中的 control_timer() 函数,它作为一个线程运行,程序启动后,延时 20 秒(实际实现时,延时操作可改为接收停止命令的操作)后进入上述的 TCP 服务端停止处理。最后处理的是接收线程(receive_threading() 线程实例),服务端 run_tcp_server() 每接受一个连接都会生成相应的接收线程,退出时也一并清理。

? ? ? ?其中 local_var.py (import local_var)只是定义了一些全局变量,为避免冗余,本文未给出其源代码。

import socket
import local_var
import threading
import time
# import sys


def create_socket():
    local_var.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = '0.0.0.0'
    port = 9999
    local_var.server_socket.bind((host, port))
    print('timeout time:', local_var.server_socket.gettimeout())
    local_var.server_socket.listen(10)


# 这个函数在退出时运行一次,以防止 run_tcp_server 因 accept 阻塞而无法退出
def run_client():
    local_var.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = 'localhost'
    print(host)
    port = 9999
    try:
        local_var.client_socket.connect((host, port))
        local_var.client_socket.close()
    except Exception as e:
        print('except at run client: ', e)


def run_tcp_server():
    create_socket()
    while local_var.server_on:
        try:
            client_socket, addr = local_var.server_socket.accept()
        except socket.timeout:
            print('socket time out!')
            continue
        local_var.connect_list[addr] = {'socket': client_socket, 'in_listen': True}
        t = threading.Thread(target=receive_threading, args=(addr,))
        try:
            t.start()
        except Exception as e:
            print('except on run_tcp_server 2: ', e)
            client_socket.close()
        print(f'Client address: {addr}')
        msg = 'Hello client!' + '\r\n'
        try:
            client_socket.send(msg.encode('utf-8'))
        except Exception as e:
            print('except on run_tcp_server 2: ', e)
        # client_socket.close()


def receive_threading(the_addr):
    in_listen = local_var.connect_list[the_addr]['in_listen']
    the_socket = local_var.connect_list[the_addr]['socket']
    while in_listen:
        try:
            s = the_socket.recv(1000)
            print(s)
            if len(s) == 0:
                the_socket.close()
                local_var.connect_list.pop(the_addr)
                in_listen = False
        except Exception as e:
            print(f'except on receive_threading: address{the_addr}', e)
            the_socket.close()
            local_var.connect_list.pop(the_addr)
            in_listen = False


def control_timer():
    # 设置定时,模仿退出命令事件的输入
    time.sleep(20)
    print('time over!')
    # 改变循环变量,使得 run_tcp_server() 的循环退出
    local_var.server_on = False
    # 运行一下client端连接,保证 run_tcp_server() 的 accept 退出阻塞
    run_client()
    # 停止所有接收子线程(receive_threading),这时不怕 recv 函数阻塞,因为服务端将退出,从而使 recv 产生异常
    for i in local_var.connect_list.keys():
        local_var.connect_list[i]['in_listen'] = False
    # local_var.control_queue.put('stop')
    # print(local_var.control_queue.qsize())


if __name__ == '__main__':
    local_var.server_on = True
    t_outside = threading.Thread(target=run_tcp_server)
    t_outside.start()
    t_timer = threading.Thread(target=control_timer)
    t_timer.start()
    t_outside.join()
    t_timer.join()

下面是测试用的驱动程序,一个简单的客户端车徐,连接后接收,然后退出。如果需要测试服务端的接收功能,可以把中间注释掉的发送部分恢复。

mport socket
import time
import sys

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

host = 'localhost'
port = 9999

s.connect((host, port))
message = s.recv(1024)

# for i in range(10):
#    s.sendall(b'hello')
#    time.sleep(2)

s.close()

print(message.decode('utf-8'))

下面是运行结果,开始运行 20 秒后退出,控制台打印出的 ”timeout time: None“ 表示 socket 运行在阻塞模式,其间客户端程序运行过两次,各有一次连接过程,最后打出的一个 “Client address:…" 字符串是 run_client() 函数执行的结果:

C:\Users\A\AppData\Local\Programs\Python\Python37\python.exe C:/Users/A/OneDrive/文档/Python/try_tcp_stream/socket_server.py
timeout time: None
Client address: ('127.0.0.1', 50874)
b''
Client address: ('127.0.0.1', 50880)
b''
time over!
localhost
b''Client address: ('127.0.0.1', 50898)


Process finished with exit code 0

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-10-02 14:37:41  更:2021-10-02 14:38:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 16:46:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码