Python: Как закрыть UDP-сокет, пока данные ожидают в recv? - PullRequest
1 голос
/ 26 мая 2010

давайте рассмотрим этот код на python:

import socket
import threading
import sys
import select


class UDPServer:
    def __init__(self):
        self.s=None
        self.t=None
    def start(self,port=8888):
        if not self.s:
            self.s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.s.bind(("",port))
            self.t=threading.Thread(target=self.run)
            self.t.start()
    def stop(self):
        if self.s:
            self.s.close()
            self.t.join()
            self.t=None
    def run(self):
        while True:
            try:
                #receive data
                data,addr=self.s.recvfrom(1024)
                self.onPacket(addr,data)
            except:
                break
        self.s=None
    def onPacket(self,addr,data):
        print addr,data


us=UDPServer()
while True:
    sys.stdout.write("UDP server> ")
    cmd=sys.stdin.readline()
    if cmd=="start\n":
        print "starting server..."
        us.start(8888)
        print "done"
    elif cmd=="stop\n":
        print "stopping server..."
        us.stop()
        print "done"
    elif cmd=="quit\n":
        print "Quitting ..."
        us.stop()
        break;

print "bye bye"

Он запускает интерактивную оболочку, с помощью которой я могу запускать и останавливать сервер UDP. Сервер реализован через класс, который запускает поток, в котором существует бесконечный цикл обратного вызова recv / onPacket внутри блока try / исключением, который должен обнаруживать ошибку и выходы из петля. Я ожидаю, что когда я наберу «stop» на оболочке, сокет закроется, и функция recvfrom вызовет исключение из-за аннулирования дескриптора файла. Вместо этого кажется, что recvfrom все еще блокирует поток, ожидающий данных, даже после вызова close . Почему это странное поведение? Я всегда использовал этот шаблон для реализации UDP-сервера в C ++ и JAVA, и он всегда работал.

Я также пытался с помощью " select " передать список с сокетом аргументу xread , чтобы получить событие нарушения дескриптора файла из выберите вместо этого из recvfrom , но select кажется "нечувствительным" к close тоже.

Мне нужен уникальный код, который будет поддерживать то же поведение в Linux и Windows с Python 2.5 - 2.6.

Спасибо.

Ответы [ 2 ]

3 голосов
/ 26 мая 2010

Обычное решение - дать трубе сообщение рабочему потоку, когда нужно умереть.

  1. Создать трубу, используя os.pipe. Это дает вам сокет с чтением и записью заканчивается в одной и той же программе. Он возвращает необработанные файловые дескрипторы, которые можно использовать как есть (os.read и os.write) или превратить в файловые объекты Python, используя os.fdopen.

  2. Рабочий поток ожидает и сетевой сокет, и конец чтения канала, используя select.select. Когда канал становится читаемым, рабочий поток очищается и выходит. Не читайте данные, игнорируйте их: прибытие - это сообщение.

  3. Когда главный поток хочет уничтожить работника, он записывает байт (любое значение) в конец записи канала. Затем главный поток присоединяется к рабочему потоку, а затем закрывает канал (не забудьте закрыть оба конца).

P.S. Закрытие используемого сокета - плохая идея в многопоточной программе. На странице Linux close (2) написано:

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

Так что повезло, что ваш первый подход не сработал!

1 голос
/ 26 мая 2010

Это не Java. Хорошие советы:

  • Не используйте темы. Используйте асинхронный ввод-вывод.
  • Используйте сетевую среду более высокого уровня

Вот пример использования витой:

from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor, stdio
from twisted.protocols.basic import LineReceiver

class UDPLogger(DatagramProtocol):    
    def datagramReceived(self, data, (host, port)):
        print "received %r from %s:%d" % (data, host, port)


class ConsoleCommands(LineReceiver):
    delimiter = '\n'
    prompt_string = 'myserver> '

    def connectionMade(self):
        self.sendLine('My Server Admin Console!')
        self.transport.write(self.prompt_string)

    def lineReceived(self, line):
        line = line.strip()
        if line:
            if line == 'quit':
                reactor.stop()
            elif line == 'start':
                reactor.listenUDP(8888, UDPLogger())
                self.sendLine('listening on udp 8888')
            else:
                self.sendLine('Unknown command: %r' % (line,))
        self.transport.write(self.prompt_string)

stdio.StandardIO(ConsoleCommands())
reactor.run()

Пример сеанса:

My Server Admin Console!
myserver> foo  
Unknown command: 'foo'
myserver> start
listening on udp 8888
myserver> quit
...