Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
176 views
in Technique[技术] by (71.8m points)

sockets - python multithreaded server unable to receive data from client

In the frame of our course, our teacher asked us to write a client-server program, where the server split two matrices that it wants to multiply then send them to the client and the client should calculate their part of the result and send it back to the server.

I succeed to divide the matrix and send it to clients but my problem is that my client cannot send back the results to the server. When I try to receive any message at the server-side, my client no longer receives the matrix to compute.

Here is my server code

!/usr/bin/env python
# -*- coding: utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM, timeout
from threading import Thread
import numpy as np
import pickle
buf = 4096
class ErrorLevels:
 
    OK = "OK"
    ERROR = "ERREUR"
 
 
class Server(Thread):
 
 
    def __init__(self):
 
        Thread.__init__(self)
 
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.bind(("localhost", 2020))
        self.socket.settimeout(0.5)
 
        self.running = False
        self.client_pool = []
 
    def client_handling_stopped(self, client, error_level, error_msg):
 
        print("Le gerant de {} s'est arrete avec le niveau d'erreur {} ({})".format(client.address[0],error_level,error_msg))
 
        self.clean_up()
 
        # self.log_connection_amount()
 
    def log_connection_amount(self):
 
        print("Il y a maintenant {} client(s) connecte(s)".format(len(self.client_pool)))
 
    def stop(self):
 
        print("Arrêt du serveur")
 
        for client in self.client_pool:
            client.close_connection()
 
        self.running = False
 
    def clean_up(self):
        """
        Enleve tous les gérants de clients innactifs de la liste des gerants de clients
        """
        self.client_pool = [client for client in self.client_pool if client.alive]
    #le serveur genere le calcul a envoyer aux clients
     #generation de matrices
    def matrice_aleatoire(self,intervalle, ligne, colonne):
        matrice = np.random.randint(intervalle, size=(ligne, colonne))
        return matrice


    def run(self):
        A = self.matrice_aleatoire(10,100,100)
        B = self.matrice_aleatoire(10,100,100)
