asyncio doesn't send the entire image data over tcp - python

I am trying to send an image from my local computer to a computer in the cloud using asyncio with TCP protocol. Sometimes I get the entire image being sent and sometimes only part of the image gets sent.
client code
import os
os.environ['PYTHONASYNCIODEBUG'] = '1'
import asyncio
import logging
logging.basicConfig(level=logging.ERROR)
async def tcp_echo_client(data, loop):
reader, writer = await asyncio.open_connection(<ip_addr>, <port>,
loop=loop)
print('Sending data of size: %r' % str(len(data)))
writer.write(data)
await writer.drain()
#print("Message: %r" %(data))
print(type(data))
print('Close the socket')
writer.write_eof()
writer.close()
with open('sendpic0.jpg','rb') as f:
data=f.read()
loop = asyncio.get_event_loop()
loop.run_until_complete(tcp_echo_client(data, loop))
loop.close()
server code:
import os
os.environ['PYTHONASYNCIODEBUG'] = '1'
import asyncio
import logging
logging.basicConfig(level=logging.ERROR)
async def handle_echo(reader, writer):
data = await reader.read()
addr = writer.get_extra_info('peername')
#print("Received %r from %r" % (message, addr))
print("Length of data recieved: %r" % (str(len(data))))
#with open('recvpic0.jpg','wb') as f:
# f.write(data)
print("Close the client socket")
writer.close()
#print("Message: %r" %(data))
print("Received data of length: %r" %(str(len(data))))
loop = asyncio.get_event_loop()
data=b''
coro = asyncio.start_server(handle_echo, '', <port_number>, loop=loop)
server = loop.run_until_complete(coro)
print("Received data of length: %r" %(str(len(data))))
# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
I didn't give the ip address and port number on purpose but it shouldn't matter.
Here is the output:
server output
Received data of length: '0'
Serving on ('0.0.0.0', 50001)
Length of data recieved: '249216'
Close the client socket
Received data of length: '249216'
Length of data recieved: '250624'
Close the client socket
Received data of length: '250624'
Length of data recieved: '256403'
Close the client socket
Received data of length: '256403'
client output
$ python client.py
Sending data of size: '256403'
Close the socket
$ python client.py
<class 'bytes'>
Close the socket
$ python client.py
Sending data of size: '256403'
<class 'bytes'>
Close the socket
I am using Python 3.6.
I don't know if I am supposed to have a checking mechanism or send data in chunks? I would assume all that would happen automatically under the read function.
I adjusted the code from this website: http://asyncio.readthedocs.io/en/latest/tcp_echo.html

