Client and server programming: How to achieve numerous data exchanges per client and multiple clients simultaneously?

Asked

Viewed 1,256 times

2

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 to allow numerous data exchanges, until the client decides to close the connection?

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

2 answers

4


Problem 1: There is only "1 data exchange per connected client!" ; there are several, depending on the size of your mailing list to be sent/received by the customer.

Problem #2: The server only handles one connection because you have programmed to be stuck/processing a single connection, then missing as you said, threads, one for each client.

It seems to me that you just want the server to forward the same message to the client who sent it, so these programs (server and client) can be made like this (python3 style): (Explanations throughout 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

Client(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 mailing 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:
- running server: e.g python3 server.py
- open 2 or more terminals with: e.g python3 client.py and send messages from each of them

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

Here I leave a server/client chat that I once did a little more complete.

  • I tried to make a single version with Threads. I edited the question. What do you think?

  • 1

    Sorry! I hadn’t thought about it! I’m back to the previous state! Threads server code -> https://pastebin.com/4Bmajhk6

  • 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!

  • 1

    I’ll create new question to not pollute here, ok?

  • 1

    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

  • 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

Show 1 more comment

3

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)
  • if you can, thank you! I’m wanting to learn this topic!

  • if pussyable, how to do: "of threads (a thread taking care of each connection socket)"

  • 1

    Made to order.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.