由于一些需求,需要在PC上读取超声波传感器的数据,而超声波传感器使用的是I2C/TTL接口,虽然可以使用TTL转USB来接入PC,但是TTL接口只能够支持同时连接两个超声波传感器,而这里可能需要使用6个。
想到的一只解决办法是将超声波传感器连接到Raspberry PI的GPIO的I2C接口上,然后PC和Raspberry PI使用WIFI进行通信。这里需要在Raspberry PI上启动一个TCP Server,PC通过调用此TCP Server的接口获取超声波传感器的测量数据。虽然也可以让Raspberry PI将超声波传感器的数据发送到PC,但这样就需要让超声波传感器一直处于工作状态,会带来较大的能耗。
Echo Server可以作为一个最简单的TCP Server的模版,通过简单的修改就可以实现其他的TCP通信服务。一般的TCP Echo Server实现大致如下,由于Raspberry PI中的主流编程语言是Python,以下代码均使用Python。
from SocketServer import BaseRequestHandler, TCPServer; class ServerHandler(BaseRequestHandler): def handle(self): data = self.request.recv(1024); self.request.send(data); server = TCPServer(('', 1987), ServerHandler); server.serve_forever();
测试效果如下
Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Hello Hello Connection closed by foreign host.
这里使用了Python中的TCPServer框架,但一般的TCPServer框架所实现的方式基本是类似HTTP Server,即处理完成后就关闭连接,并不能支持连接的服务。而获取超声波传感器的频率可能是比较高的,因此如果每次都重新建立连接会产生较大的开销,同时也增加了测量的延迟。
Epoll是Linux 2.6中支持的路复用IO接口,是对select/poll的增强版本,显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用效率。目前大多数Linux下的应用程序(Nginx Redis等)都使用了Epoll方式进行网络IO操作。同时Epoll提供了水平触发(LT)和边界触发(ET)两种IO事件。
这里我们只能直接使用Epoll调用来实现连接复用的Echo Server,其中关键是将数据输出之后重新调整为接收数据的模式,其实现的代码大致如下
import socket; import select; #开启一个Socket HOST = ''; PORT = 1987 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((HOST, 1987)); sock.listen(1); #初始化Epoll epoll = select.epoll(); epoll.register(sock.fileno(), select.EPOLLIN); #连接和接受数据 conns = {}; recvs = {}; try: while True: #等待事件发生 events = epoll.poll(1); #事件循环 for fd, event in events: #如果监听的Socket有时间则接受新连接 if fd == sock.fileno(): client, addr = sock.accept(); client.setblocking(0); #注册新连接的输入时间 epoll.register(client.fileno(), select.EPOLLIN); conns[client.fileno()] = client; recvs[client.fileno()] = ''; elif event & select.EPOLLIN: #读取数据 while True: try: buff = conns[fd].recv(1024); if len(buff) == 0: break; except: break; recvs[fd] += buff; #调整输出事件 if len(buff) != 0: epoll.modify(fd, select.EPOLLOUT); else: #如果数据为空则连接已断开 epoll.modify(fd, select.EPOLLHUP); elif event & select.EPOLLOUT: #发送数据 try: n = conns[fd].send(recvs[fd]); recvs[fd] = ''; #重新调整为接收数据 epoll.modify(fd, select.EPOLLIN); except: epoll.modify(fd, select.EPOLLHUP); elif event & select.EPOLLHUP: #关闭清理连接 epoll.unregister(fd); conns[fd].close(); del conns[fd]; del recvs[fd]; finally: epoll.unregister(sock.fileno()); epoll.close(); sock.close();
使用Telnet连接上Echo Server,先输入Hello,会返回Hello,再输入Hi,则会继续返回Hi
Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Hello Hello Hi Hi