This looks like the closing-writer bug described in detail in this article.
In short, writer.close is not a coroutine, so you cannot wait for close to actually flush the data from asyncio's buffer to the OS. Awaiting writer.drain() before the close() doesn't help because it only pauses until the background writes reduce the buffer size to a "low watermark", and not - as one might expect - until the buffer is emptied.
UPDATE: As of Python 3.7, released in June 2018, the straightforward fix is to await writer.wait_closed() at the end of tcp_echo_writer.
At the time the answer was originally written, the only available fix was to copy the implementation of asyncio.open_connection (not quite as bad as it sounds, since it's in essence a short a convenience function) and add a call to transport.set_write_buffer_limits(0). This will make await writer.drain() actually wait for all data to be written to the OS (which the referenced article argues would be the right thing to do anyway!):
#asyncio.coroutine
def fixed_open_connection(host=None, port=None, *,
loop=None, limit=65536, **kwds):
if loop is None:
loop = asyncio.get_event_loop()
reader = asyncio.StreamReader(limit=limit, loop=loop)
protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
transport, _ = yield from loop.create_connection(
lambda: protocol, host, port, **kwds)
###### Following line added to fix buffering issues:
transport.set_write_buffer_limits(0)
######
writer = asyncio.StreamWriter(transport, protocol, reader, loop)
return reader, writer
Weird that such a bug us hiding out in main asyncio library.
I suspect that most people don't see the bug because they keep the event loop running for a longer time doing other things, so after writer.close() the data eventually gets written out and the socket closed.

Related

server stop receiving msg after 1 msg receive

The idea is to create a server for sending and receiving files for backup, right now the server receives 1 msg from a client in python and another in C++, the problem is, the python client manages to send 1 string and then the server kinda looks, and I have to end the connection, that's for the python client, when I'm trying to send data from the c++ client i got nothing
I'm using Websockets, but my problem seems to be on the try: statement, honestly cant figure it out wheres my problem
sidenote: I'm using quit() to stop my program, but every time I used it I got way too many errors so I had to comment it
Here's my Server.py code
import asyncio
import websockets
import socket
import sqlite3
import sys
def get_ip(): # returns primary private IP only
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
async def handle_connectio(websocket, path): # recive and handle connection from client, would handle json or file data
while True:
try:
async for name in websocket:
#name = await websocket.recv()
print(f"<<< {name}")
#break
except websockets.exceptions.ConnectionClosed:
print (f"Coneecion terminada")
#quit()
break
else:
print (f"algo paso")
#quit()
break
print ("Iniciando el Server webSocket")
print ("Current Ip: " + get_ip())
servidor = websockets.serve(handle_connectio, get_ip(), 8000)
#loop = asyncio.get_event_loop()
#loop.run_until_complete(servidor)
asyncio.get_event_loop().run_until_complete(servidor)
asyncio.get_event_loop().run_forever()
#async def main(): # main function
# print ("Iniciando Server websocket")
# print("Current Ip: " + get_ip())
# async with websockets.serve(handle_connectio, get_ip(), 8000):
# await asyncio.Future()
#if __name__ == '__main__':
# asyncio.run(main())
edit: I did try to simplify my code and it manages to receive the msg and show when the connection is closed - still the main problem persists.
async def handle_connectio(websocket, path): # recive and handle connection from client, would handle json or file data
try:
while True:
#async for data in websocket:
data = await websocket.recv()
print(f"<<< {data}")
await asyncio.sleep(1)
except websockets.exceptions.ConnectionClosed:
print (f"Coneecion terminada")
edit2: heres my client code, if this donst work i would switch to sockets
import asyncio
import websockets
async def client():
direc = "ws://192.168.1.69:8000"
async with websockets.connect(direc) as web:
while True:
nombre = input("Introduce el mensaje >>> ")
await web.send(nombre)
asyncio.get_event_loop().run_until_complete(client())
From looking at and running the example code at https://websockets.readthedocs.io/en/stable/, it's apparent that your connection handler shouldn't loop forever (while True:) but exit after handling all the messages supplied by the websocket. It will be called again when another message arrives.
Edit:
The original server code works fine. The problem is that the client is using the input() function, which blocks asyncio from running, which prevents websocket protocol from running correctly and blocks messages from sending. A small delay after send (await asyncio.sleep(1)) works, although ideally the input() and the asyncio comm logic would be separated, to avoid an arbitrary delay.
Ok for some odd reason websockets wont work/behave properly so i had to switch to Sockets and now i can send the data back and forth, i would post my client and server code for anyone on the future.
Server.py
import socket
# socket.SOCK_STREAM -> TCP
# socket.SOCK_DGRAM -> UDP
def get_ip(): # returns primary private IP only
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
def servidor():
print (f"Iniciando el Servidor Sockets")
print (f"Current IP Addres: " + get_ip())
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((get_ip(), 8000))
server.listen(1)
conn, address = server.accept() # Accept the Client connection
while True:
#1024 is the bandwidth bits
try:
msg = conn.recv(1024).decode() # Recive the msg and trasform it from Binary to String
print("<<< " + msg)
except:
print (f"coneccion terminada")
break
if __name__ == "__main__":
servidor()
Client.py
import socket
print ('Iniciando cliente')
conn_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
conn_client.connect( ('192.168.1.68', 8000))
while True:
try:
msg = (f">>> ")
conn_client.sendall(msg.encode())
except:
print (f"Connection Close")
break
#recibido = conn_client.recv(1024)
#print (recibido.decode())
conn_client.close()

How to implement logic like sock.recv(), based on python-asyncio's transport & protocol api?

I'm trying to build some simple applications based on asyncio tcp. In traditional socket programming, we use sock.recv() and sock.send() to manage the receiving and sending of sockets, but I noticed that using sockets directly is not recommended according to asyncio documentation, correspondingly, they suggest using the transport abstraction.
I want to know how to use transport to reproduce a logic similar to traditional socket programming. For example I'd like to implement the following logic:
async def main():
loop = asyncio.get_running_loop()
transport, protocal = await loop.create_connection(EchoClientProtocol(), '', 25000)
await transport.write("hello")
await transport.read(5) # Error
....
The above code does not work because transport does not provide a read method in the begining, the read event must be implemented in the corresponding protocol. This prevents me from clearly separating different tcp packages. What is the right way to do it? Thanks.
You can implement TCP server and client using asyncio streams
Edit based on #user4815162342 great suggests:
I increased for read maximum number of bytes on chuck from 1 byte to 8192 bytes, it was my bad idea to use use the smallest possible number in the example and it could be misleading for other people.
In addition BytesIO is much better suited for concatenation than just += bytes. I introduced BytesIO to this code example.
Server Script Example:
import asyncio
import socket
from io import BytesIO
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
print(len(asyncio.all_tasks())) # let's show number of tasks
ip, port = writer.get_extra_info('peername') # get info about incoming connection
print(f"Incoming connection from {ip}: {port}")
# better use BytesIO than += if you gonna concat many times
all_data = BytesIO()
while True:
try:
# read chunk up to 8 kbytes
data = await asyncio.wait_for(reader.read(8192), timeout=2.0)
all_data.write(data)
if reader.at_eof():
print(f"Received data:\n{all_data.getvalue().decode('utf8')}")
break
except (asyncio.CancelledError, asyncio.TimeoutError):
print("Too slow connection aborted")
break
writer.write(b"FROM_SERVER:\n") # prepare data
writer.write(all_data.getvalue()) # prepare more data
# simulate slow server
# await asyncio.sleep(5)
await writer.drain() # send all prepared data
if writer.can_write_eof():
writer.write_eof()
writer.close() # do not forget to close stream
async def main_server():
server = await asyncio.start_server(
client_connected_cb=handler,
host="localhost",
port=8888,
family=socket.AF_INET, # ipv4
)
ip, port = server.sockets[0].getsockname()
print(f"Serving on: {ip}:{port}")
print("*" * 200)
async with server:
await server.serve_forever()
if __name__ == '__main__':
asyncio.run(main_server())
Client Script Example:
import asyncio
from io import BytesIO
async def main():
reader, writer = await asyncio.open_connection(host="localhost", port=8888)
# remove comment to test slow client
# await asyncio.sleep(20)
for i in range(10):
writer.write(f"hello-{i}\n".encode("utf8")) # prepare data
await writer.drain() # send data
if writer.can_write_eof():
writer.write_eof() # tell server that we sent all data
# better use BytesIO than += if you gonna concat many times
data_from_server = BytesIO() # now get server answer
try:
while True:
# read chunk up to 8 kbytes
data = await asyncio.wait_for(reader.read(8192), timeout=1.0)
data_from_server.write(data)
# if server told use that no more data
if reader.at_eof():
break
print(data_from_server.getvalue().decode('utf8'))
writer.close()
except ConnectionAbortedError:
# if our client was too slow
print("Server timed out connection")
writer.close()
except (asyncio.TimeoutError, asyncio.CancelledError):
# if server was too slow
print("Did not get answer from server due to timeout")
writer.close()
if __name__ == '__main__':
asyncio.run(main())

Python3 asyncio: using infinite loop for multiple connections and proper connection closing

I have server, where I need to keep connection with client as long as possible. I need to allow for multiple clients connect to this server. Code:
class LoginServer(BaseServer):
def __init__(self, host, port):
super().__init__(host, port)
async def handle_connection(self, reader: StreamReader, writer: StreamWriter):
peername = writer.get_extra_info('peername')
Logger.info('[Login Server]: Accepted connection from {}'.format(peername))
auth = AuthManager(reader, writer)
while True:
try:
await auth.process()
except TimeoutError:
continue
finally:
await asyncio.sleep(1)
Logger.warning('[Login Server]: closing...')
writer.close()
#staticmethod
def create():
Logger.info('[Login Server]: init')
return LoginServer(Connection.LOGIN_SERVER_HOST.value, Connection.LOGIN_SERVER_PORT.value)
The problem: currently only one client can connect to this server. It seems socket do not closing properly. And because of this even previous client cannot reconnect. I think this is because infinite loop exists. How to fix this problem?
The while loop is correct.
If you wanted a server that waits on data from a client you would have the following loop in your handle_connection.
while 1:
data = await reader.read(100)
# Do something with the data
See the example echo server here for more details on reading / writing.
https://asyncio.readthedocs.io/en/latest/tcp_echo.html
Your problem is likely that this function doesn't return and is looping itself without await'g anything. That would mean the asyncio loop would never regain control so new connections could not be made.
await auth.process()

Python - I am trying to run a main logic (game server) loop concurrently with asyncio start_server waiting for TCP traffic

I have a script at the moment which uses asyncio to listen on a TCP port for any incoming connections, if a connection is received then it parses the data and sets a class variable to the data received.
This works perfect just to wait for incoming connections - but I cannot figure out how to run a loop concurrent to this waiting for an incoming connection. I am building a server to receive input via TCP, and update things stored within caches in a loop in the main() function.
This is what I have at present:
import socket
import binascii
import struct
import asyncio
TCP_IP = '127.0.0.1'
TCP_PORT = 30194
BUFFER_SIZE = 1024
loop = asyncio.get_event_loop()
coro = asyncio.start_server(incomingData, TCP_IP, TCP_PORT)
server = loop.run_until_complete(coro)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Async method to handle incoming connections
async def incomingData(reader, writer):
data = await reader.read(BUFFER_SIZE)
incomingMessage = data.decode()
addr = writer.get_extra_info('peername')
print("Received %r from %r" % (incomingMessage, addr))
# [0] login packet
if ord(incomingMessage[0]) == 0:
username = ""
password = ""
try:
usernameLength = ord(incomingMessage[1]) + 2 # plus two for the 2 chars taken up by the hex
passwordLength = ord(incomingMessage[usernameLength]) + 1 + usernameLength
for x in range(2,usernameLength):
username += incomingMessage[x]
for i in range(usernameLength,passwordLength):
password += incomingMessage[i]
loginHandler.username = username
loginHandler.password = password
except:
print("Unexpected error")
writer.write(data)
await writer.drain()
writer.close()
However, if I add a loop to encompass loop.run_forever() then it only outputs the print line once and never again (I would expect it to print hundreds of times running in a loop) - for instance:
try:
while True:
print("debug print?")
loop.run_forever()
Will print only once in execution - how can I have a logic loop running concurrent to the asyncio TCP start_server loop?
You could maybe try multithreading.
So you would create a thread for the two things you are trying to do.
You can look at this https://www.tutorialspoint.com/python/python_multithreading.htm
This is my first answer.

How to tell if a connection is dead in python

I want my python application to be able to tell when the socket on the other side has been dropped. Is there a method for this?
Short answer:
use a non-blocking recv(), or a blocking recv() / select() with a very
short timeout.
Long answer:
The way to handle socket connections is to read or write as you need to, and be prepared to handle connection errors.
TCP distinguishes between 3 forms of "dropping" a connection: timeout, reset, close.
Of these, the timeout can not really be detected, TCP might only tell you the time has not expired yet. But even if it told you that, the time might still expire right after.
Also remember that using shutdown() either you or your peer (the other end of the connection) may close only the incoming byte stream, and keep the outgoing byte stream running, or close the outgoing stream and keep the incoming one running.
So strictly speaking, you want to check if the read stream is closed, or if the write stream is closed, or if both are closed.
Even if the connection was "dropped", you should still be able to read any data that is still in the network buffer. Only after the buffer is empty will you receive a disconnect from recv().
Checking if the connection was dropped is like asking "what will I receive after reading all data that is currently buffered ?" To find that out, you just have to read all data that is currently bufferred.
I can see how "reading all buffered data", to get to the end of it, might be a problem for some people, that still think of recv() as a blocking function. With a blocking recv(), "checking" for a read when the buffer is already empty will block, which defeats the purpose of "checking".
In my opinion any function that is documented to potentially block the entire process indefinitely is a design flaw, but I guess it is still there for historical reasons, from when using a socket just like a regular file descriptor was a cool idea.
What you can do is:
set the socket to non-blocking mode, but than you get a system-depended error to indicate the receive buffer is empty, or the send buffer is full
stick to blocking mode but set a very short socket timeout. This will allow you to "ping" or "check" the socket with recv(), pretty much what you want to do
use select() call or asyncore module with a very short timeout. Error reporting is still system-specific.
For the write part of the problem, keeping the read buffers empty pretty much covers it. You will discover a connection "dropped" after a non-blocking read attempt, and you may choose to stop sending anything after a read returns a closed channel.
I guess the only way to be sure your sent data has reached the other end (and is not still in the send buffer) is either:
receive a proper response on the same socket for the exact message that you sent. Basically you are using the higher level protocol to provide confirmation.
perform a successful shutdow() and close() on the socket
The python socket howto says send() will return 0 bytes written if channel is closed. You may use a non-blocking or a timeout socket.send() and if it returns 0 you can no longer send data on that socket. But if it returns non-zero, you have already sent something, good luck with that :)
Also here I have not considered OOB (out-of-band) socket data here as a means to approach your problem, but I think OOB was not what you meant.
It depends on what you mean by "dropped". For TCP sockets, if the other end closes the connection either through
close() or the process terminating, you'll find out by reading an end of file, or getting a read error, usually the errno being set to whatever 'connection reset by peer' is by your operating system. For python, you'll read a zero length string, or a socket.error will be thrown when you try to read or write from the socket.
From the link Jweede posted:
exception socket.timeout:
This exception is raised when a timeout occurs on a socket
which has had timeouts enabled via a prior call to settimeout().
The accompanying value is a string whose value is currently
always “timed out”.
Here are the demo server and client programs for the socket module from the python docs
# Echo server program
import socket
HOST = '' # Symbolic name meaning all available interfaces
PORT = 50007 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
data = conn.recv(1024)
if not data: break
conn.send(data)
conn.close()
And the client:
# Echo client program
import socket
HOST = 'daring.cwi.nl' # The remote host
PORT = 50007 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)
On the docs example page I pulled these from, there are more complex examples that employ this idea, but here is the simple answer:
Assuming you're writing the client program, just put all your code that uses the socket when it is at risk of being dropped, inside a try block...
try:
s.connect((HOST, PORT))
s.send("Hello, World!")
...
except socket.timeout:
# whatever you need to do when the connection is dropped
If I'm not mistaken this is usually handled via a timeout.
I translated the code sample in this blog post into Python: How to detect when the client closes the connection?, and it works well for me:
from ctypes import (
CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
c_short, c_ssize_t, c_char, ARRAY
)
__all__ = 'is_remote_alive',
class pollfd(Structure):
_fields_ = (
('fd', c_int),
('events', c_short),
('revents', c_short),
)
MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02
EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040
libc = CDLL('libc.so.6')
recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int
poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int
class IsRemoteAlive: # not needed, only for debugging
def __init__(self, alive, msg):
self.alive = alive
self.msg = msg
def __str__(self):
return self.msg
def __repr__(self):
return 'IsRemoteAlive(%r,%r)' % (self.alive, self.msg)
def __bool__(self):
return self.alive
def is_remote_alive(fd):
fileno = getattr(fd, 'fileno', None)
if fileno is not None:
if hasattr(fileno, '__call__'):
fd = fileno()
else:
fd = fileno
p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
result = poll(p, 1, 0)
if not result:
return IsRemoteAlive(True, 'empty')
buf = ARRAY(c_char, 1)()
result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
if result > 0:
return IsRemoteAlive(True, 'readable')
elif result == 0:
return IsRemoteAlive(False, 'closed')
else:
return IsRemoteAlive(False, 'errored')
Trying to improve on #kay response. I made a more pythonic version
(Note that it was not yet tested in a "real-life" environment, and only on Linux)
This detects if the remote side closed the connection, without actually consuming the data:
import socket
import errno
def remote_connection_closed(sock: socket.socket) -> bool:
"""
Returns True if the remote side did close the connection
"""
try:
buf = sock.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT)
if buf == b'':
return True
except BlockingIOError as exc:
if exc.errno != errno.EAGAIN:
# Raise on unknown exception
raise
return False
Here is a simple example from an asyncio echo server:
import asyncio
async def handle_echo(reader, writer):
addr = writer.get_extra_info('peername')
sock = writer.get_extra_info('socket')
print(f'New client: {addr!r}')
# Initial of client command
data = await reader.read(100)
message = data.decode()
print(f"Received {message!r} from {addr!r}")
# Simulate a long async process
for _ in range(10):
if remote_connection_closed(sock):
print('Remote side closed early')
return
await asyncio.sleep(1)
# Write the initial message back
print(f"Send: {message!r}")
writer.write(data)
await writer.drain()
writer.close()
async def main():
server = await asyncio.start_server(
handle_echo, '127.0.0.1', 8888)
addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
print(f'Serving on {addrs}')
async with server:
await server.serve_forever()
if __name__ == '__main__':
asyncio.run(main())

Categories