Неблокирующие сокеты python

Я хотел бы написать небольшое приложение Bluetooth-сервера на телефон Nokia в PyS60. Он должен иметь возможность отправлять ответ на запрос клиента и иметь возможность передавать данные клиенту.

вариант 1 : если я использую socket.recv(1024) , программа ждет, пока что-то не будет получено, поэтому сервер не может передавать данные клиенту. В реализации Python для S60 отсутствует метод socket.settimeout() , поэтому я не мог написать правильный неблокирующий код.

oprion 2 : Метод socket.makefile() выглядел неплохо, но не мог заставить его работать. Когда я заменил conn.recv(1024) на fd = socket.makefile() fd.readline() , он ничего не прочитал.

вариант 3 : просмотрел функцию select() , но не повезло с ней. Когда я изменил conn.recv() на r,w,e = select.select([conn],[],[]) как было предложено, клиент даже не подключается. Он зависает в «Ожидание клиента …». Странный…

Я знаю, что есть неплохие серверные реализации и асинхронные API-интерфейсы, но мне нужен только базовый материал. Заранее спасибо!

вот что у меня есть:

 sock = btsocket.socket(btsocket.AF_BT, btsocket.SOCK_STREAM) channel = btsocket.bt_rfcomm_get_available_server_channel(sock) sock.bind(("", channel)) sock.listen(1) btsocket.bt_advertise_service(u"name", sock, True, btsocket.RFCOMM) print "Waiting for the client..." conn, client_mac = sock.accept() print "connected: " + client_mac while True: try: data = conn.recv(1024) if len(data) != 0: print "received [%s]" % data if data.startswith("something"): conn.send("something\r\n") else: conn.send("some other data \r\n") except: pass 

Это, очевидно, блокирование, поэтому «некоторые другие данные» никогда не отправляются, но это лучшее, что я получил до сих пор. По крайней мере, я могу отправить что-то в ответ клиенту.

Наконец-то нашел решение!

Функция select не работала с модулем btsocket новых портов PyS60. Кто-то написал новый_btsocket (доступный здесь ) с рабочей функцией выбора.

Вот простой пример, основанный на эхо-сервере

 #!/usr/bin/python import socket import select server = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) server.bind( ('localhost', 12556) ) server.listen( 5 ) toread = [server] running = 1 # we will shut down when all clients disconenct while running: rready,wready,err = select.select( toread, [], [] ) for s in rready: if s == server: # accepting the socket, which the OS passes off to another # socket so we can go back to selecting. We'll append this # new socket to the read list we select on next pass client, address = server.accept() toread.append( client ) # select on this socket next time else: # Not the server's socket, so we'll read data = s.recv( 1024 ) if data: print "Received %s" % ( data ) else: print "Client disconnected" s.close() # remove socket so we don't watch an invalid # descriptor, decrement client count toread.remove( s ) running = len(toread) - 1 # clean up server.close() 

Тем не менее, я по-прежнему считаю, что socketserver чище и проще. Внедрить handle_request и вызвать serve_forever

Вот реализация сервера Epoll (неблокирующая)

http://pastebin.com/vP6KPTwH (то же самое, что и ниже, считал, что это может быть проще для копирования)

используйте python epollserver.py для запуска сервера.

