问题
想在flask编写的服务器程序实现这样一个功能,与底层硬件通信。底层硬件是网络接口,通过TCP/UDP协议进行数据传输。因此服务器需要使用socket。然而flask的socket是websocket,它虽然从socket衍生出来,但仅用于与客服端进行数据交互。怎么办?
解决办法
- socket加还是要加的。socket有两种用法,一种是在客户端的用法,一种是服务端的用法。如果硬件是服务端,需要flask的程序向服务端请求数据,这个直接加socket就行
@socketio.on('needdata')
def onNeeddata():
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)#建立一个socket
sock.connect(hardware_addr)#连接到硬件
sock.send(cmd)#向硬件发送指令
data=sock.recv(1024)#接收硬件的数据
socketio.emit("givedata",data)#发给客户端
- 然而如果硬件是客户端,又如何呢? 这样socket的程序就相当于开了两个服务器,一个连接客户端,一个是连接硬件
对于上图中的通信B很容易搞定,下面主要是解决通信A的问题。
- 基本的socket服务端程序
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)#新建socket
sock.bind(address)#绑定地址
sock.listen(30)
connection, addr=sock.accept()#等待客户端发送消息,此时程序阻塞在这,如果收到消息会往下执行。
msg=connection.recv(1024)#通过新生成的connection来接收消息,connection也是一个socket
connection.sendto(data,client_address)#通过新生成的connection来发送数据
- 可处理多个连接的socket服务端程序
基本的用法只能一次接收一个客户端的连接,并且也程序也有阻塞,若没收到消息,则程序一直原地踏步。要处理多个连接,可以设置一个主循环,来不断的接收消息,还不断的发送数据,并且同时处理多个连接。
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)#新建socket
sock.setblocking(False)#设置成非阻塞
sock.bind(address)#绑定地址
sock.listen(30)
CLIENT=[]#客户的列表
while True:
if stop():#退出循环
break
try:
connection, addr=sock.accept()
CLIENT.append((connection,addr))
except BlockingIOError:#有阻塞异常,可以pass掉
pass
for c_socket,c_addr in client_list:
try:
msg=c_socket.recv(1024)#接收数据
except (BlockingIOError, ConnectionResetError):
pass
c_socket.sendto(data,c_addr )#发送数据
#清理连接到客户端的socket
for c_socket,c_addr in client_list:
c_socket.close()
client_list.remove((c_socket,c_addr))
sock.close()#关闭服务器的socket
- socket加线程
flask框架其实也有一个大循环,两个大循环要合并的话,就需要通过线程来实现。将通信A的socket放到线程里,可以将socket的连接客户端的程序做一个线程,接收数据又是一个线程。
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)#设置成非阻塞
CLIENT=[]
app=Flask(__name__)
#连接线程
def connect():
while True:
if stop():
break
try:
connection, addr=sock.accept()
CLIENT.append((connection, addr))
print("接收到新模块 其地址为:",addr,"目前模块有",len(CLIENT),"个")
except BlockingIOError:
pass
def receive():
while True:
if stop():
break
for c_socket,c_addr in CLIENT:
try:
msg=c_socket.recv(1024)
except (BlockingIOError, ConnectionResetError):
pass
@app.route('/')
def index():
sock.bind(server_address)
sock.listen(30)
heartbeat()
connect_run=threading.Thread(target=connect)#连接客户端线程
receive_run=threading.Thread(target=receive)#接收数据线程
connect_run.start()#启动连接客户端线程
receive_run.start()#启动接收数据线程
return render_template('index.html')
#开启flask服务器
if __name__=="__main__":
socketio.run(app, debug=True, host=SERVER_IP, port=SERVER_PORT)
for c_socket,c_addr in CLIENT:
c_socket.close()
CLIENT.remove((c_socket,c_addr))
sock.close()
需要注意的是socket的bind函数和listen函数必须在flask里面的某个路径函数里,如果放在开启flask前面就开启线程,则flask无法开启,具体原因正在查明