现在的位置: 首页 > 综合 > 正文

简易聊天程序的python实现

2013年03月30日 ⁄ 综合 ⁄ 共 7885字 ⁄ 字号 评论关闭

近一个月都在看python, 基本看完了一遍, 写了这个程序做一下总结:

#!/usr/bin/python

"MyTools.py"

import time
import Queue
import threading

def clock():
    strtime = time.ctime()
    index = strtime.index(":")
    return strtime[index-2:index+6]

class MyThread(threading.Thread):
    def __init__(self, func, args):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
    def run(self):
        apply(self.func, self.args)

class MyLock(object):
    def __init__(self):
        self.queue = Queue.Queue(1)
    def lock(self):
        self.queue.put("", True)
    def unlock(self):
        self.queue.get(True)
#!/usr/bin/python

"Server.py"

import sys
import socket
import MyTools

class Server(object):
    def __init__(self, host, port, liscnt):
        self.tcpSerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcpSerSock.bind((host, port))
        self.tcpSerSock.listen(liscnt)
        self.tcpCliSock = []
        self.namelist = []
        self.locker = MyTools.MyLock()
        self.running = True

        MyTools.MyThread(self.__command, ()).start()
        self.__mainloop()

        self.tcpSerSock.close()
    
    def __command(self):
        try:
            while raw_input().lower() != "quit":
                pass
        except (EOFError, KeyboardInterrupt):
            pass
        finally:
            self.running = False
    
    def __mainloop(self):
        while self.running:
            tcpCliSock, addr = self.tcpSerSock.accept()
            
            self.locker.lock()
            self.tcpCliSock.append(tcpCliSock)
            self.locker.unlock()
            
            MyTools.MyThread(self.__recv, (tcpCliSock,)).start()
    
    def __recv(self, tcpCliSock):
        BUFSIZ = 1024
        name = ""
        
        while self.running:
            try:
                data = tcpCliSock.recv(BUFSIZ)
            except:
                break
            if not data:
                break
            if not name:
                name = data
                data = " ".join((name, "\n+\n"))
                self.__send(data, tcpCliSock, True)
                self.namelist.append(name)
            else:
                data = " ".join((name, MyTools.clock(), "\n", data))
                self.__send(data, tcpCliSock, False)

        if name:
            data = " ".join((name, "\n-\n"))
            self.__send(data, tcpCliSock, False)
            self.namelist.remove(name)

        self.locker.lock()
        self.tcpCliSock.remove(tcpCliSock)
        self.locker.unlock()
        
        tcpCliSock.close()
        
    def __send(self, data, tcpCliSock, bFirst):
        self.locker.lock()
        if bFirst:
            tcpCliSock.send(data + str(self.namelist))
        for eachSock in self.tcpCliSock:
            if eachSock != tcpCliSock:
                eachSock.send(data)
        self.locker.unlock()

def main():
    host = ""
    port = 34567
    liscnt = 5

    argc = len(sys.argv)
    if argc >= 2:
        port = int(sys.argv[1])
    if argc >= 3:
        liscnt = int(sys.argv[2])

    Server(host, port, liscnt)

if __name__ == "__main__":
    main()
#!/usr/bin/python

"Client.py"

import socket
import Tkinter
import MyTools
import tkMessageBox