Проверьте его с помощью wget localhost:8888

 import sys
 импорт, выберите
 import fcntl
 импортировать email.parser
 импортировать StringIO
 import datetime


 «»»
 Видеть:
 http://docs.python.org/library/socket.html
 «»»

 __author__ = ['Caleb Burns', 'Ben DeMott']

 def main (argv = None):
     EOL1 = '\ n \ n'
     EOL2 = '\ n \ r \ n'
     response = 'HTTP / 1.0 200 OK \ r \ nДата: Пн, 1 янв 1996 01:01:01 GMT \ r \ n'
     response + = 'Content-Type: text / plain \ r \ nContent-Length: 13 \ r \ n \ r \ n'
     ответ + = 'Привет, мир!'
     serversocket = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
     # Сообщите дескриптору файла сокета сервера, чтобы уничтожить его, когда закончится эта программа.
     socketFlags = fcntl.fcntl (serversocket.fileno (), fcntl.F_GETFD)
     socketFlags | = fcntl.FD_CLOEXEC
     fcntl.fcntl (serversocket.fileno (), fcntl.F_SETFD, socketFlags)

     serversocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     serversocket.bind (('0.0.0.0', 8888))
     serversocket.listen (1)
     # Используйте асинхронные сокеты.
     serversocket.setblocking (0)
     # Разрешить очередь до 128 запросов (соединений).
     serversocket.listen (128)
     # Слушайте события сокета на сокете сервера, определенные вышеуказанным вызовом bind ().
     epoll = select.epoll ()
     epoll.register (serversocket.fileno (), select.EPOLLIN)
     print "Начался запуск сервера Epoll ..."

     пытаться:
         # Словарь соединений сопоставляет файловые дескрипторы (целые числа) с соответствующими объектами сетевого подключения.
         connections = {}
         request = {}
         ответы = {}
         в то время как True:
             # Спросите epoll, если в каких-либо сокетах есть события и подождите до 1 секунды, если события не присутствуют.
             events = epoll.poll (1)
             # fileno - это дескриптор файла.
             # event - код события (тип).
             для fileno, событие в событиях:
                 # Проверьте наличие события чтения в сокете, потому что может присутствовать новое соединение.
                 if fileno == serversocket.fileno ():
                     # connection - новый объект сокета.
                     # адрес - это IP-адрес клиента.  Формат адреса зависит от семейства адресов сокета (например, AF_INET).
                     соединение, адрес = serversocket.accept ()
                     # Установите новое сокет-соединение в неблокирующий режим.
                     connection.setblocking (0)
                     # Прослушивание событий чтения в новом сокет-соединении.
                     epoll.register (connection.fileno (), select.EPOLLIN)
                     соединения [connection.fileno ()] = соединение
                     request [connection.fileno ()] = b ''
                     ответы [connection.fileno ()] = ответ
                 # Если произошло событие чтения, прочитайте новые данные, отправленные с клиента.
                 elif event & select.EPOLLIN:
                     запросы [fileno] + = соединения [fileno] .recv (1024)
                     # Как только мы закончим чтение, перестанем слушать события чтения и начнем прослушивать события EPOLLOUT (это скажет нам, когда мы сможем отправить данные обратно клиенту).
                     если EOL1 в запросах [fileno] или EOL2 в запросах [fileno]:
                         epoll.modify (fileno, select.EPOLLOUT)
                         # Распечатайте данные запроса на консоль.
                         epoll.modify (fileno, select.EPOLLOUT)

                         data = запросы [fileno]
                         eol = data.find ("\ r \ n") # Это конец строки FIRST
                         start_line = data [: eol] #get содержимое первой строки (которая является информацией протокола)
                         # метод POST | GET и т. д.
                         метод, uri, http_version = start_line.split ("")
                         # re-used facebook httputil library (хорошо работает для нормализации и разбора заголовков)
                         headers = HTTPHeaders.parse (данные [eol:])
                         print "\ nCLIENT: FD:% s% s: '% s'% s"% (fileno, method, uri, datetime.datetime.now ())


                 # Если клиент готов к приему данных, отправьте ответ.
                 elif event & select.EPOLLOUT:
                     # Отправить ответ на один бит за раз, пока не будет отправлен полный ответ.
                     # ПРИМЕЧАНИЕ. Здесь мы будем использовать sendfile ().
                     byteswritten = connections [fileno] .send (ответы [fileno])
                     ответы [fileno] = ответы [fileno] [byteswritten:]
                     если len (ответы [fileno]) == 0:
                         # Скажите сокет, что мы больше не заинтересованы в событиях чтения / записи.
                         epoll.modify (fileno, 0)
                         # Сообщите клиенту, что мы закончили отправку данных, и он может закрыть соединение.  (хорошая форма)
                         соединения [fileno] .shutdown (socket.SHUT_RDWR)
                 # EPOLLHUP (зависание) означает, что клиент отключился, чтобы очистить / закрыть сокет.
                 elif event & select.EPOLLHUP:
                     epoll.unregister (fileno)
                     соединения [fileno] .close ()
                     del connections [fileno]
     в конце концов:
         # Закройте оставшийся открытый сокет после завершения программы.
         epoll.unregister (serversocket.fileno ())
         epoll.close ()
         serversocket.close ()


 #! / usr / bin / env python
 #
 # Copyright 2009 Facebook
 #
 # Лицензия на лицензию Apache, версия 2.0 («Лицензия»);  вы можете
 # не использовать этот файл, кроме как в соответствии с Лицензией.  Вы можете получить
 # копия Лицензии на
 #
 # http://www.apache.org/licenses/LICENSE-2.0
 #
 # Если это не предусмотрено действующим законодательством или не согласовано в письменной форме, программное обеспечение
 #, распространяемый по лицензии, распространяется по ОС «AS IS» БЕЗ
 # ГАРАНТИИ ИЛИ УСЛОВИЯ ЛЮБОГО ВИДА, явные или подразумеваемые.  См.
 # Лицензия на конкретный язык, регулирующий разрешения и ограничения
 # по лицензии.

 "" "Код утилиты HTTP, разделяемый клиентами и серверами." ""

 class HTTPHeaders (dict):
     "" "Словарь, который поддерживает Http-Header-Case для всех ключей.

     Поддерживает несколько значений на ключ с помощью пары новых методов,
     add () и get_list ().  Регулярный интерфейс словаря возвращает один
     значение на ключ, с несколькими значениями, соединенными запятой.

     >>> h = HTTPHeaders ({"content-type": "text / html"})
     >>> h.keys ()
     ['Тип содержимого']
     >>> h ["Content-Type"]
     'Текст / html'

     >>> h.add («Set-Cookie», «A = B»)
     >>> h.add («Set-Cookie», «C = D»)
     >>> h ["set-cookie"]
     'А = В, C = D'
     >>> h.get_list ("set-cookie")
     ['A = B', 'C = D']

     >>> для (k, v) в отсортированном (h.get_all ()):
     ... print '% s:% s'% (k, v)
     ...
     Content-Type: text / html
     Set-Cookie: A = B
     Set-Cookie: C = D
     «»»
     def __init __ (self, * args, ** kwargs):
         # Не пропускайте args или kwargs для dict .__ init__, поскольку он будет обходить
         # наш __setitem__
         ДИКТ .__ __ INIT (Я)
         self._as_list = {}
         self.update (* args, ** kwargs)

     # новые общедоступные методы

     def add (self, name, value):
         "" "Добавляет новое значение для данного ключа." ""
         norm_name = HTTPHeaders._normalize_name (имя)
         если norm_name в себе:
             # обход нашего переопределения __setitem__, так как он изменяет _as_list
             dict .__ setitem __ (self, norm_name, self [norm_name] + ',' + значение)
             self._as_list [norm_name] .append (значение)
         еще:
             self [norm_name] = значение

     def get_list (self, name):
         "" "Возвращает все значения для данного заголовка в виде списка." ""
         norm_name = HTTPHeaders._normalize_name (имя)
         return self._as_list.get (norm_name, [])

     def get_all (self):
         "" "Возвращает итерацию всех (имя, значение) пар.

         Если заголовок имеет несколько значений, несколько пар будут
         с тем же именем.
         «»»
         для имени, списка в self._as_list.iteritems ():
             для значения в списке:
                 доходность (имя, стоимость)


     def items (self):
         return [{ключ: значение [0]} для ключа, значение в self._as_list.iteritems ()]

     def get_content_type (self):
         return dict.get (self, HTTPHeaders._normalize_name ('content-type'), None)

     def parse_line (self, line):
         "" "Обновляет словарь одной строкой заголовка.

         >>> h = HTTPHeaders ()
         >>> h.parse_line («Content-Type: text / html»)
         >>> h.get ('content-type')
         'Текст / html'
         «»»
         name, value = line.split (":", 1)
         self.add (name, value.strip ())

     @classmethod
     def parse (cls, headers):
         "" "Возвращает словарь из текста заголовка HTTP.

         >>> h = HTTPHeaders.parse («Content-Type: text / html \\ r \\ nContent-Length: 42 \\ r \\ n")
         >>> sorted (h.iteritems ())
         [('Content-Length', '42'), ('Content-Type', 'text / html')]
         «»»
         h = cls ()
         для строки в headers.splitlines ():
             если строка:
                 h.parse_line (линия)
         return h

     переопределение # dict

     def __setitem __ (self, name, value):
         norm_name = HTTPHeaders._normalize_name (имя)
         dict .__ setitem __ (self, norm_name, value)
         self._as_list [norm_name] = [значение]

     def __getitem __ (self, name):
         return dict .__ getitem __ (self, HTTPHeaders._normalize_name (имя))

     def __delitem __ (self, name):
         norm_name = HTTPHeaders._normalize_name (имя)
         dict .__ delitem __ (self, norm_name)
         del self._as_list [norm_name]

     def get (self, name, default = None):
         return dict.get (self, HTTPHeaders._normalize_name (имя), по умолчанию)

     def update (self, * args, ** kwargs):
         # dict.update обходит наш __setitem__
         для k, v в dict (* args, ** kwargs) .iteritems ():
             self [k] = v

     @staticmethod
     def _normalize_name (имя):
         "" "Преобразует имя в Http-Header-Case.

         >>> HTTPHeaders._normalize_name ("coNtent-TYPE")
         'Тип содержимого'
         «»»
         return "-". join ([w.capitalize () для w в name.split ("-")])


 if (__ name__ == '__main__'):
     sys.exit (основной (sys.argv))