views:

272

answers:

2

Hey,

Currently, I am trying to do a small project with sockets in Python, a two-user chatting system.

import socket
import threading

#Callback. Print doesn't work across threads
def data_recieved(data):
    print data

#Thread class to gather input
class socket_read(threading.Thread):
    sock = object
    def __init__(self, sock):
        threading.Thread.__init__(self)
        self.sock = sock
    def run(self):
        while True:
            data = self.sock.recv(1000)
            if (data == "\quitting\\"):
                return
            data_recieved(self.sock.recv(1000))

####################################################################################
server = False
uname = input("What's your username: ")
print "Now for the technical info..."
port = input("What port do I connect to ['any' if first]: ")
#This is the first client. Let it get an available port
if (port == "any"):
    server = True
    port = 9999
    err = True
    while err == True:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.bind(('', port))
            err = False
        except:
            err = True
        sock.close()

    print "Bound to port #" + str(port)
    print "Waiting for client..."

    sock.listen(1)
    (channel, info) = sock.accept()
else:
    #This is the client. Just bind it tho a predisposed port
    host = input("What's the IP of the other client: ")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, int(port)))

msg = ""
if (server == True):
    #Use the connection from accept
    reader = socket_read(channel)
else:
    #Use the actual socket
    reader = socket_read(sock)
reader.start()
while msg != 'quit':
    #Get the message...
    msg = uname + ": " + input("Message: ")
    try:
        #And send it
        if (server == True):
            #Use the connection from accept
            channel.send(msg)
        else:
            #Use direct socket
            sock.send(msg)
    except:
        break
reader.join()
channel.send("\quitting\\")
sock.close()

(I hope the comments help)

Anyhow, by calling for input at the same time, and getting the other socket's message, I've got a small syncronization issue. I can connect, but when I recieve a message, it doesn't cancel the input statement.

In other words, when I recieve a message, it says this

Message: user: I got a message
#Flashing cursor here

So that it doesn't cancel the input statement.

Also, I only get every other message.

Any suggestions?

+1  A: 

What you have here is not so much a synchronization issue as it is a presentation/UI issue. I would suggest making your life easier and picking some UI toolkit (curses, wxPython, pyqt) to handle interaction with the user. Using input() is very handy for quick-and-dirty one-off code, but it is not very sophisticated.

If you do this you will see you do not need to use threads at all (as is often the case), and your problems will go away as if by magic!

Ranieri
Thanks for the idea. However, the sockets is the reason to multithread, not the user interaction. I needed to monitor for input and send output instantaneously, not send and wait for reply.
new123456
You shouldn't have to use threads for the sockets either.For example, using QT you can get a signal when there is data waiting on the socket OR on the user input front. Then you can handle it. The event loop takes care of everything else.What you are doing here in very error-prone, as you are finding out.
Ranieri
+1  A: 

Alright, sorry for such a quick answer to my own question, but callbacks are MAGICAL when using threading (at least on Linux's model).

Anyhow, did this:

import socket
import threading

def msg_loop(socket):
    msg = ""
    if (server == True):
        reader = socket_read(channel)
    else:
        reader = socket_read(sock)
    reader.start()
    while msg != 'quit':
        msg = uname + " said : " + input("Message: ")
        print ""
        try:
            if (server == True):
                channel.send('null')
                channel.send(msg)
            else:
                sock.send('null')
                sock.send(msg)
        except:
            break

def data_recieved(data, socket):
    print "Hold on...\n\n" + data + "\n"
    msg_loop(socket)

class socket_read(threading.Thread):
    sock = object
    def __init__(self, sock):
        threading.Thread.__init__(self)
        self.sock = sock
    def run(self):
        while True:
            data = self.sock.recv(1000)
            if (data == "\quitting\\" or data == ''):
                return
            data_recieved(self.sock.recv(1000), self.sock)

####################################################################################
server = False
uname = str(input("What's your username: "))
print "Now for the technical stuff..."
port = input("What port do I connect to ['any' if first]: ")
if (port == "any"):
    server = True
    port = 9999
    err = True
    while err == True:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.bind(('', port))
            err = False
        except:
            print "Socket #" + str(port) + " failed"
            err = True
            sock.close()
            port -= 1

    print "Bound to port #" + str(port)
    print "Waiting for client..."

    sock.listen(1)
    (channel, info) = sock.accept()
else:
    host = input("What's the IP of the other client: ")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, int(port)))

if (server == True):
    msg_loop(channel)
else:
    msg_loop(sock)

reader.join()
channel.send("\quitting\\")
sock.close()

As you can see, I added the message loop as a callback.

Also note, I send a null value, to circumvent the "every other" problem.

That, and I use a newline at the end of printing in data_recieved to disable the newline.

(If you like the code, it doesn't run as well on Windows. This is because, apparently, Python's threading model on there doesn't execute as instentaniously. Try it on your local Linux box)

new123456
That doesn't look right.If you keep calling into data_recieved and socket_read without returning, you will crash out of your stack eventually.
Ranieri