假设我开始在端口上接收一个线程。套接字调用将阻止recvfrom。 然后,不知何故在另一个线程中,我关闭套接字。
在Windows上,这将取消阻止recvfrom,我的线程执行将终止。
在Linux上,这不会解锁recvfrom,因此,我的线程永远不会做任何事情,并且线程执行不会终止。
任何人都可以帮助我解决Linux上发生的事情吗?当套接字关闭时,我希望recvfrom取消阻止
我一直在阅读有关使用select()的内容,但我不知道如何在特定情况下使用它。
答案 0 :(得分:11)
在套接字上调用shutdown(sock, SHUT_RDWR)
,然后等待线程退出。 (即pthread_join
)。
您会认为close()
会取消阻止recvfrom()
,但它不会在Linux上解锁。
答案 1 :(得分:3)
这里有一个使用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的超时功能,而是创建一个套接字对(使用socketpair())并让主线程在它想要的时候在套接字对的末尾发送一个字节/ O线程消失,并且当它在套接字对的另一端的套接字上接收到一个字节时,让I / O线程退出。不过,我会把它留作读者练习。 :)
将套接字设置为非阻塞模式通常也是一个好主意,以避免即使在select()指示后,recvfrom()调用可能会阻塞(小但非零)的机会。 socket已准备好读取,如here所述。但阻止模式可能足够好"为了你的目的。
答案 2 :(得分:2)
不是答案,但Linux关闭手册页包含有趣的引用:
关闭文件描述符可能是不明智的 在同一进程中的其他线程中使用系统调用。自从一个文件 描述符可以重用,有一些模糊的竞争条件 可能会导致意想不到的副作用。
答案 3 :(得分:0)
你要求不可能的事。调用close
的线程无法知道另一个线程在recvfrom
中被阻止。尝试编写保证发生这种情况的代码,你会发现它是不可能的。
无论你做什么,close
的电话总是可以与recvfrom
的电话竞争。对close
的调用会更改套接字描述符所引用的内容,因此可以将调用的语义更改为recvfrom
。
进入recvfrom
的线程无法以某种方式向调用close
的线程发信号通知它被阻塞(而不是即将阻止或只是进入系统调用)。因此,确实无法确保close
和recvfrom
的行为是可预测的。
请考虑以下事项:
recvfrom
,但它会被系统需要执行的其他操作抢占。close
。socket
启动的线程,并获得与您close
d相同的decalriptor。recvfrom
,现在它从套接字接收库打开。糟糕。
不要像这样远程做任何事情。当另一个线程正在或可能正在使用它时,不得释放资源。周期。
答案 4 :(得分:-1)
当套接字关闭时,我希望recvfrom取消阻止
recvfrom()是特定于Python上的UDP套接字的函数。这里简要总结了我如何使用一种被称为"轮询"的想法来解决问题。 (尝试运行该程序,print语句将为您提供最新信息):
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()