#code fonctionnnant pour 10 clients
    #division de A  en 10 sous matrices de 10 lignes et envoie aux clients
        C = np.vsplit(A, 10)
            #dictionnaire a envoyer a chaque client
        data = []
        for i in range(10):
            dic = {'num':i,'partA':C[i],'partB':B}
            data.append(dic)
        print("Démarrage du serveur
Attente des connexions clients...")
 
        self.running = True
 
        self.socket.listen(5)
        i=-1
 
        while self.running:
 
            try:
 
                client, address = self.socket.accept()
                i=i+1
            except timeout:
                continue # on retourne au début de la boucle jusqu'à avoir un client
 
            print("Connexion depuis {}".format(address))
            #envoie et reception du calcul aux clients connnectes
            #actuellement 10 clients
            client_handling = ClientHandling(client, address,data[i], self.client_handling_stopped)
            self.client_pool.append(client_handling)
            client_handling.start()
            
            
                
 
            # self.log_connection_amount()
 
 #classe d'ojbets thread pour gerer les connections clients
class ClientHandling(Thread):
 
    def __init__(self, client, address,data, exit_callback):
 
        Thread.__init__(self)
 
        self.client = client
        self.address = address
        self.data = data
        self.exit_callback = exit_callback # une fonction qui devra être appelée lorsque cet objet sera devenu inactif
        self.alive = True
 
    def _stop(self, error_level, error_msg):
 
        self.alive = False
        self.close_connection()
        self.exit_callback(self, error_level, error_msg)
 
    def close_connection(self):
 
        self.alive = False
        self.client.close()
        print("Fin de la communication avec {}".format(self.address))
   
   
    def run(self):
 
        try:
 #envoie du calcul
            print("debut envoie du calcul")
            data_string = pickle.dumps(self.data)
            self.client.sendall(data_string)
            print("fin envoie")
 #reception resultat
            ''' 
            here is the problem when i try to receive the result 
            pick_ = b''
            while 1:
                dat = self.client.recv(buf)
                pick_ += dat
                print("reception resultat")
                if not dat:break
            res = pickle.loads(dat)
            print("fin reception")
           # print(res)'''
            
  #quelques exceptions possibles
        except ZeroDivisionError:
 
            self._stop(ErrorLevels.ERROR, "Une division par zero tente")
 
        except ConnectionAbortedError:
 
            if self.alive: # innatendu
                self._stop(ErrorLevels.ERROR, "La connexion abandonnee")
 
            else: # on est dans le cas où le gérant est volontairement arrêté
                return # on arrête donc tout, plus besoin de faire quoi que ce soit
 
        self._stop(ErrorLevels.OK, "Le client a ferme la connection")
 
 
try:
 #lancement du thread serveur
    server = Server()
    server.start()
 
    while True: continue
 
except KeyboardInterrupt:
 
    server.stop()
    server.join()

here is my client.py

import socket
from threading import Thread
#import numpy as np
import pickle
hote = "localhost"
port = 2020
buf = 4096
connexion_avec_serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connexion_avec_serveur.connect((hote, port))
print("Connexion établie avec le serveur sur le port {}".format(port))
#thread pour le calcul du client 
class Calcul(Thread):
    def __init__(self):
        Thread.__init__(self)
    #fonction qui extrait les donnees et multiplie
    def multmat(self,data):
        num = data['num']
        A = data['partA']
        B = data['partB']
        C = A @ B
        resul = {'num':num,'partC':C}
        return resul
    def run(self):
        #reception calcul
        pick_str = b''
        while 1:
            data = connexion_avec_serveur.recv(buf)
            pick_str += data
            if not data:break
            #connexion_avec_serveur.close()
        dic = pickle.loads(pick_str)
        #print(dic)
       #calcul du produit
        res = self.multmat(dic)
        print(res)
        #envoie du resultat du calcul
        data_string = pickle.dumps(res)
        connexion_avec_serveur.sendall(data_string)       
cal = Calcul()
cal.start()
cal.join()
connexion_avec_serveur.close() 
question from:https://stackoverflow.com/questions/65650305/python-multithreaded-server-unable-to-receive-data-from-client

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The main problem is that the client does not know when the complete message from the server has been received. The receiving code expects the server to close the connection before it can process the incoming data. However, the server can not close the connection because it is waiting for the client to send a response over the same connection.

The client is blocked at data = connexion_avec_serveur.recv(buf) until the server closes the connection, or some other network event occurs that severs that connection. Meanwhile the server is also blocked at dat = self.client.recv(buf) awaiting a response from the client - there is a deadlock.

The solution is to arrange for the client to know when it has received the complete message from the server, which means adding some protocol. One way is for the sever to append a sentinel value to signal the end of the message, and for the client to watch for that sentinel. Another way is for the server to prepend to the message the length of the payload, in this case the length of the pickled data, which I show here.

For the client change the run() function:

import struct

    def run(self):
        # First 4 bytes are the length of the payload
        data = connexion_avec_serveur.recv(4)
        msglen = struct.unpack('!L', data)[0]
        print(f'Length of payload {msglen = }')
        payload = []

        while msglen > 0:
            print(f'{msglen = } calling recv...')
            data = connexion_avec_serveur.recv(buf)
            print(f'received {len(data)} bytes')
            payload.append(data)
            msglen -= len(data)

        print(f'total bytes read {sum(len(s) for s in payload)}')
        dic = pickle.loads(b''.join(payload))
        #print(dic)
       #calcul du produit
        res = self.multmat(dic)
        print(res)
        #envoie du resultat du calcul
        data_string = pickle.dumps(res)
        connexion_avec_serveur.sendall(data_string)

And for the server:

import struct

    def run(self):

        try:
 #envoie du calcul
            print("debut envoie du calcul")
            data = pickle.dumps(self.data)
            # prepend message with length of the pickled data
            msg = struct.pack(f'!L{len(data)}s', len(data), data)
            print(f'sending {len(msg)} bytes to client')
            self.client.sendall(msg)

            print("fin envoie")
 #reception resultat

            pick_ = b''
            while True:
                print('calling recv()')
                dat = self.client.recv(buf)
                print(f'recv() returned {len(dat)} bytes')
                pick_ += dat
                print("reception resultat")
                if not dat:
                    break

            res = pickle.loads(pick_)
            print(f'{res = }')
            print("fin reception")

  #quelques exceptions possibles
        except ZeroDivisionError:

            self._stop(ErrorLevels.ERROR, "Une division par zero tente")

        except ConnectionAbortedError:

            if self.alive: # innatendu
                self._stop(ErrorLevels.ERROR, "La connexion abandonnee")

            else: # on est dans le cas où le gérant est volontairement arrêté
                return # on arrête donc tout, plus besoin de faire quoi que ce soit

        self._stop(ErrorLevels.OK, "Le client a ferme la connection")

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...