7

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

选项 1:如果我使用socket.recv(1024),程序会等待直到收到某些内容,因此服务器无法将数据推送到客户端。S60 的 Python 实现缺少该socket.settimeout()方法,因此我无法编写正确的非阻塞代码。

oprion 2:该socket.makefile()方法看起来不错,但无法使其发挥作用。当我更换conn.recv(1024)to 时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

它显然是阻塞的,所以永远不会发送“一些其他数据”,但这是我迄今为止最好的。至少我可以发送一些东西来回复客户。

4

3 回答 3

2

终于找到解决方案了!

选择功能不适用于较新的 PyS60 端口的 btsocket 模块。有人编写了一个带有工作选择功能的 new_btsocket(可在此处获得)。

于 2011-12-08T21:42:07.113 回答
1

这是一个基于回显服务器的简单示例

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

于 2011-12-06T18:49:52.857 回答
0

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

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

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

使用它进行测试wget localhost:8888

导入系统
导入套接字,选择
导入 fcntl
导入 email.parser
导入字符串IO
导入日期时间


"""
看:
http://docs.python.org/library/socket.html
"""

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

定义主(argv=无):
    EOL1 = '\n\n'
    EOL2 = '\n\r\n'
    response = 'HTTP/1.0 200 OK\r\n日期:星期一,1996 年 1 月 1 日 01:01:01 GMT\r\n'
    response += '内容类型:文本/纯文本\r\n内容长度: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 服务器已启动..."

    尝试:
        #连接字典将文件描述符(整数)映射到其对应的网络连接对象。
        连接 = {}
        请求 = {}
        响应 = {}
        而真:
            # 询问 epoll 是否有任何套接字有事件,如果没有事件则等待最多 1 秒。
            事件 = epoll.poll(1)
            # fileno 是一个文件描述符。
            # event 是事件代码(类型)。
            对于文件号,事件中的事件:
                # 检查套接字上的读取事件,因为可能存在新连接。
                如果 fileno == serversocket.fileno():
                    # 连接是一个新的套接字对象。
                    #地址是客户端IP地址。地址的格式取决于套接字的地址族(即AF_INET)。
                    连接,地址 = serversocket.accept()
                    # 将新的套接字连接设置为非阻塞模式。
                    连接.setblocking(0)
                    # 监听新套接字连接上的读取事件。
                    epoll.register(connection.fileno(), select.EPOLLIN)
                    连接[connection.fileno()] = 连接
                    请求[connection.fileno()] = b''
                    响应[connection.fileno()] = 响应
                # 如果发生读取事件,则读取客户端发送的新数据。
                elif 事件 & select.EPOLLIN:
                    请求[文件号] += 连接[文件号].recv(1024)
                    # 一旦我们完成读取,停止侦听读取事件并开始侦听 EPOLLOUT 事件(这将告诉我们何时可以开始将数据发送回客户端)。
                    如果请求 [fileno] 中的 EOL1 或请求 [fileno] 中的 EOL2:
                        epoll.modify(fileno, select.EPOLLOUT)
                        # 将请求数据打印到控制台。
                        epoll.modify(fileno, select.EPOLLOUT)

                        数据=请求[文件号]
                        eol = data.find("\r\n") #这是第一行的结尾
                        start_line = data[:eol] #获取第一行的内容(也就是协议信息)
                        # 方法是 POST|GET 等
                        方法, uri, http_version = start_line.split(" ")
                        # 重用 facebooks httputil 库(可以很好地规范化和解析标头)
                        标头 = HTTPHeaders.parse(数据[eol:])
                        print "\nCLIENT: FD:%s %s: '%s' %s" % (fileno, method, uri, datetime.datetime.now())


                # 如果客户端准备好接收数据,则发送响应。
                elif 事件和 select.EPOLLOUT:
                    # 一次发送一个响应,直到发送完整的响应。
                    # 注意:这是我们将使用 sendfile() 的地方。
                    byteswritten = connections[fileno].send(responses[fileno])
                    响应[fileno] = 响应[fileno][byteswritten:]
                    如果 len(responses[fileno]) == 0:
                        # 告诉套接字我们不再对读/写事件感兴趣。
                        epoll.modify(fileno, 0)
                        # 告诉客户端我们已经完成发送数据,它可以关闭连接。(好形式)
                        连接[fileno].shutdown(socket.SHUT_RDWR)
                # EPOLLHUP(挂起)事件意味着客户端已断开连接,因此清理/关闭套接字。
                elif 事件 & select.EPOLLHUP:
                    epoll.unregister(fileno)
                    连接[文件号].close()
                    删除连接[文件号]
    最后:
        # 程序完成后关闭剩余的打开套接字。
        epoll.unregister(serversocket.fileno())
        epoll.close()
        serversocket.close()


