Programming with client and server: How to get numerous data exchanges per client and numerous clients simultaneously?

3

The client:

from socket import *


serverHost = 'localhost'
serverPort = 50007

# Menssagem a ser mandada codificada em bytes
menssagem = [b'Ola mundo da internet!']

# Criamos o socket e o conectamos ao servidor
sockobj = socket(AF_INET, SOCK_STREAM)
sockobj.connect((serverHost, serverPort))

# Mandamos a menssagem linha por linha
for linha in menssagem:
    sockobj.send(linha)

    # Depois de mandar uma linha esperamos uma resposta
    # do servidor
    data = sockobj.recv(1024)
    print('Cliente recebeu:', data)

# Fechamos a conexão
sockobj.close()

The server:

from socket import *
import time

# Cria o nome do host
meuHost = '' #maquina local = localhost = 127.0.0.1/0.0.0.0

# Utiliza este número de porta
minhaPort = 50007


sockobj = socket(AF_INET, SOCK_STREAM) #AF_INET é IP; SOCK_STREAM = TCP

# Vincula o servidor ao número de porta
sockobj.bind((meuHost, minhaPort))

# O socket começa a esperar por clientes limitando a 
# 5 conexões por vez
sockobj.listen(5)


while True:
    # Aceita uma conexão quando encontrada e devolve a
    # um novo socket conexão e o endereço do cliente
    # conectado
    conexão, endereço = sockobj.accept()
    print('Server conectado por', endereço)

    while True: #só existe 1 troca de dados por cliente conectado!
        # Recebe data enviada pelo cliente
        data = conexão.recv(1024) #recebe 1024 bytes
        # time.sleep(3)

        # Se não receber nada paramos o loop
        if not data: break

        # O servidor manda de volta uma resposta
        conexão.send(b'Eco=>' + data)

    # Fecha a conexão criada depois de responder o
    # cliente
    conexão.close()

Problem: There is only 1 data exchange per connected client! After the first data exchange, the connection is terminated!

How to modify the server in order to allow numerous exchanges of data, until the client resolves to terminate the connection?

Problem 2: The server only handles 1 client at a time: How to make it capable of handling multiple clients simultaneously?

    
asked by anonymous 08.04.2018 / 00:54

2 answers

4

Problem 1: There is not only "1 data exchange per connected client!"; there are several, depending on the size of your list of messages to send / receive by the client.

Problem 2: The server only deals with a connection because you programmed to get processed / only one connection, there is missing as you said, threads, one for each client.

It seems to me that you only want the server to forward the same message to the client that sent it, so these programs (server and client) can be done like this: over the code in comment)

Server:

import socket, threading

def handle_client(client_conn):
    while True:
        data = client_conn.recv(1024) # a espera de receber alguma coisa
        if(not data):
            print(client_conn.getpeername(), 'disconectou-se')
            return
        client_conn.send(b'Eco=>' + data) # enviar msg para o mesmo cliente que mandou

with socket.socket() as s: # por default ja abre socket AF_INET e TCP (SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # reutilizar porta logo após servidor terminal, evita a excepcao 'OSError: [Errno 98] Address already in use'
    s.bind(('', 50007)) # localhost por default no primeiro elemento do tuple
    s.listen(5)
    while True: # aceitar todas as conexoes que possam vir
        conexao, endereco = s.accept() # a espera de conexao
        print('Server conectado por', endereco)
        threading.Thread(target=handle_client, args=(conexao,)).start() # comecar thread para lidar com o cliente, uma para cada cliente

Customer (s):

import socket, select, sys

with socket.socket() as s: # por default ja abre socket AF_INET e TCP (SOCK_STREAM)
    s.connect(('', 50007))
    while True:
        io_list = [sys.stdin, s]
        ready_to_read,ready_to_write,in_error = select.select(io_list , [], [])   # visto que as funcoes input e recv sao 'bloqueadoras' da execucao do codigo seguinte temos de 'seguir' ambos os eventos desta maneira
        if s in ready_to_read: # caso haja dados a chegar
            data = s.recv(1024)
            if(not data): # ex: caso o servidor se desligue, ou conexao perdida
                break
            print(data.decode())  # decode/encode por default e utf-8
        else: # enviar msg
            msg = sys.stdin.readline() # capturar mensagem inserida no terminial, no command prompt
            s.send(msg.encode())  # decode/encode por default e utf-8
            sys.stdout.flush()

DOCS of select

If you just want to do what you're doing, sending / receiving a message list can simplify your client to:

import socket

mgs = ['msg1', 'msg2', 'msg3']
with socket.socket() as s:
    s.connect(('', 50007))
    for msg in mgs:
        s.send(msg.encode())
        print(s.recv(1024).decode()) # decode/encode por default e utf-8

Way to use / test:
     - run server: e.g. python3 server.py
     - open 2 or more terminals with: eg python3 client.py and send messages from each of them

Note: You should avoid using special characters in object / variable names ("connection", "address").

Here I leave a server / chat client that I did in a little bit more complete time.

    
08.04.2018 / 12:45
3

This program has several sockets usage errors.

1) In TCP, bytes are sent and received, not complete messages. There is no guarantee that a send () will send all the string provided, nor that recv () will receive a complete message, let alone the total bytes specified, which is only a maximum.

2) It is necessary to observe the return value of send () to see how many bytes were actually sent; if not all, you must call send () again to send the rest, usually done in a loop.

3) The same goes for recv (), but for variable-length messages, you need to decide how to detect the "end of message" - whether it's a line break, or an X number of bytes, or other criterion. There is no equivalent of the hammer saying "over" or "exchange":)

4) In both send () and recv () you need to test whether you have sent or received zero bytes. This means that the connection has been closed. In the case of send (), a return less than zero means error, which usually happens when trying to use an already closed connection. (In the recv () of the server you are already doing this check.)

5) This is not an error, it is a technique: for a server to serve multiple clients, it is necessary to throw threads (a thread taking care of each connection socket) or asynchronous programming (using select ()) .

Improved customer (not perfect, but will serve multiple customers at once, 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()

Improved 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()

For completeness, I'll 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)
    
08.04.2018 / 02:26