Разблокировать recvfrom, когда сокет закрыт - PullRequest
6 голосов
/ 17 июня 2011

Допустим, я запускаю поток для получения через порт.Вызов сокета заблокируется на recvfrom.Затем, как-то в другом потоке, я закрываю сокет.

В Windows это разблокирует recvfrom, и выполнение моего потока прекращается.

В Linux это не разблокирует recvfrom, и какрезультат, мой поток сидит, ничего не делая вечно, и выполнение потока не прекращается.

Может кто-нибудь помочь мне с тем, что происходит в Linux?Когда сокет закрыт, я хочу, чтобы recvfrom разблокировал

Я продолжаю читать об использовании select (), но я не знаю, как использовать его для моего конкретного случая.

Ответы [ 5 ]

11 голосов
/ 02 ноября 2011

Вызовите shutdown(sock, SHUT_RDWR) на сокете, затем дождитесь завершения потока.(т. е. pthread_join).

Можно подумать, что close() разблокирует recvfrom(), но в Linux это не так.

3 голосов
/ 18 июня 2011

Вот эскиз простого способа использования 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 ()) и сделать так, чтобы основной поток отправлял байт на своем конце пары сокетов, когда он хочет, чтобы I Поток / O должен уйти, и поток I / O завершится, когда он получит байт в свой сокет на другом конце пары сокетов. Я оставлю это как упражнение для читателя. :)

Часто также неплохо установить сокет в неблокирующий режим, чтобы избежать (небольшой, но ненулевой) вероятности того, что вызов recvfrom () может заблокироваться даже после того, как select () указал, что сокет готов читать, как описано здесь . Но режим блокировки может быть «достаточно хорошим» для вашей цели.

2 голосов
/ 17 июня 2011

Не является ответом, но страница руководства по закрытию Linux содержит интересную цитату:

Вероятно, неразумно закрывать файловые дескрипторы, когда они могут использоваться системными вызовами в других потоках в том жепроцесс.Поскольку файловый дескриптор может быть повторно использован, существуют некоторые неясные условия гонки, которые могут вызвать непреднамеренные побочные эффекты.

0 голосов
/ 24 января 2018

Когда сокет закрыт, я хочу, чтобы recvfrom разблокировал

recvfrom () - это функция, специфичная для сокетов UDP на Python. Вот краткое описание того, как я решил проблему, используя идею, называемую «опрос» (попробуйте запустить программу также, операторы 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()
0 голосов
/ 13 июня 2017

Вы просите невозможного. Поток, вызывающий close, просто не может знать, что другой поток заблокирован в recvfrom. Попробуйте написать код, который гарантирует, что это произойдет, вы обнаружите, что это невозможно.

Независимо от того, что вы делаете, вызов на close всегда будет доступен для вызова на recvfrom. Вызов close изменяет то, к чему относится дескриптор сокета, поэтому он может изменить семантическое значение вызова на recvfrom.

Поток, который входит в recvfrom, не может каким-либо образом сигнализировать потоку, который вызывает close, что он заблокирован (в отличие от того, чтобы вот-вот заблокировать или просто ввести системный вызов). Таким образом, буквально невозможно найти способ предсказать поведение close и recvfrom.

Примите во внимание следующее:

  1. Поток собирается вызвать recvfrom, но он получает приоритет перед другими вещами, которые должна сделать система.
  2. Позже поток вызывает close.
  3. Поток, запущенный системной библиотекой ввода-вывода, вызывает socket и получает тот же дескриптор, что и вы close d.
  4. Наконец, поток вызывает recvfrom, и теперь он получает из сокета открытую библиотеку.

К сожалению.

Никогда бы не сделал что-нибудь, даже отдаленно подобное. Ресурс не должен быть освобожден, пока его использует или может использовать другой поток. Период.

...