Non-blocking python sockets

I would like to write a small Bluetooth server application for my Nokia phone in PyS60. He should be able to send a response to a client’s request and be able to also transmit data to the client.

option 1 : if I use socket.recv(1024) , the program waits until something is received, so the server cannot transmit data to the client. The Python implementation for S60 lacks the socket.settimeout() method, so I could not write the correct non-blocking code.

oprion 2 : The socket.makefile() approach looked pretty good, but couldn't make it work. When I replaced conn.recv(1024) with fd = socket.makefile() fd.readline() , it did not read anything.

option 3 : Looked at the select() function, but no luck. When I changed conn.recv() to r,w,e = select.select([conn],[],[]) , as suggested, the client does not even connect. It hangs in "Waiting for a customer ...". Strange ...

I know that there are good server implementations and asynchronous APIs, but I need only basic material. Thanks in advance!

here's what i have:

 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 

It explicitly blocks, so "some other data" is never sent, but this is the best I've received so far. At least I can send something back to the client.

+7
source share
3 answers

Finally found a solution!

The select function did not work with the btsocket module of the new PyS60 ports. Someone wrote a new_btsocket (available here ) with a working select function.

+2
source

Here is a simple example based on an echo server

 #!/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 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() 

However, I still find socketserver to be cleaner and more understandable. Add handle_request and call serve_forever

+1
source

Here's the Epoll server implementation (non-blocking)

http://pastebin.com/vP6KPTwH (same as below), maybe it will be easier to copy)

use python epollserver.py to start the server.

