-1

i have a code which needs to perform the task of sending and receiving messages from specific clients. When i mention the client address in the send function of the server, i am being given a key error.

[NEW CONNECTION] ('10.14.0.1', 52870) connected.[ACTIVE CONNECTIONS] 1

Exception in thread Thread-3:
Traceback (most recent call last):
  File "C:\Users\sho\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "c:\Users\sho\Documents\TcpCommExample\Server and clients\server 5.py", line 33, in run
    clients["10.14.0.1"].send("VITA".encode(FORMAT))
KeyError: '10.14.0.1'

I have checked the dictionary by printing it and the client address does exist in it, i am not sure where i am making a mistake. Here is the code and any help would be tremendously appreciated.

import socket, threading
import time



HEADER = 80
PORT = 9000
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
VITA_POSITIVE = "0000"
my_timer = 0

class ClientThread(threading.Thread):

    def __init__(self, conn: socket.socket, addr: str):
        threading.Thread.__init__(self)
    
        self.conn = conn
        self.addr = addr

    def send(self, msg: str):
        self.conn.sendall(msg.encode())

    def run(self):
        print(f"[NEW CONNECTION] {self.addr} connected.")
        connected = True
        while connected:
            clients["10.14.0.1"].send("VITA".encode(FORMAT))
            vita_response_iconet = clients["10.14.0.1"].recv(HEADER).decode(FORMAT) 
        
            print(vita_response_iconet)
            if vita_response_iconet == VITA_POSITIVE:

                print("VITA received from Iconet")
                vita_iconet = 1  
        
            else:
                print("VITA not received from Iconet")
                vita_iconet = 0
        
            clients["10.14.0.1"].send("VITA".encode(FORMAT))
            vita_response_robot = clients["10.14.0.1"].recv(HEADER).decode(FORMAT)

            print(vita_response_robot)
            if vita_response_iconet == VITA_POSITIVE:

                print("VITA received from Robot") 
                vita_robot = 1
        
            else:
                print("VITA not received from Robot")
                vita_robot = 0


        if vita_iconet and vita_robot == 1:
            my_timer = 0
        else:
            my_timer = my_timer
        

        self.conn.close()
    

def countup():
global my_timer  
for x in range(1, my_timer+1):
time.sleep(1)
countup_thread = threading.Thread(target=countup)
countup_thread.start()


def start():
server.listen()
print(f"[LISTENING] Server is listening on {SERVER}")
while True:
    conn, addr = server.accept()
    print('interesting')
    print(conn)
    print(addr)
    thread = ClientThread(conn, addr)
    print ('be ready')
    thread.start()
    clients[addr] = thread
    print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 2}")


server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)
clients = {}

connections = threading.Thread(target=start)
connections.start()