class Client(object):
    def __init__(self):
        self.__login()

    def __login(self):
        self.top = Tkinter.Tk()
        self.top.title("login")
        self.top.geometry("200x70")

        text = (("host:", "localhost"), ("port:", "34567"), ("name:", ""))
        for i in range(1, len(text)+1):
            cmd = compile("""
frame%d = Tkinter.Frame(self.top)
label%d = Tkinter.Label(frame%d, text=%r)
label%d.pack(side=Tkinter.LEFT)
self.cwd%d = Tkinter.StringVar(self.top)
self.cwd%d.set(%r)
entry%d = Tkinter.Entry(frame%d, width=15, textvariable=self.cwd%d)
entry%d.pack(side=Tkinter.LEFT)
frame%d.pack()""" \
                    % (i, i, i, text[i-1][0], i, i, i, \
                       text[i-1][1], i, i, i, i, i), "", "exec")
            exec cmd
        
        framex = Tkinter.Frame(self.top)
        button1 = Tkinter.Button(framex, text="login", command=self.__check)
        button2 = Tkinter.Button(framex, text="close", command=self.__close)
        button1.pack(side=Tkinter.LEFT)
        button2.pack(side=Tkinter.LEFT)
        framex.pack()
        
        self.top.mainloop()

    def __check(self, ev=None):
        host = self.cwd1.get()
        if not host:
            tkMessageBox.showwarning("check", "host is invalid")
            return
        
        try:
            port = int(self.cwd2.get())
        except (ValueError, TypeError):
            tkMessageBox.showwarning("check", "port is invalid")
            return
        
        self.name = self.cwd3.get()
        if not self.name:
            tkMessageBox.showwarning("check", "please write your name")
            return

        connected = False
        try:
            self.tcpCliSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.tcpCliSock.connect((host, port))
            self.tcpCliSock.send(self.name)
            connected = True
        except:
            self.tcpCliSock.close()
            tkMessageBox.showerror("error", "can not connect to server")
        
        self.__close()
        del self.cwd1, self.cwd2, self.cwd3, self.top

        if connected:
            self.__start()

    def __start(self, ev=None):
        self.locker = MyTools.MyLock()
        
        self.top = Tkinter.Tk()
        self.top.title("Client")

        self.topframe = Tkinter.Frame(self.top)
        
        self.leftframe = Tkinter.Frame(self.topframe)
        self.chatframe = Tkinter.Frame(self.leftframe)
        self.scrollbarl = Tkinter.Scrollbar(self.chatframe)
        self.scrollbarl.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
        self.listboxl = Tkinter.Listbox(self.chatframe, height=15, width=50, \
                                        yscrollcommand=self.scrollbarl.set)
        self.scrollbarl.config(command=self.listboxl.yview)
        self.listboxl.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH)
        self.chatframe.pack()
        self.cwd = Tkinter.StringVar(self.leftframe)
        self.entry = Tkinter.Entry(self.leftframe, width=50, \
                                   textvariable=self.cwd)
        self.entry.bind("<Return>", self.__send)
        self.entry.pack()

        self.buttonframe = Tkinter.Frame(self.leftframe)
        self.button1 = Tkinter.Button(self.buttonframe, text="send", \
                                      command=self.__send)
        self.button2 = Tkinter.Button(self.buttonframe, text="clear", \
                                      command=self.__clear)
        self.button3 = Tkinter.Button(self.buttonframe, text="close", \
                                      command=self.__close)
        self.button1.pack(side=Tkinter.LEFT)
        self.button2.pack(side=Tkinter.LEFT)
        self.button3.pack(side=Tkinter.LEFT)
        self.buttonframe.pack()
        self.leftframe.pack(side=Tkinter.LEFT)

        self.rightframe = Tkinter.Frame(self.topframe)
        self.scrollbarr = Tkinter.Scrollbar(self.rightframe)
        self.scrollbarr.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
        self.listboxr = Tkinter.Listbox(self.rightframe, height=18, width=10, \
                                        yscrollcommand=self.scrollbarr.set)
        self.listboxr.insert(Tkinter.END, self.name)
        self.scrollbarr.config(command=self.listboxr.yview)
        self.listboxr.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH)
        self.rightframe.pack(side=Tkinter.LEFT)

        self.topframe.pack()

        self.thread = MyTools.MyThread(self.__recv, ())
        self.thread.setDaemon(True)
        self.thread.start()
        
        self.top.mainloop()

    def __clear(self):
        self.locker.lock()
        self.listboxl.delete(0, Tkinter.END)
        self.locker.unlock()
        
    def __close(self, quit=True, ev=None):
        self.top.quit()
        self.top.destroy()

    def __send(self, ev=None):
        data = self.cwd.get()
        self.cwd.set("")
        if data.strip():
            self.tcpCliSock.send(data)
            info = " ".join((self.name, MyTools.clock()))
            data = "".join((" " * 4, data))
            
            self.locker.lock()
            self.listboxl.insert(Tkinter.END, info)
            self.listboxl.insert(Tkinter.END, data)
            self.locker.unlock()
        else:
            tkMessageBox.showinfo("info", "can not send blank string")

    def __recv(self):
        BUFSIZ = 1024
        while True:
            try:
                data = self.tcpCliSock.recv(BUFSIZ)
            except:
                break
            if not data:
                break
            else:
                index = data.find("\n")
                if data[index:index+3] == "\n+\n":
                    names = data[index+3:]
                    if names:
                        namelist = eval(names)
                        for name in namelist:
                            self.listboxr.insert(Tkinter.END, name)
                    else:
                        name = data[:index-1]
                        self.listboxr.insert(Tkinter.END, name)
                elif data[index:index+3] == "\n-\n":
                    name = data[:index-1]
                    for i in range(self.listboxr.size()):
                        if self.listboxr.get(i) == name:
                            self.listboxr.delete(i, i+1)
                            break
                else:     
                    info = data[:index].strip()
                    data = "".join((" " * 4, data[index+1:].strip()))
                            
                    self.locker.lock()
                    self.listboxl.insert(Tkinter.END, info)
                    self.listboxl.insert(Tkinter.END, data)
                    self.locker.unlock()
        self.tcpCliSock.close()
        tkMessageBox.showerror("error", "disconnect to server")