Test it using wget localhost:8888

  import sys
 import socket, select
 import fcntl
 import email.parser
 import StringIO
 import datetime


 "" "
 See:
 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 \ nDate: Mon, 1 Jan 1996 01:01:01 GMT \ r \ n'
     response + = 'Content-Type: text / plain \ r \ nContent-Length: 13 \ r \ n \ r \ n'
     response + = 'Hello, world!'
     serversocket = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
     # Tell the server socket file descriptor to destroy itself when this program ends.
     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)
     # Use asynchronous sockets.
     serversocket.setblocking (0)
     # Allow a queue of up to 128 requests (connections).
     serversocket.listen (128)
     # Listen to socket events on the server socket defined by the above bind () call.
     epoll = select.epoll ()
     epoll.register (serversocket.fileno (), select.EPOLLIN)
     print "Epoll Server Started ..."

     try:
         #The connection dictionary maps file descriptors (integers) to their corresponding network connection objects.
         connections = {}
         requests = {}
         responses = {}
         while True:
             # Ask epoll if any sockets have events and wait up to 1 second if no events are present.
             events = epoll.poll (1)
             # fileno is a file desctiptor.
             # event is the event code (type).
             for fileno, event in events:
                 # Check for a read event on the socket because a new connection may be present.
                 if fileno == serversocket.fileno ():
                     # connection is a new socket object.
                     # address is client IP address.  The format of address depends on the address family of the socket (ie, AF_INET).
                     connection, address = serversocket.accept ()
                     # Set new socket-connection to non-blocking mode.
                     connection.setblocking (0)
                     # Listen for read events on the new socket-connection.
                     epoll.register (connection.fileno (), select.EPOLLIN)
                     connections [connection.fileno ()] = connection
                     requests [connection.fileno ()] = b ''
                     responses [connection.fileno ()] = response
                 # If a read event occured, then read the new data sent from the client.
                 elif event & select.EPOLLIN:
                     requests [fileno] + = connections [fileno] .recv (1024)
                     # Once we're done reading, stop listening for read events and start listening for EPOLLOUT events (this will tell us when we can start sending data back to the client).
                     if EOL1 in requests [fileno] or EOL2 in requests [fileno]:
                         epoll.modify (fileno, select.EPOLLOUT)
                         # Print request data to the console.
                         epoll.modify (fileno, select.EPOLLOUT)

                         data = requests [fileno]
                         eol = data.find ("\ r \ n") #this is the end of the FIRST line
                         start_line = data [: eol] #get the contents of the first line (which is the protocol information)
                         # method is POST | GET, etc
                         method, uri, http_version = start_line.split ("")
                         # re-used facebooks httputil library (works well to normalize and parse headers)
                         headers = HTTPHeaders.parse (data [eol:])
                         print "\ nCLIENT: FD:% s% s: '% s'% s"% (fileno, method, uri, datetime.datetime.now ())


                 # If the client is ready to receive data, sent it out response.
                 elif event & select.EPOLLOUT:
                     # Send response a single bit at a time until the complete response is sent.
                     # NOTE: This is where we are going to use sendfile ().
                     byteswritten = connections [fileno] .send (responses [fileno])
                     responses [fileno] = responses [fileno] [byteswritten:]
                     if len (responses [fileno]) == 0:
                         # Tell the socket we are no longer interested in read / write events.
                         epoll.modify (fileno, 0)
                         # Tell the client we are done sending data and it can close the connection.  (good form)
                         connections [fileno] .shutdown (socket.SHUT_RDWR)
                 # EPOLLHUP (hang-up) events mean the client has disconnected so clean-up / close the socket.
                 elif event & select.EPOLLHUP:
                     epoll.unregister (fileno)
                     connections [fileno] .close ()
                     del connections [fileno]
     finally:
         # Close remaining open socket upon program completion.
         epoll.unregister (serversocket.fileno ())
         epoll.close ()
         serversocket.close ()


 #! / usr / bin / env python
 #
 # Copyright 2009 Facebook
 #
 # Licensed under the Apache License, Version 2.0 (the "License");  you may
 # not use this file except in compliance with the License.  You may obtain
 # a copy of the License at
 #
 # http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 # License for the specific language governing permissions and limitations
 # under the License.

 "" "HTTP utility code shared by clients and servers." ""

 class HTTPHeaders (dict):
     "" "A dictionary that maintains Http-Header-Case for all keys.

     Supports multiple values ​​per key via a pair of new methods,
     add () and get_list ().  The regular dictionary interface returns a single
     value per key, with multiple values ​​joined by a comma.

     >>> h = HTTPHeaders ({"content-type": "text / html"})
     >>> h.keys ()
     ['Content-Type']
     >>> h ["Content-Type"]
     'text / html'

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

     >>> for (k, v) in sorted (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):
         # Don’t pass args or kwargs to dict .__ init__, as it will bypass
         # our __setitem__
         dict .__ init __ (self)
         self._as_list = {}
         self.update (* args, ** kwargs)

     # new public methods

     def add (self, name, value):
         "" "Adds a new value for the given key." ""
         norm_name = HTTPHeaders._normalize_name (name)
         if norm_name in self:
             # bypass our override of __setitem__ since it modifies _as_list
             dict .__ setitem __ (self, norm_name, self [norm_name] + ',' + value)
             self._as_list [norm_name] .append (value)
         else:
             self [norm_name] = value

     def get_list (self, name):
         "" "Returns all values ​​for the given header as a list." ""
         norm_name = HTTPHeaders._normalize_name (name)
         return self._as_list.get (norm_name, [])

     def get_all (self):
         "" "Returns an iterable of all (name, value) pairs.

         If a header has multiple values, multiple pairs will be
         returned with the same name.
         "" "
         for name, list in self._as_list.iteritems ():
             for value in list:
                 yield (name, value)


     def items (self):
         return [{key: value [0]} for key, value in self._as_list.iteritems ()]

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

     def parse_line (self, line):
         "" "Updates the dictionary with a single header line.

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

     @classmethod
     def parse (cls, headers):
         "" "Returns a dictionary from HTTP header text.

         >>> 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 ()
         for line in headers.splitlines ():
             if line:
                 h.parse_line (line)
         return h

     # dict implementation overrides

     def __setitem __ (self, name, value):
         norm_name = HTTPHeaders._normalize_name (name)
         dict .__ setitem __ (self, norm_name, value)
         self._as_list [norm_name] = [value]

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

     def __delitem __ (self, name):
         norm_name = HTTPHeaders._normalize_name (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 (name), default)

     def update (self, * args, ** kwargs):
         # dict.update bypasses our __setitem__
         for k, v in dict (* args, ** kwargs) .iteritems ():
             self [k] = v

     @staticmethod
     def _normalize_name (name):
         "" "Converts a name to Http-Header-Case.

         >>> HTTPHeaders._normalize_name ("coNtent-TYPE")
         'Content-Type'
         "" "
         return "-". join ([w.capitalize () for w in name.split ("-")])


 if (__ name__ == '__main__'):
     sys.exit (main (sys.argv))
0
source

All Articles