print("[STARTING] server is starting...")
start()
Sho
  • 21
  • 5
  • error shows you in which line you have problem so you should check what you have in variables before this line. Maybe this `"10.14.0.1"` doesn't exist in client - maybe some other code already removed it. OR maybe code didn't add it yet. – furas May 29 '22 at 11:30
  • first you should edit question and put code with correct indentations. Code with wrong indentations is useless - we can't run it and we can't see if you problem makes some wrong indentation. And you could use one or two empty lines between lines of code - don't put 10 empty lines because code it long and less readable. – furas May 29 '22 at 11:33
  • I apologise for the trouble and inconvenience i caused with my indentations, I am new to stack overflow and i am still finding my way around it. I hope you accept my apology with a kind heart. I have made the edits to the code and added the correct indentations. – Sho May 29 '22 at 13:39
  • you still have wrong indentattions in functions `countup()` and `start()` - and it is hard to say which line is inside function and which is outside function. – furas May 29 '22 at 14:06
  • you run function `start()` two times - first as `Thread(target=start)` and later directly `start()` - and this can make problem. – furas May 29 '22 at 14:08
  • I don't understand why you use hardcoded address `"10.14.0.1"` in `clients["10.14.0.1"]` - you should use `clients[self.addr]`. Maybe you connect from different `IP` and it can't find `"10.14.0.1"` in `client` because there is different address. Frankly, you should use `self.conn` instead of `clients[self.addr]` – furas May 29 '22 at 14:11
  • the `countup()` and `start()` are indented in such a way because i wish to run them indipendently and not under the `ClientThread()` – Sho May 29 '22 at 15:12
  • Thank you for the suggestion, i will run the code with using `self.conn[self.addr]` and give it a try – Sho May 29 '22 at 15:15
  • not `self.conn[self.addr]` but only `self.conn` like `self.conn.send(...)`, `self.conn.recv(...)` – furas May 29 '22 at 15:17
  • But then the message would be sent to all the clients connected rather than a specific client right? I wish to send message to a specific client – Sho May 29 '22 at 15:28
  • no. `self.conn` has only one connection. Don't you know your code? You create `thread = ClientThread(conn, addr)` and you send single connection as `conn` which you later assign `self.conn = conn` in `__init__`. And `self.conn` is NOT list `clients` – furas May 29 '22 at 15:31
  • Maybe use `print()` (and `print(type(...))`, `print(len(...))`, etc.) to see which part of code is executed and what you really have in variables. It is called `"print debuging"` and it helps to see what code is really doing. – furas May 29 '22 at 15:35
  • BTW: did you use `print()` to see what you really have in `clients`? Now I realized that first problem was because you tried to access `clients["10.14.0.1"]` but it keeps it as `clients[ ('10.14.0.1', 52870) ]`. You set `client[ addr ] = ... ` but `addr` is NOT `"10.14.0.1"` but `('10.14.0.1', 52870)` – furas May 29 '22 at 15:39

1 Answers1

0

You add to dictionary using

clients[addr] = thread

but addr is not "10.14.0.1" but ('10.14.0.1', 52870) and it sets

clients[ ('10.14.0.1', 52870) ] = thread

and you would have to use clients[ ('10.14.0.1', 52870) ] to access element in clients.

But there is other problem.

client keeps thread but you need connection/socket to run send() and recv() - so you have to use self.conn for this.

self.conn.send(...)

I didn't test it but it should be something like this

import socket
import threading
import time

# --- constants ---

HEADER = 80
PORT   = 9000
#SERVER = socket.gethostbyname(socket.gethostname())
SERVER = '0.0.0.0'
ADDR   = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
VITA_POSITIVE = "0000"

# --- classes ---

class ClientThread(threading.Thread):

    def __init__(self, conn: socket.socket, addr: str):
        super().__init__()
    
        self.conn = conn
        self.addr = addr

    def send(self, msg: str):
        self.conn.sendall(msg.encode())

    def run(self):
        print(f"[NEW CONNECTION] {self.addr} connected.")
        connected = True
        while connected:
            self.conn.send("VITA".encode(FORMAT))
            vita_response_iconet = self.conn.recv(HEADER).decode(FORMAT) 
        
            print(vita_response_iconet)
            
            if vita_response_iconet == VITA_POSITIVE:
                print("VITA received from Iconet")
                vita_iconet = 1  
            else:
                print("VITA not received from Iconet")
                vita_iconet = 0
        
            self.conn.send("VITA".encode(FORMAT))
            vita_response_robot = self.conn.recv(HEADER).decode(FORMAT)

            print(vita_response_robot)
            
            if vita_response_iconet == VITA_POSITIVE:
                print("VITA received from Robot") 
                vita_robot = 1
            else:
                print("VITA not received from Robot")
                vita_robot = 0

        if vita_iconet and vita_robot == 1:
            my_timer = 0
        else:
            my_timer = my_timer

        self.conn.close()
    
# --- functions ---

def start():
    try:
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        # solution for "[Error 89] Address already in use". Use before bind()
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
        server.bind(ADDR)
    
        server.listen()
        print(f"[LISTENING] Server is listening on {SERVER}")
    
        while True:
            print('wait for client')
            conn, addr = server.accept()
            
            print('interesting')
            print(conn)
            print(addr)
            
            thread = ClientThread(conn, addr)
            print ('be ready')
            thread.start()
            clients[addr] = thread
    
            print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 2}")
    except KeyboardInterrupt:
        print('Stopped by Ctrl+C')
    finally:
        server.close()
        
