2011-12-06 33 views
7

Tôi muốn viết một ứng dụng máy chủ Bluetooth nhỏ cho điện thoại Nokia của mình trong PyS60. Nó cần để có thể gửi phản hồi cho yêu cầu của khách hàng và có thể đẩy dữ liệu đến máy khách.Không chặn các ổ cắm python

tùy chọn 1: nếu tôi sử dụng socket.recv(1024), chương trình đợi cho đến khi một cái gì đó được nhận, do đó máy chủ không thể đẩy dữ liệu cho khách hàng. Việc triển khai Python cho S60 thiếu phương thức socket.settimeout(), vì vậy tôi không thể viết mã không chặn thích hợp.

oprion 2: Cách tiếp cận socket.makefile() có vẻ ổn nhưng không thể hoạt động tốt. Khi tôi thay thế conn.recv(1024) thành fd = socket.makefile() fd.readline(), nó không đọc được gì.

tùy chọn 3: Nhìn vào chức năng select(), nhưng không có may mắn với nó. Khi tôi thay đổi conn.recv() thành r,w,e = select.select([conn],[],[]) như nó đã được đề xuất, khách hàng thậm chí không kết nối. Nó treo ở "Chờ khách hàng ...". Strange ...

Tôi biết rằng có triển khai máy chủ khá đẹp và API không đồng bộ là tốt, nhưng tôi chỉ cần một công cụ thực sự cơ bản ở đây. Cảm ơn trước!

đây là những gì tôi có:

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 

Nó rõ ràng là ngăn chặn, do đó, "một số dữ liệu khác" không bao giờ được gửi đi, nhưng đó là tốt nhất mà tôi đã có cho đến nay. Ít nhất tôi có thể gửi một cái gì đó để trả lời cho khách hàng.

Trả lời

2

Tìm thấy giải pháp cuối cùng!

Chức năng chọn không hoạt động với mô-đun btsocket của các cổng PyS60 mới hơn. Ai đó đã viết một new_btsocket (có sẵn here) với chức năng chọn hoạt động.

1

Dưới đây là một ví dụ đơn giản dựa trên một 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() 

Điều đó nói rằng, tôi vẫn thấy socketserver sạch hơn và dễ dàng hơn. Thực hiện handle_request và gọi serve_forever

+0

Cảm ơn, nhưng đó không phải là những gì tôi cần. Tôi chỉ cần kết nối một máy khách (qua Bluetooth), vì vậy nó không gây ra vấn đề gì để khóa socket cho đến khi máy khách kết nối. Sau khi kết nối được khởi tạo, máy chủ sẽ gửi và nhận dữ liệu. Tôi không hiểu, tại sao recv() chặn socket ở chế độ .setblocking (0). lựa chọn dường như cũng đang chặn. –

0

Dưới đây là một Server thực hiện Epoll (non-blocking)

http://pastebin.com/vP6KPTwH (điều tương tự như dưới đây, cảm thấy điều này có thể được dễ dàng hơn để sao chép)

sử dụng python epollserver.py để khởi động server.

thử nghiệm nó bằng cách sử 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))