def main():
    Client()

if __name__ == "__main__":
    main()

不可否定, 界面很难看...complile语句块的写法很难看, python设计者当初有想过这个问题吗...

回归正题!

写出的这段代码出现了一个大BUG, 按如下操作会产生一个致命的错误, 找了很久, 实在是不能理解:
1> 开启服务器
2> 开启三个客户端, 此时正常收发信息
3> 关闭一个客户端, 出现BUG1, 此时理应在其它两个客户端的人员列表中清理掉退出的用户, 可是服务器竟然没有检测到有用户退出, 所以没有发送清理用户的信息(退出时会断开连接的, 服务器怎么会不知道呢?)
4> 此时开启一个新客户端, 出现BUG2, 新客户端用户得到不别的用户列表(有时)
5> 再检测各自的收发情况, 出现BUG3, 此时新客户端只能发不能收, 先前的客户端都正常, 不过还是没有清理掉上次退出的客户端, 其实BUG2与BUG3可能是同一个BUG
6> 再退出新客户端, 服务器报错在48line: data = tcpCliSock.recv(BUFSIZ), error: [Error 10054]
7> 第6步不做, 再进一个客户端也有可能出错, 不过错误都是报在那一行..
8> 别的步骤也有可能会引发错误: 但都是报在那里

总之, 有用户退出后就有可能引发错误, 而错误就是这个10054, 究竟是为什么? (为什么客户端退出服务器不知道, 但进一个新客户端时又知道了, 莫名其妙啊, 感觉线程之间串掉了似的...)

另外两个疑问:

疑问一:
对于def __send(self, ev=None): 这种
我去掉了__send的ev=None, 在Entry中输入字符再回车就不会触发__send
是不是说只有在有bind操作时才需要ev=None呢?, (不加也不会引发语法问题)

疑问二:
Client的代码中, 有一个compile语句块, 起先我在其中加了一句:entry%d.bind("<Return>", self.__check)
可是居然报错: AttributeError: 'Client' object has no attribute '__check', 可明明是有的, 但是后面我写的: self.entry.bind("<Return>", self.__send)又有用, 这是不是说用compile语句块时, 不会去找__check, 或者说找的方式不对, 还是我写的有问题?

 

Server中的__command()本来是想给服务器一个退出的机会的(通过向服务器中输入quit), 但是发现不好做, 暂时先这样.

 

----------------------------------------------------  补充 -----------------------------------------------------

上面的问题已经在我发的一个帖子中解决掉了,
上面也是更新过的代码,

这里再次感谢angel_su等人的帮助, 谢谢...

不过在最近的测试中又发现了一个新的问题: 右边列表中的名字有时候会多删, 有时候名字显示为错误的, 当然这不是前面的问题了

 

抱歉!评论已关闭.