tags:

views:

511

answers:

6

I study network programming and would like to write a simple command-line chat in Python.

I'm wondering how make receving constant along with inputing available for sending at any time.

As you see, this client can do only one job at a time:

from socket import *

HOST = 'localhost'
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

while 1:
    data = raw_input('> ')
    if not data: break
    tcpCliSock.send(data)
    data = tcpCliSock.recv(BUFSIZE)
    if not data: break
    print data

tcpCliSock.close()

So if another client sends a message, this client will only receive it after sending a message too. I bet you understand me. I have googled for the matter and found out many interesting things such as asynchronous I/O, threading, non-blocking synchronization, concurrent programming and so on. I have also installed the twisted package. In brief, I've been learning all these things but yet haven't found what I was looking for. (Of course, I will keep trying and trying until I get to the point.)

So, my question is how make that? =)

+3  A: 

If you want to code it from scratch select is the way to go (and you can read on Google Book Search most of the chapter of Python in a Nutshell that covers such matters); if you want to leverage more abstraction, asyncore is usable, but Twisted is much richer and more powerful.

Alex Martelli
+1  A: 

You should use select.

Check:

Macarse
+1  A: 

I wrote one in async I/O... its a lot easier to wrap your head around than a full threading model.

if you can get your hands ahold of "talk"'s source code, you can learn a lot about it. see a demo http://dsl.org/cookbook/cookbook_40.html#SEC559 , or try it your self if you are on a linux box...

it sends characters in real-time.

also, ytalk is interactive and multiple users.... kinda like hudddlechat or campfire.

Ape-inago
+1  A: 

Chat programs are doing two things concurrently.

  1. Watching the local user's keyboard and sending to the remote user (via a socket of some kind)

  2. Watching the remote socket and displaying what they type on the local console.

You have several ways to do this.

  1. A program that opens socket and keyboard and uses the select module to see which one has input ready.

  2. A program that creates two threads. One threads reads the remote socket and prints. The other thread reads the keyboard and sends to the remote socket.

  3. A program that forks two subprocesses. One subprocess reads the remote socket and prints. The other subprocess reads the keyboard and sends to the remote socket.

S.Lott
http://docs.python.org/library/select.html is the worse documentation I've ever read. There are no examples! I got nothing from that sourse...
Okay, then read about the select API somewhere else. http://linux.die.net/man/2/select. http://www.cs.utah.edu/~swalton/listings/sockets/programs/. Google does work.
S.Lott
http://docs.python.org/howto/sockets.html#non-blocking-sockets
Bastien Léonard
A: 

Well, well, here's what I am having at this very moment.

Server goes like this:

import asyncore
import socket

clients = {}

class MainServerSocket(asyncore.dispatcher):
    def __init__(self, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(('',port))
        self.listen(5)
    def handle_accept(self):
        newSocket, address = self.accept( )
        clients[address] = newSocket
        print "Connected from", address
        SecondaryServerSocket(newSocket)

class SecondaryServerSocket(asyncore.dispatcher_with_send):
    def handle_read(self):
        receivedData = self.recv(8192)
        if receivedData:
            every = clients.values()
            for one in every:
                one.send(receivedData+'\n')
        else: self.close( )
    def handle_close(self):
        print "Disconnected from", self.getpeername( )
        one = self.getpeername( )
        del clients[one]

MainServerSocket(21567)
asyncore.loop( )

And client goes just like this:

from Tkinter import *
from socket import *
import thread

HOST = 'localhost'
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

class Application(Frame):
    def __init__(self, master):
        Frame.__init__(self, master)
        self.grid()
        self.create_widgets()
        self.socket()

    def callback(self, event):
        message = self.entry_field.get()
        tcpCliSock.send(message)

    def create_widgets(self):
        self.messaging_field = Text(self, width = 110, height = 20, wrap = WORD)
        self.messaging_field.grid(row = 0, column = 0, columnspan = 2, sticky = W)

        self.entry_field = Entry(self, width = 92)
        self.entry_field.grid(row = 1, column = 0, sticky = W)
        self.entry_field.bind('<Return>', self.callback)

    def add(self, data):
        self.messaging_field.insert(END, data)

    def socket(self):
        def loop0():
            while 1:
                data = tcpCliSock.recv(BUFSIZE)
                if data: self.add(data)

        thread.start_new_thread(loop0, ())



root = Tk()
root.title("Chat client")
root.geometry("550x260")

app = Application(root)

root.mainloop()

Now it's time to make the code look better and add some functionality.

Thanks for your help, folks!

Oh, I was talking about a command-line chat but ended up with a GUI app. Although, that doesn't make things different much.
A: 

Your question was not very coherent. However, your program does not need to be asynchronous at all to attain what you are asking for.

This is a working chat script you originally wanted with minimal changes. It uses 1 thread for receiving and 1 for sending, both using blocking sockets. It is far simpler than using asynchronous methods.

from socket import *
from threading import Thread
import sys

HOST = 'localhost'
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

def recv():
    while True:
        data = tcpCliSock.recv(BUFSIZE)
        if not data: sys.exit(0)
        print data

Thread(target=recv).start()
while True:
    data = raw_input('> ')
    if not data: break
    tcpCliSock.send(data)

tcpCliSock.close()
Unknown
> It is far simpler than using asynchronous methods.Hey, dude, where do I use any asynchronous methods in my chat client? (Well, I don't care about the server; I was interesting in clients.)My chat client is same as yours, except you import the threading module, and I import the thread module. Although, thanks to you!
The difference is that you imported asyncore, therefore you used asynchronous methods. Also you included a whole unnecessary GUI but other than that its the same.
Unknown
The asyncore module was imported in the server app, not in the client app. Да и фиг с ним...
By the way, GUI is necessary. Try the code you wrote. Launch two clients and try to communicate. It is not beautiful that way. But it's another story, though. =)
susharu: you never said it had to look good.
Unknown