Hal P.
Hal P.

Reputation: 31

Use threading python socket client that listens in the background and can send real time messages

What I’m Trying to Do:
I’m trying to create a client script that can listen for potential messages from the server and receive input from a user at any point that tells the script to send a message to the server all on a single socket. The server I’m working with will only connect to one client at a time, so everything is on one socket. The script will facilitate interaction between a maya ui and a file with prewritten functions that determine what to send to the server.

How it works:
I have the script with two threads. The parent thread is the adding messages thread and there is a child thread that runs the listening in the background. The child thread has a constant background loop, listening for any messages from the server (for example an error message) and reads a message queue to see if anything has been added. If something is added to the queue, the listening loop stops, sends a message, then starts the listening loop again. The parent thread allows the user to add a command into the message queue using the add_message() attribute. The maya ui will have buttons that call functions to add commands to the message queue. I made this with python 2.7

Here is an simplified example of the client. I added a single message before starting the listening loop so you can see what it is supposed to look like.

import socket
import struct
import threading
import Queue
import time

class ThreadedClient(threading.Thread):
    def __init__(self, host, port):
        threading.Thread.__init__(self)
        #set up queues
        self.send_q = Queue.Queue(maxsize = 10)
        #declare instance variables       
        self.host = host
        self.port = port
        #connect to socket
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((self.host, self.port))

    #LISTEN
    def listen(self):
        while True: #loop forever
            try:
                print 'checking for message...'
                # stops listening if there's a message to send
                if self.send_q.empty() == False:
                    self.send_message()
                else:
                    print 'no message'
                print 'listening...'
                message = self.s.recv(4096)
                print 'RECEIVED: ' + message
            except socket.timeout:
                pass

    def start_listen(self):
        t = threading.Thread(target = self.listen())
        t.start()
        #t.join()
        print 'started listen'


    #ADD MESSAGE
    def add_message(self, msg):
        #put message into the send queue
        self.send_q.put(msg)
        print 'ADDED MSG: ' + msg
        #self.send_message()

    #SEND MESSAGE
    def send_message(self):
        #send message
        msg = self.get_send_q()
        if msg == "empty!":
            print "nothing to send"
        else:
            self.s.sendall(msg)
            print 'SENDING: ' + msg
            #restart the listening
            #self.start_listen()


    #SAFE QUEUE READING
    #if nothing in q, prints "empty" instead of stalling program
    def get_send_q(self):       
        if self.send_q.empty():
            print "empty!"
            return "empty!"
        else:
            msg = self.send_q.get()
            return msg

if __name__ == '__main__':
    port = 8001
    address = 'localhost'
    s = ThreadedClient(address, port)
    s.start()
    print('Server started, port: ', port)

    s.add_message('hello world')
    s.start_listen()
    s.add_message('hello world')

And here is an example server for the client:

import socket
import sys

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 8001)
print >>sys.stderr, 'starting up on %s port %s' % server_address
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)

while True:
    # Wait for a connection
    print >>sys.stderr, 'waiting for a connection'
    connection, client_address = sock.accept()

    try:
        print >>sys.stderr, 'connection from', client_address

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(16)
            print >>sys.stderr, 'received "%s"' % data
            if data:
                print >>sys.stderr, 'sending data back to the client'
                connection.sendall(data)
            else:
                print >>sys.stderr, 'no more data from', client_address
                break

    finally:
        # Clean up the connection
        connection.close()

Problem:
Once I start the listening loop thread, the script will no longer take any more input. Just as a test I added a line after starting the listening thread that would add a message to the queue and nothing happens.

Previous Approaches:
This question is essentially exactly what I’m trying to do as well, although it was never solved: How to handle chat client using threading and queues?

I have looked at Grab user input asynchronously and pass to an Event loop in python about creating a main loop that accepts user input and have tried implementing the queue system but am getting stuck.

I’ve also tried out How to use threading to get user input realtime while main still running in python approach but I can’t use raw_input() for my final usage of the code. I have also tried socket.timeout, but also loops without taking input.

One approach I was considering was making the script asynchronous, but from what I’ve read I don’t think it will resolve the issue.

TL/DR:
Is there a way to create a script that has a client listening to the server loop running in the background while at the same time being able to accept real time commands from the user?

I would greatly appreciate any help or nudge in the right direction, I've been stuck on this a while now.

Upvotes: 2

Views: 10238

Answers (1)

Hal P.
Hal P.

Reputation: 31

The script works. There were just some errors throwing it off. In the start_listen() function

 t = threading.Thread(target = self.listen())

should be

 t = threading.Thread(target = self.listen)

and then in the __init__() after

self.s.connect((self.host, self.port))

add

self.s.settimeout(.1)

so that the script will cycle through the while loop.

Upvotes: 1

Related Questions