febeks17 febeks17 - 1 month ago 11
Python Question

Using one socket in UDP chat using threading

I am working on UDP chat which should be listening and being able to send message any time using only one socket. Example, I will have the chat program done, I will open it first time, then second time and I must be able to communicate over UDP from both programs, simply each program has only one opened socket.

My two threads are for listening, which is deamon thread, because I want it to listen to new messages nonstop, and my other is sending the messages, which is just like a normal thread.

First of all, my problem is that it looks like my threads are blocking each other, because if I run the program, I only get output from the first thread I start.

Second problem is that I am not sure if my sending function or the entire class is written properly, or if there is something missing or incorrect.

Thanks in advance. Btw, I am new into python and I am using python 3, just to make it clear.

import socket
import threading
import logging
import time
from sys import byteorder


class Sending():
def __init__(self, name, tHost, tPort):
self.name = name
self.host = tHost
self.port = tPort

def set_name(self, name):
self.name = name

def send(self, name, tHost, tPort, msgType, dgramSize):
logging.debug('Starting send run')
message = input('Enter message: ')
data = bytearray()
data.extend( (name.encode('utf-8'), message.encode('utf-8'), msgType.to_bytes(1, byteorder = 'little')) )
#data.extend(message.encode(encoding='utf_8'))
self.sock.sendto(bytearray(data), (tHost, tPort))

def run(self):

th2 = threading.Thread(name = 'send', target=self.send('username', 'localhost', 8001, 1, 1400))
th2.start()

class Receiving():
def __init__(self, host, port):
self.host = host
self.port = port

def create_socket(self, host, port):
logging.debug('Starting socket')
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((host, port))
#print ('socket ready')
time.sleep(5)
while True:
data, addr = sock.recvfrom(1500)
print('Prijata:' + data + addr)

def run(self):

th1 = threading.Thread(name = 'rec', target=self.create_socket('localhost', 8000))
th1.setDaemon(True)
th1.start()

if __name__ == '__main__':
#print ('running')
rec = Receiving('localhost', 8000)
send = Sending('username', 'localhost', 8001)
send.run()
rec.run()

Answer

Congrats on your introduction to Python! It looks like you're using Python 3, and in future questions it's helpful if you are explicit about which version you're using because there are minor but program-breaking incompatibilities in some code (including this code!).

I found a few errors in your program:

  • The most major issue - as Trevor Barnwell says, you're not calling threading.Thread quite correctly. The target= argument needs to be a callable object (i.e. function), but in this case it should just be a reference to the function. If you add brackets to the function, self.create_socket(host, port) as you have above, it actually runs the function immediately. As Trevor explained, your Sending.send() method was called early, but additionally there was a similar bug in Receiving. Because Receiving.create_socket() creates an infinite loop, it never returns program execution. While the console output looks correct to the user, the actual program execution has never made it to running the listener in a separate thread.

  • bytearray.extend() takes an iterable of ints, what you're passing right now is a tuple of byte objects.

  • In Sending.send() you call self.sock, but you never assign self.sock a value, so it fails.

  • Sending.run() only runs Sending.send() one time. After completing input for the user, it immediately exits, because the program has finished.

If you're looking for an in-depth, project based introduction to Python appropriate for an experienced programmer (including an exercise very similar to this question on basic sockets, and another on threading), I highly recommend you check out Wesley Chun's "Core Python Applications Programming". The most recent edition (3rd) has a lot of Python 2 code, but it's easily portable to Python 3 with some minor work on the reader's part.

I tried to modify your code as little as possible to get it working, here it is:

import socket
import threading
import logging
import time


class Sending():
    def __init__(self, name, tHost, tPort, target):
        self.name = name
        self.host = tHost
        self.port = tPort
        self.target_port = target
        self.sock = self.create_socket()

    def create_socket(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind((self.host, self.port))
        return sock

    def set_name(self, name):
        self.name = name

    def send_loop(self):
        while True:
            logging.debug('Starting send run')
            message = input('Enter message: ')
            data = bytearray()
            data.extend(message.encode('utf-8'))
            self.sock.sendto(bytearray(data), (self.host, self.target_port))

    def run(self):
        th2 = threading.Thread(name='send', target=self.send_loop)
        th2.start()


class Receiving():
    def __init__(self, host, port):
        self.host = host
        self.port = port

    def create_socket(self):
        logging.debug('Starting socket')
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind((self.host, self.port))
        print ('socket ready')
        time.sleep(5)
        while True:
            data, addr = sock.recvfrom(1500)
            print('\nPrijata:' + data.decode('utf-8') + str(addr))

    def run(self):
        th1 = threading.Thread(name='rec', target=self.create_socket)
        print("Made it here")
        th1.daemon = True
        th1.start()
        return

if __name__ == '__main__':
    print('running')
    rec = Receiving('localhost', 8000)
    send = Sending('username', 'localhost', 8001, 8000)
    rec.run()
    send.run()
Comments