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

时间:2011-06-17 18:12:54

标签: c sockets select recv

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

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

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

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

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

5 个答案:

答案 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的线程发信号通知它被阻塞(而不是即将阻止或只是进入系统调用)。因此,确实无法确保closerecvfrom的行为是可预测的。

请考虑以下事项:

  1. 线程即将调用recvfrom,但它会被系统需要执行的其他操作抢占。
  2. 稍后,线程调用close
  3. 由系统的I / O库调用socket启动的线程,并获得与您close d相同的decalriptor。
  4. 最后,线程调用recvfrom,现在它从套接字接收库打开。
  5. 糟糕。

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

答案 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()