非阻塞python套接字

时间:2011-12-06 18:15:13

标签: python sockets pys60

我想在PyS60中为我的诺基亚手机写一个小型蓝牙服务器应用程序。它需要能够发送对客户端请求的响应,并能够将数据推送到客户端。

选项1 : 如果我使用socket.recv(1024),程序会等到收到某些内容,因此服务器无法将数据推送到客户端。 Python for 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-s,但我只需要一个非常基本的东西。提前谢谢!

这就是我所拥有的:

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

它显然是阻塞的,所以“其他一些数据”从未被发送过,但它是迄今为止我所获得的最好的数据。至少我可以发送回复给客户的东西。

3 个答案:

答案 0 :(得分:2)

终于找到了解决方案!

select函数不能与较新的PyS60端口的btsocket模块一起使用。 有人写了一个带有工作选择函数的new_btsocket(可用here)。

答案 1 :(得分:1)

以下是基于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'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

答案 2 :(得分:0)

这是一个Epoll服务器实现(非阻塞)

http://pastebin.com/vP6KPTwH(同样如下,感觉这可能更容易复制)

使用python epollserver.py启动服务器。

使用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 (i.e., 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))