在套接字关闭时取消阻止recvfrom

假设我开始在端口上接收线程。 套接字调用将阻止recvfrom。 然后,不知何故在另一个线程中,我关闭套接字。

在Windows上,这将取消阻止recvfrom,我的线程执行将终止。

在Linux上,这不会解锁recvfrom,因此,我的线程永远不会做任何事情,并且线程执行不会终止。

任何人都可以帮助我解决Linux上发生的事情吗? 当套接字关闭时,我希望recvfrom解除阻塞

我一直在阅读有关使用select()的内容,但我不知道如何将它用于我的特定情况。

在套接字上调用shutdown(sock, SHUT_RDWR) ,然后等待线程退出。 (即pthread_join )。

你会认为close()会解锁recvfrom() ,但它不会在linux上。

这是使用select()处理此问题的简单方法的草图:

 // Note: untested code, may contain typos or bugs static volatile bool _threadGoAway = false; void MyThread(void *) { int fd = (your socket fd); while(1) { struct timeval timeout = {1, 0}; // make select() return once per second fd_set readSet; FD_ZERO(&readSet); FD_SET(fd, &readSet); if (select(fd+1, &readSet, NULL, NULL, &timeout) >= 0) { if (_threadGoAway) { printf("MyThread: main thread wants me to scram, bye bye!\n"); return; } else if (FD_ISSET(fd, &readSet)) { char buf[1024]; int numBytes = recvfrom(fd, buf, sizeof(buf), 0); [...handle the received bytes here...] } } else perror("select"); } } // To be called by the main thread at shutdown time void MakeTheReadThreadGoAway() { _threadGoAway = true; (void) pthread_join(_thread, NULL); // may block for up to one second } 

一个更优雅的方法是避免使用select的超时function,而是创建一个套接字对(使用socketpair())并让主线程在它想要I / O线程时在套接字对的末尾发送一个字节离开,并在它在套接字对的另一端的套接字上接收到一个字节时让I / O线程退出。 我会把它作为读者的练习。 🙂

将套接字设置为非阻塞模式通常也是一个好主意,以避免即使在select()指示套接字已准备好读取后,recvfrom()调用可能会阻塞(小但非零)的机会。 ,如这里所述。 但阻止模式可能对你的目的来说“足够好”。

不是答案,但Linux关闭手册页包含有趣的引用:

关闭文件描述符可能是不明智的,因为它们可能在同一进程中的其他线程中被系统调用使用。 由于文件描述符可能被重用,因此存在一些可能导致意外副作用的模糊竞争条件。

你在寻求不可能的事情。 调用close的线程根本没有可能的方法知道另一个线程在recvfrom被阻塞。 尝试编写保证发生这种情况的代码,你会发现它是不可能的。

无论你做什么,总是有可能通过调用recvfromclose比赛。 对close的调用改变了套接字描述符引用的内容,因此它可以改变对recvfrom的调用的语义。

进入recvfrom的线程无法以某种方式向调用close的线程发信号通知它被阻塞(而不是阻止或只是进入系统调用)。 因此,实际上没有可能的方法来确保closerecvfrom的行为是可预测的。

考虑以下:

  1. 一个线程即将调用recvfrom ,但它会被系统需要做的其他事情抢先一步。
  2. 之后,线程调用close
  3. 由系统的I / O库启动的线程调用socket并获得与您close的一个相同的decsriptor。
  4. 最后,线程调用recvfrom ,现在它从套接字接收库打开。

哎呀。

不要像这样远程做任何事情。 当另一个线程正在或可能正在使用它时,不得释放资源。 期。

当套接字关闭时,我希望recvfrom解除阻塞

recvfrom()是特定于Python上UDP套接字的函数。 以下是我如何使用被称为“轮询”的想法解决问题的简要总结(尝试运行程序,打印语句将为您提供有关最新情况的确切信息):

 import socket import threading import signal import time # Custom class to create a socket, close the socket, and poll the socket class ServerSocket(): def __init__(self, addresses): # "Standard" way to create and preapare a working socket self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(addresses) def poll(self): self.socket.settimeout(2) modifiedMsg, senderAddress = self.socket.recvfrom(1024) def close(self): self.socket.close() class ServiceExit(Exception): """ Custom exception which is used to trigger the clean exit of all running threads and the main program. """ pass def service_shutdown(signum, frame): raise ServiceExit # Custom class to create a UDP server on a separate thread. # This server will know to close the blocking UDP socket when the user # of the main program signals termination via typing CTRL-C into the terminal class Server(threading.Thread): def __init__(self, addresses): threading.Thread.__init__(self) self.mysocket = ServerSocket(addresses) # This flag below will help us determine when to stop the "run" loop below # The while loop below is where interrupt the blocking recvfrom() call by # timing out every 2 seconds and checking to see if the flag has been set # to discontinue the while loop self.shutdown_flag = threading.Event() def run(self): while not self.shutdown_flag.is_set(): try: print('socket blocking') self.mysocket.poll() except socket.timeout: print('socket unblocked') pass # as a final step, we close the socket self.mysocket.close() print('socket closed') def main(): # assign the methods that will be called when our main program receives a SIGTERM or SIGINT signal # You can send this main problem such a signal by typing CTRL-C after you run this program signal.signal(signal.SIGTERM, service_shutdown) signal.signal(signal.SIGINT, service_shutdown) # Start the server thread that will eventually block on recvfrom() try: print('starting udp server thread') udp_server = Server(('localhost', 5000)) udp_server.start() while True: time.sleep(0.5) # This server will accept UDP packets on the local host at port 5000 # Feel free to change these settings to fit your needs except ServiceExit: print('shutting down server thread') udp_server.shutdown_flag.set() udp_server.join() print('server thread shut down') if __name__ == '__main__': main()