¿Cómo puedo volver a enviar datos a un objeto SocketConnect sin crear un nuevo socket?
Tengo una función de Python a la que se puede acceder a través de una conexión TCP en el puerto 1234. En Wolfram Language, tengo el siguiente código:
ClearAll[getResFromPython];
getResFromPython[sock_][arg_String] := (
WriteString[sock, ExportString[<|"arg" -> arg|>, "JSON"]];
ReadString[sock]
);
Dado que la creación de un nuevo socket no es exactamente instantánea, toma casi dos segundos en mi computadora, me gustaría hacer lo siguiente:
ClearAll[sock, vals];
sock = SocketConnect[{"localhost", 1234}, "TCP"];
vals = Map[getResFromPython[sock], {"arg1", "arg2", "arg3"}];
Close[sock];
(* Now do something with vals. *)
Esto no funciona. Solo "arg1"se evalúa correctamente, y creo que es porque el socket debe vaciarse antes de que se escriba el siguiente argumento. Pero no puedo encontrar ninguna función en la documentación que limpie el socket. Hay una WSFlushfunción, pero tiene un propósito completamente diferente.
¿Cuál es la forma correcta de reutilizar una conexión de socket sobre una lista de argumentos?
Editar
En caso de que alguien esté interesado, aquí está mi sencilla configuración de Python:
# tcpserver.py
import socketserver
import urllib.parse
import json
def getVal(arg):
return 1
class TCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.data = json.loads(self.request.recv(2048).strip())
res = getVal(self.data["arg"])
self.request.sendall(bytes(json.dumps({"res": res}), "utf-8"))
if __name__ == "__main__":
HOST, PORT = "localhost", 1234
with socketserver.TCPServer((HOST, PORT), SASTCPHandler) as server:
server.serve_forever()
Respuestas
He estado lidiando con esto por un tiempo y finalmente me di cuenta de que el problema no es Mathematica. TCPHandlerparece matar la conexión después del primero handle(). No mantiene viva una conexión. Puede seguir leyendo y usar el código al final de mi respuesta, o puede intentar conectarse una vez y escribir una vez por lotes. Esto significa que escribe todos los datos juntos para una conexión utilizando la forma secuencial de WriteString[sock, string1, string2, ..., stringn].
Si usa esta implementación de servidor básica a continuación, verá que Mathematica es bastante capaz de múltiples WriteStringen el mismo socket y una sola conexión:
import socket
from time import sleep
TCP_IP = '127.0.0.1'
TCP_PORT = 1234
BUFFER_SIZE = 2048
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
connection, addr = s.accept()
while True:
try:
connection.send("keeping alive".encode())
data = connection.recv(BUFFER_SIZE)
if data:
print("received data:" + str(data))
connection.send("got it!".encode())
else:
sleep(0.06)
except KeyboardInterrupt:
connection.close()
except socket.error:
print("client disconnected")
connection.close()
break
... y de Mathematica:
sock = SocketConnect[{"localhost", 1234}, "TCP"];
WriteString[sock, "Hey!"]
Print@ByteArrayToString@SocketReadMessage[sock]
WriteString[sock, "What's up?"]
Print@ByteArrayToString@SocketReadMessage[sock]
Close[sock]
Debería ver el resultado del lado del servidor:
received data:b'Hey!'
received data:b"What's up?"
client disconnected
Entonces sabemos que es posible. Echemos un vistazo a un volcado de paquetes de Wireshark para que TCPServerpueda ver qué está mal. Supervisé la interfaz de bucle invertido en lugar de Ethernet o Wi-Fi para producir la traza.
Parece que el servidor es el que termina la conexión. El servidor envía un FINpaquete justo antes de que Mathematica esté a punto de enviar el segundo elemento. Mathematica no lo esperaba y continúa enviando el segundo elemento (el [PSH, ACK] final en la traza) . Mathematica no finaliza la conexión y cumple con el, FINpor lo que el servidor envía un RSTy cancela la conexión de manera dura, sin manejar las solicitudes restantes. Después de TCPHandler.handle()regresar de procesar el primer artículo, TCPServerno mantiene la conexión.
Si desea mantener viva la conexión durante el handle(), tendrá que crear un bucle y un retraso de encuesta como este:
# tcpserver.py
import socketserver
import urllib.parse
import json
from time import sleep
def getVal(arg):
return 1
class TCPHandler(socketserver.BaseRequestHandler):
def handle(self):
alive = True
while(alive):
try:
self.data = self.request.recv(2048)
if self.data:
jsonresult = json.loads(self.data.strip())
res = getVal(jsonresult["arg"])
print(jsonresult)
self.request.sendall(bytes(json.dumps({"res": res}), "utf-8"))
else:
print("no data")
time.sleep(0.06) # 60ms delay
continue
except:
print("finished!")
alive = False
if __name__ == "__main__":
HOST, PORT = "localhost", 1234
with socketserver.TCPServer((HOST, PORT), TCPHandler) as server:
server.socket.settimeout(None)
server.serve_forever()
... y use la siguiente pequeña modificación en su código de Mathematica (he usado en SocketReadMessagelugar de ReadString) :
ClearAll[getResFromPython];
getResFromPython[sock_][arg_String] := (
WriteString[sock, ExportString[<|"arg" -> arg|>, "JSON"]];
ByteArrayToString@SocketReadMessage[sock]);
ClearAll[sock, vals];
sock = SocketConnect[{"localhost", 1234}, "TCP"];
vals = Map[getResFromPython[sock], {"arg1", "arg2", "arg3"}];
Close[sock];
¡Finalmente funciona!
{'arg': 'arg1'}
{'arg': 'arg2'}
{'arg': 'arg3'}
no data
finished!
También funciona con el envío de múltiples solicitudes con demoras entre las cuales puede hacer esto:
sock = SocketConnect[{"localhost", 1234}, "TCP"];
vals = Reap[Do[
Sow[Map[getResFromPython[sock], {"arg1", "arg2", "arg3"}]];
Pause[1];
, 5]] // Last // First
Close[sock];