#!/usr/bin/env python
#
# 版权所有 2009 Facebook
#
# 根据 Apache 许可证 2.0 版(“许可证”)获得许可;您可以
# 除非符合许可证,否则不要使用此文件。您可以获得
#许可证的副本在
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则软件
# 根据许可分发是在“原样”基础上分发的,没有
# 任何明示或暗示的保证或条件。见
# 特定语言管理权限和限制的许可证
# 根据许可证。

"""客户端和服务器共享的 HTTP 实用程序代码。"""

类 HTTPHeaders(dict):
    """为所有键维护 Http-Header-Case 的字典。

    通过一对新方法支持每个键的多个值,
    add() 和 get_list()。正则字典接口返回单个
    每个键的值,多个值用逗号连接。

    >>> h = HTTPHeaders({"content-type": "text/html"})
    >>> h.keys()
    ['内容类型']
    >>> h[“内容类型”]
    '文本/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()):
    ... 打印 '%s: %s' % (k,v)
    ...
    内容类型:文本/html
    设置 Cookie:A=B
    设置 Cookie:C=D
    """
    def __init__(self, *args, **kwargs):
        # 不要将 args 或 kwargs 传递给 dict.__init__,因为它会绕过
        # 我们的 __setitem__
        dict.__init__(self)
        self._as_list = {}
        self.update(*args, **kwargs)

    # 新的公共方法

    def add(self, name, value):
        """为给定的键添加一个新值。"""
        norm_name = HTTPHeaders._normalize_name(名称)
        如果在 self 中的 norm_name:
            # 绕过我们对 __setitem__ 的覆盖,因为它修改了 _as_list
            dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
            self._as_list[norm_name].append(value)
        别的:
            自我[规范名称] = 价值

    def get_list(自我,姓名):
        """将给定标头的所有值作为列表返回。"""
        norm_name = HTTPHeaders._normalize_name(名称)
        return self._as_list.get(norm_name, [])

    def get_all(self):
        """返回所有(名称,值)对的可迭代对象。

        如果一个标题有多个值,多对将是
        以相同的名称返回。
        """
        对于名称,请在 self._as_list.iteritems() 中列出:
            对于列表中的值:
                产量(名称,值)


    def 项目(自己):
        返回 [{key: value[0]} for key, value in self._as_list.iteritems()]

    def get_content_type(self):
        返回 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('内容类型')
        '文本/html'
        """
        名称,值 = line.split(":", 1)
        self.add(名称,value.strip())

    @classmethod
    def 解析(cls,标题):
        """从 HTTP 标头文本返回字典。

        >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n")
        >>> 排序(h.iteritems())
        [('Content-Length', '42'), ('Content-Type', 'text/html')]
        """
        h = cls()
        对于 headers.splitlines() 中的行:
            如果行:
                h.parse_line(线)
        返回 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):
        返回 dict.__getitem__(self, HTTPHeaders._normalize_name(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):
        返回 dict.get(self, HTTPHeaders._normalize_name(name), 默认)

    def 更新(自我,*args,**kwargs):
        # dict.update 绕过我们的 __setitem__
        对于 dict(*args, **kwargs).iteritems() 中的 k,v:
            自我[k] = v

    @静态方法
    def _normalize_name(名称):
        """将名称转换为 Http-Header-Case。

        >>> HTTPHeaders._normalize_name("内容类型")
        '内容类型'
        """
        return "-".join([w.capitalize() for w in name.split("-")])


如果(__name__ == '__main__'):
    sys.exit(主要(sys.argv))
于 2011-12-06T19:44:56.660 回答