This program has several socket usage errors.
1) In TCP, bytes are sent and received, not complete messages. There is no guarantee that a send() will send the entire provided string, nor that recv() will receive a complete message, let alone the specified total bytes, which is only a maximum.
2) You need to look at the return value of send() to see how many bytes were actually sent; if not all, you need to call send() again to send the rest, usually you do this in a loop.
3) The same goes for recv(), but in the case of messages of variable size, you need to decide how you will detect the "end of the message" - whether it is a line break, or an X number of bytes, or another criterion. There is no equivalent of the Radioamador saying "over" or "exchange" :)
4) In both send() and recv() it is necessary to test whether it sent or received zero bytes. This means that the connection has been closed. In the case of send(), a return less than zero means an error, which usually happens when trying to use an already closed connection. (In server recv() you are already doing this check.)
5) This is not an error, it is a technique: for a server to serve several clients, one must use either threads (a thread taking care of each connection socket) or asynchronous programming (using select()).
Improved client (not perfect, but will serve multiple customers at the same time, which is what you want):
from socket import *
import time
serverHost = 'localhost'
serverPort = 50007
# Adotei o caractere \n como fim de mensagem
mensagem = [b'Ola mundo da internet!\n', b'bla\n', b'ble\n']
sockobj = socket(AF_INET, SOCK_STREAM)
sockobj.connect((serverHost, serverPort))
for linha in mensagem:
time.sleep(1)
while linha:
enviado = sockobj.send(linha)
if enviado == 0:
print("Servidor fechou")
break
# corta parte da linha ja enviada
linha = linha[enviado:]
data = b''
# Adotei o caractere \n como fim de mensagem
while b'\n' not in data:
newdata = sockobj.recv(1024)
if not newdata:
print('Servidor fechou conexao')
break
data += newdata
print('Cliente recebeu:', data)
sockobj.close()
Enhanced server:
from socket import *
import time
import threading
def cuida_cliente(conexao, endereco):
print('Server conectado a', endereco)
aberto = True
while aberto:
data = b''
while b'\n' not in data:
newdata = conexao.recv(1024)
if not newdata:
print("Cliente fechou")
data = b''
aberto = False
break
data += newdata
if not data:
break
msg = b'Eco=>' + data + b'\n'
while msg:
enviado = conexao.send(msg)
if enviado <= 0:
print("Cliente fechou")
aberto = False
break
msg = msg[enviado:]
conexao.close()
return
meuHost = ''
minhaPort = 50007
sockobj = socket(AF_INET, SOCK_STREAM)
sockobj.bind((meuHost, minhaPort))
sockobj.listen(5)
while True:
conexao, endereco = sockobj.accept()
t = threading.Thread(target=cuida_cliente, args=(conexao, endereco))
t.start()
By complete, I will include a second version of the server, based on select(), which does not need threads and works completely asynchronously. Note that it is much more complex, but is considered much better.
from socket import *
import time
import select
# Estrutura que representa um cliente: [socket, buffer recv, buffer send]
clientes = []
# Retorna índice de "clientes" cujo socket seja igual ao passado
def acha_cliente(socket):
for i, cliente in enumerate(clientes):
if socket is cliente[0]:
return i
return -1
# Cria um cliente novo
def abre_cliente(conexao, endereco):
print('Server conectado a', endereco)
clientes.append([conexao, b'', b''])
# Fecha e exclui cliente com erro
def erro_cliente(socket):
i = acha_cliente(socket)
if i >= 0:
clientes[i][0].close()
del clientes[i]
return
def recv_cliente(socket):
i = acha_cliente(socket)
if i < 0:
socket.close()
return
newdata = socket.recv(1024)
if not newdata:
print("Cliente fechou")
clientes[i][0].close()
del clientes[i]
return
clientes[i][1] += newdata
if b'\n' in clientes[i][1]:
# Recebeu msg completa do cliente
# Coloca mensagem para envio no buffer de transmissão
clientes[i][2] = b'Eco=>' + clientes[i][1] + b'\n'
# Limpa buffer de recepcão
clientes[i][1] = b''
def send_cliente(socket):
i = acha_cliente(socket)
if i < 0:
socket.close()
return
# Tenta enviar todo o buffer de transmissão
enviado = socket.send(clientes[i][2])
if enviado <= 0:
print("Cliente fechou")
clientes[i][0].close()
del clientes[i]
return
# Remove parte já enviada do buffer
clientes[i][2] = clientes[i][2][enviado:]
meuHost = ''
minhaPort = 50007
sockobj = socket(AF_INET, SOCK_STREAM)
sockobj.bind((meuHost, minhaPort))
sockobj.listen(5)
while True:
ler = [sockobj]
gravar = []
erro = [sockobj]
for cliente in clientes:
# Todo cliente pode enviar dados quando quiser
ler.append(cliente[0])
erro.append(cliente[0])
if cliente[2]:
# Dados pendentes para envio ao cliente
gravar.append(cliente[0])
ler, gravar, erro = select.select(ler, gravar, erro, 10)
if not ler and not gravar and not erro:
print("Timeout")
continue
# Despacha erros
for socket in erro:
erro_cliente(socket)
# Despacha leituras
for socket in ler:
if socket is sockobj:
# Socket principal do servidor
conexao, endereco = sockobj.accept()
abre_cliente(conexao, endereco)
else:
recv_cliente(socket)
# Despacha gravações
for socket in gravar:
send_cliente(socket)
I tried to make a single version with Threads. I edited the question. What do you think?
– Ed S
Sorry! I hadn’t thought about it! I’m back to the previous state! Threads server code -> https://pastebin.com/4Bmajhk6
– Ed S
would show how to fix the "no more customers" part. I’m trying to learn by correcting what I tried to do. sometimes seeing something ready, I don’t think my mistake!
– Ed S
I’ll create new question to not pollute here, ok?
– Ed S
created separate question to not pollute here -> https://answall.com/questions/289952/revis%C3%a3o-de-c%C3%b3digo-server-with-threads-to-handle-with-m%C3%baltiplos-clients
– Ed S
I started answering but I don’t have time for more, I’m going to dinner now: https://pastebin.com/g6V3rLUV I’m going to delete the comments so it doesn’t get messy. @Eds
– Miguel