如何关闭在while循环中的线程中侦听的阻塞套接字?

Posted

技术标签:

【中文标题】如何关闭在while循环中的线程中侦听的阻塞套接字?【英文标题】:How to close a blocking socket listening in a thread in while loop? 【发布时间】:2017-01-28 01:57:45 【问题描述】:

我有一个需要双向通信的服务器和客户端,但问题是当客户端在等待服务器数据时,它无法关闭,我在客户端closeEvent 中调用了socket.shutdown(),但应用程序没有t退出,它只是挂在那里。正确的做法是什么?谢谢!

看问题demoscreencast here,当我关闭client.py窗口时,进程没有终止,是不是因为client.py中的recv调用被阻塞了?

我已经尝试过how to close a blocking socket while it is waiting to receive data?这里的建议,但它不起作用。

server.py

import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic

# enable ctrl-c to kill the app
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

import mysocket
import random


class MyWindow(QDialog):

    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        layout = QVBoxLayout(self)
        button = QPushButton('start')
        button2 = QPushButton('send rand int')
        layout.addWidget(button)
        layout.addWidget(button2)
        self.setLayout(layout)
        self.resize(200, 40)

        button.clicked.connect(self.start_server)
        button2.clicked.connect(self.send_num)

        self.start_server()

    def send_num(self, *args):
        rand_num = random.randint(1, 10)
        self.socket.send(str(rand_num))

    def start_server(self):
        self.socket = mysocket.SocketServer(port=5000)
        self.socket.start()
        print 'socket server started'


def main():
    app = QApplication(sys.argv)
    win = MyWindow()
    win.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

mysocket.py

# enable ctrl-c to kill the app
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

import socket
import threading

# enable ctrl-c to kill the app
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)


class SocketServer(object):

    def __init__(self, port=0):
        self.host = 'localhost'
        self.port = port
        self.bufsize = 4096
        self.backlog = 5
        self.separator = '<>'
        self.clients = []

    def listen(self):
        while True:
            client, address = self.socket.accept()
            # client.settimeout(60)
            self.clients.append(client)
            threading.Thread(
                target=self.server, args=(client, address)).start()

    def start(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((self.host, self.port))
        self.socket.listen(self.backlog)

        threading.Thread(target=self.listen).start()

    def server(self, client, address):
        data = client.recv(self.bufsize)
        while True:

            if self.separator in data:
                data_split = data.split(self.separator)
                cmds = data_split[:-1]

                # execute cmds in threads
                self.process_cmds(cmds)

                data = data_split[-1]

            data += client.recv(self.bufsize)

    def process_cmds(self, cmds):

        for cmd in cmds:
            print 'executing: %s' % cmd

    def send(self, data):
        for client in self.clients:
            try:
                client.send(data)
            except:
                # self.clients.pop(client)
                pass

client.py

import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic

import socket
import threading

# enable ctrl-c to kill the app
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)


class MyWindow(QDialog):

    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        layout = QVBoxLayout(self)
        button = QPushButton('connect')

        for i in range(5):
            cmd_button = QPushButton('cmd - %s' % i)
            layout.addWidget(cmd_button)
            cmd_button.clicked.connect(lambda _, i=i: self.send_cmd(i))

        layout.addWidget(button)
        self.setLayout(layout)
        self.resize(200, 40)

        button.clicked.connect(self.connect_server)

        self.connect_server()

    def listen(self):
        while True:
            data = self.socket.recv(1024)
            if data:
                print 'received:', data

            print 'executed while'

    def connect_server(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(('localhost', 5000))
        print 'socket connected'
        threading.Thread(target=self.listen).start()

    def send_cmd(self, i):
        cmd = 'cmd - %s<>' % i
        print 'sending : %s' % cmd
        self.socket.send(cmd)

    def closeEvent(self, e):
        # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # s.connect(('localhost', 5000))
        # self.socket.close()
        self.socket.shutdown(socket.SHUT_WR)

        super(MyWindow, self).closeEvent(e)


def main():
    app = QApplication(sys.argv)
    win = MyWindow()
    win.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

【问题讨论】:

Qt 有各种高级套接字 API - 为什么不使用它们呢? 【参考方案1】:

在从套接字读取数据之前,您应该使用select 函数:

在程序顶部:

import select

以及修改后的listen函数:

def listen(self):
    while self.isVisible(self):
        readable,_,_ = select.select([self.socket], [], [], 5)
        if (readable):
            data = self.socket.recv(1024)
            print 'received:', data
        else:   
            print 'client send nothing in 5 seconds, or socket has been closed'

        print 'executed while'

另见:https://***.com/a/38520949/1212012(同样的问题,但在非 GUI 程序中)

【讨论】:

以上是关于如何关闭在while循环中的线程中侦听的阻塞套接字?的主要内容,如果未能解决你的问题,请参考以下文章

如何在套接字关闭时唤醒 select()?

没有 while(true) 循环的 C# 非阻塞套接字

如何中断被某些套接字 IO 操作阻塞的线程而不关闭它

如何立即终止套接字 IO 操作上的线程阻塞?

c#socket编程关于阻塞侦听的问题

在另一个线程上接收的套接字上触发 EAGAIN