非阻塞的Python套接字

8
我想写一个小的蓝牙服务器应用程序,使用 PyS60 在我的 Nokia 手机上。它需要能够响应客户端请求并能够向客户端推送数据。 选项1: 如果我使用socket.recv(1024),程序会等待接收到消息,因此服务器无法向客户端推送数据。Python for S60 实现缺少socket.settimeout() 方法,所以我无法编写适当的非阻塞代码。 选项2socket.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

很明显这是阻塞的,因此“一些其他数据”从未被发送,但这是我目前为止最好的办法。至少我可以回复客户端并发送一些内容。
3个回答

3

终于找到解决方案了!

在较新的PyS60端口中,btsocket模块的select函数不起作用。有人写了一个新的btsocket(可在此处获得)带有工作的select函数。


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。


谢谢,但那不是我需要的。我只需要连接一个客户端(通过蓝牙),因此阻塞套接字直到客户端连接不会有任何问题。连接建立后,服务器应该发送和接收数据。我不明白为什么在.setblocking(0)模式下recv()会阻塞套接字。select似乎也会阻塞。 - b_m

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

""" 请参阅: 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) # 告诉服务器套接字文件描述符在程序结束时销毁自己。 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 Server Started..."
try: # 连接字典将文件描述符(整数)映射到其对应的网络连接对象。 connections = {} requests = {} responses = {} while True: # 询问epoll是否有任何套接字事件,并在没有事件时等待最多1秒钟。 events = epoll.poll(1) # fileno是文件描述符。 # event是事件代码(类型)。 for fileno, event in events: # 检查套接字上的读取事件,因为可能存在新连接。 if fileno == serversocket.fileno(): # connection是一个新的套接字对象。 # address是客户端IP地址。地址的格式取决于套接字的地址族(即AF_INET)。 connection, address = serversocket.accept() # 将新的套接字连接设置为非阻塞模式。 connection.setblocking(0) # 在新的套接字连接上监听读取事件。 epoll.register(connection.fileno(), select.EPOLLIN) connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = response # 如果发生读取事件,则从客户端读取发送的新数据。 elif event & select.EPOLLIN: requests[fileno] += connections[fileno].recv(1024) # 一旦我们完成了读取,停止监听读取事件并开始监听EPOLLOUT事件(这将告诉我们何时可以开始向客户端发送数据)。 if EOL1 in requests[fileno] or EOL2 in requests[fileno]: epoll.modify(fileno, select.EPOLLOUT) # 将请求数据打印到控制台。 epoll.modify(fileno, select.EPOLLOUT)
data = requests[fileno] eol = data.find("\r\n") #这是第一行的结尾 start_line = data[:eol] #获取第一行的内容(即协议信息) # method是POST|GET等 method, uri, http_version = start_line.split(" ") # 重用Facebook的httputil库(很好地规范化和解析标头) headers = HTTPHeaders.parse(data[eol:]) print "\nCLIENT: FD:%s %s: '%s' %s" % (fileno, method, uri, datetime.datetime.now())
# 如果客户端准备好接收数据,则将其发送出响应。 elif event & select.EPOLLOUT: # 逐位

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接