近一个月都在看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等人的帮助, 谢谢...
不过在最近的测试中又发现了一个新的问题: 右边列表中的名字有时候会多删, 有时候名字显示为错误的, 当然这不是前面的问题了