I want to send several independent messages from a client to a server, depending on a variable that changes. To simulate the scenario I have created a loop to send several numbers, but only the first iteration is working. In the second iteration (which would be, the second message in real) I am getting this error in the client:
OSError: [WinError 10038] An operation was attempted on something that is not a socket
Do you know how to solve it?
I would like to send several values from my client to the server, using the same socket if possible. The client and the server are implemented on my PC for this example.
Server:
import socket
HOST='127.0.0.1'
PORT=6595
#Code for the socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((HOST,PORT))
server.listen(5)
while True:
communication_socket,address=server.accept()
message=communication_socket.recv(1024).decode('utf-8')
print(f"Message from client is: {message}")
communication_socket.close()
Client:
import socket
HOST='127.0.0.1'
PORT=6595
#Code for the socket
cli=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
i=1
while i<10:
cli.connect((HOST,PORT))
i=str(i)
cli.send(i.encode('utf-8'))
#print(cli.recv(1024).decode('utf-8'))
i=int(i)
i=i 1
cli.close()
print(i)
Error I face:
Traceback (most recent call last):
File "d:\Python projects\collision.py", line 11, in <module>
cli.connect((HOST,PORT))
OSError: [WinError 10038] An operation was attempted on something that is not a socket
CodePudding user response:
If you want to send bytes to a socket, you must have a socket open (server) and be connected to it (client) from the start (your most likely first loop iteration) until the end of whatever you are trying to send (end of loop/array/bytes). Afterwards you are free to decommission the resources be it closing the connection or anything else. In other cases you'll break the tunnel and thus communication halts.
In server part, move this out of the loop, somewhere else:
communication_socket.close()
# e.g.
while True:
...
# detect you have received everything, then break the loop
communication_socket.close()
in client part:
cli.connect((HOST,PORT))
while True:
...
cli.close()
therefore your socket will not be closed if you expect to receive multiple messages with it because the closing part on server will be after the loop, i.e. after you are done with the receiving (whenever that is) and on the client part, close after the loop, once you are done sending.
Full:
# server.py
import socket
HOST='127.0.0.1'
PORT=6595
#Code for the socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((HOST,PORT))
server.listen(5)
communication_socket,address=server.accept()
while True:
message=communication_socket.recv(1024).decode('utf-8')
if not message:
break
print(f"Message from client is: {message}")
communication_socket.close()
# client.py
import socket
HOST='127.0.0.1'
PORT=6595
#Code for the socket
cli=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
i=1
cli.connect((HOST,PORT))
while i<10:
i=str(i)
cli.send(i.encode('utf-8'))
#print(cli.recv(1024).decode('utf-8'))
i=int(i)
i=i 1
print(i)
cli.close()
Message from client is: 1
Message from client is: 2
Message from client is: 3
Message from client is: 4
Message from client is: 5
Message from client is: 6
Message from client is: 7
Message from client is: 8
Message from client is: 9
2
3
4
5
6
7
8
9
10
By accept()
you are accepting a new connection, therefore you'd get only the first byte in your example. Once you move it out, you can send all of the data, however only through one of the connections. For a new connection you'd need another accept()
and depending on your use-case it can be a/synchronous, sequentially or parallel.
By the number in recv()
you are limiting the data. In 1024
your data might be mashed into a single line, or not, depending on your computer's speed. With recv(1)
you should get only single byte, thus separating each on a separate line/print.
Don't forget to check https://docs.python.org/3/library/socket.html#example or have a look at this simple ping-pong. It might give you an idea:
# pingserver.py
import socket
from datetime import datetime
from time import sleep
from random import randrange, choice
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 8000))
sock.listen(1)
conn, addr = sock.accept()
with conn:
data = "."
print(f"I'll start with sending {data!r} to a new connection")
while True:
conn.send(f"{datetime.now()}:S\t{data}".encode())
sleep(randrange(1, 3))
data = conn.recv(1024)
data = "\t".join(data.decode().split("\t")[1:])
if data:
print(f"{datetime.now()}:S\t{data}")
# pingclient.py
import socket
from datetime import datetime
from time import sleep
from random import randrange
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect(("127.0.0.1", 8000))
while True:
sleep(randrange(1, 2))
data = sock.recv(1024)
data = "\t".join(data.decode().split("\t")[1:])
if data:
print(f"{datetime.now()}:C\t{data}")
data = chr(ord(data) 1)
sock.send(f"{datetime.now()}:C\t{data}".encode())
python pingserver.py
python pingclient.py # different terminal
# joint output sortable by time
Alternatively, even though it might be mostly more expensive:
- use
SO_REUSEADDR
and/orSO_REUSEPORT
when opening and closing fast enough causing the OS not being able to clean the address binding or even when spawning multiple sockets on the same binding - use unicast/multicast when applicable
CodePudding user response:
The error is due to the client closing the socket, then trying to connect again with the same socket. The minimal fix is to move the socket creation inside the loop so .connect
uses a new socket
each time.
import socket
HOST='127.0.0.1'
PORT=6595
i=1
while i<10:
cli=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # moved
cli.connect((HOST,PORT))
i=str(i)
cli.send(i.encode('utf-8'))
#print(cli.recv(1024).decode('utf-8'))
i=int(i)
i=i 1
cli.close()
print(i)
Then the code works:
Message from client is: 1
Message from client is: 2
Message from client is: 3
Message from client is: 4
Message from client is: 5
Message from client is: 6
Message from client is: 7
Message from client is: 8
Message from client is: 9
This isn't the efficient solution, though. Normally the client would connect and send all 9 messages, then close:
# server
import socket
server = socket.socket()
server.bind(('', 6595))
server.listen()
with server:
while True:
communication_socket, address = server.accept()
with communication_socket:
while True:
data = communication_socket.recv(1024)
if not data: break
print(f'{data=}')
# client
import socket
cli = socket.socket()
cli.connect(('localhost', 6595))
with cli:
for i in range(1,10):
cli.send(str(i).encode())
Server output:
data=b'123456'
data=b'789'
Here's where the beginner discovers TCP isn't message-based and the 9 individual messages were run together. TCP is a streaming protocol with no concept of message boundaries. Bytes can and do all run together and need a protocol defined to process messages such as "read until newline" or "read 4 bytes for message size and the "message size" bytes. Wrap the socket in a file-like wrapper via socket.makefile
and read lines or specific message packet sizes via .readline
and .read
:
# server
import socket
server = socket.socket()
server.bind(('', 6595))
server.listen()
with server:
while True:
cli, address = server.accept()
with cli, cli.makefile('r', encoding='utf8') as reader:
while True:
data = reader.readline() # reads up to a newline
if not data: break
i = int(data)
print('message:', i)
# client
import socket
cli = socket.socket()
cli.connect(('localhost', 6595))
with cli, cli.makefile('w', encoding='utf8') as writer:
for i in range(1,10):
print(i, file=writer) # print writes a newline after printing values
Server output:
message: 1
message: 2
message: 3
message: 4
message: 5
message: 6
message: 7
message: 8
message: 9