# --- main ---

#my_timer = 0

clients = {}

# I don't know why you run it in thread and later run normally as `start()`
#connections = threading.Thread(target=start)
#connections.start()

print("[STARTING] server is starting...")
start()
furas
  • 134,197
  • 12
  • 106
  • 148
  • the reason I'm using the code which i am using is because it was suggested by a fellow stackoverflow user as a possible solution to send messages to a specific client. [link](https://stackoverflow.com/questions/72221460/is-there-a-way-to-send-message-to-a-specific-client-in-a-multithreaded-tcp-serve) this is the link – Sho May 29 '22 at 16:41
  • funny, in answer in your link I see in code also `self.conn.send()` instead of `clients["10.14.0.1"].send()`, It seems this fellow also know that it has to be `self.conn.send()` but he didn't describe it in text. – furas May 29 '22 at 17:06
  • Oh, i see. but now i am confused as to how a message from the server will be delivered to a specific client, for example if i want to send 'A' for client 1 and send 'B' as a message to client 2. I am confused by the code and i am honestly struggling to find a decent explanation :( in my code, iconet and robot are my two clients and i want to send different messages to each of them – Sho May 29 '22 at 17:21
  • for every client you create separate `ClientThread` and you send its uniqe socket/connection as parameter `ClientThread(conn, addr)`. So every `ClientThread` sends to only one client because it has connection/socket for one client – furas May 29 '22 at 17:59
  • if you want to send to many clients in one thread then you may have to keep `conn, addr` on list or dictionary `clients[addr] = conn` - but your `clients` doesn't keep `connection` but `thread`. – furas May 29 '22 at 18:07
  • How would an example code look like if i have to proceed with using the list or dictionary method? I hope i am not being too dumb, this is my first foray into programming and it is a little overwhelming. – Sho May 29 '22 at 18:37
  • what do you want to create - something like web server or like chat. For web server every client should run in separated thread and every thread can get unique value from client and send unique answer to the same client (and this doesn't need dictionary beause you have all for this single client in `self.conn`, `self.addr`). For chat you keep data as in previous comment `clients[addr] = conn` and inside thread you can use `for`-loop to resend the same value to all clients `for addr, conn in clients.items(): conn.send()` or use `conn = clients[addr]` and `conn.send()`. – furas May 29 '22 at 18:48
  • one more thing: in `client-server` you always send message from server to client only after client send message from client to server. So client always send message and wait for answer. And server at the same time first wait for message and next send answer. If you send message to client when it doesn't wait for answer then it can block connection. – furas May 29 '22 at 18:55
  • Basically the function of my server is to first acknowledge the connection of the clients and then it has to take a message from the iconet client and then send the message to robot client. The loop has to repeat until the iconet sends a disconnect message at which point the socket will be closed – Sho May 29 '22 at 18:55
  • so first you have to get connections from clients and you have to know which one is `iconet` and which `robot`. And if you want to use only one client from `iconet` and one client from `robot` then you don't need to run in separated threads but you should run both in one thread. And if `robot` has to only receive messages then this connection may need to run in different way - server should work as `client` and `robot` should work as `server` – furas May 29 '22 at 18:59
  • it looks like some kind of `proxy server` – furas May 29 '22 at 19:14
  • yes identifying which client is `iconet` andwhich client is `robot` is needed. also. The robot will also send messages back to the server, so, the robot is not just for receiving. iconet will send co ordinates to the server, the server sends the co ordinates to robot and once the robot has moved to the specified co ordinates, the robot will send an ackowledgement message like `task done`. Then the whole process of receving co ordinates and sending it to robot executes again and again in a loop – Sho May 29 '22 at 19:30
  • it looks like proxy server. `iconet` as client send message to your server and wait for answer, next your server behaves like a client and send message to `robot` and wait for answer form `robot`. And when it get answer from robot then it may send answer to `iconet`. – furas May 29 '22 at 20:13
  • Yes, that is correct – Sho May 29 '22 at 20:38
  • Could you please help me out with it? – Sho May 30 '22 at 18:45