[python]我不太相信有人会愿意看这个问题

ken_scott 2012-03-16 03:27:11
我不太相信有人会愿意看这个问题, 代码这么长这么长, 要我自己都不想看, 但还是帖一下:

#!/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:
data = tcpCliSock.recv(BUFSIZ)
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 is not 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()

...全文
343 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
ken_scott 2012-03-20
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 angel_su 的回复:]
试试修改下面3处代码:
[/Quote]
万分感谢! 这个问题解决了
angel_su 2012-03-19
  • 打赏
  • 举报
回复
试试修改下面3处代码:
        while self.running:
#~ data = tcpCliSock.recv(BUFSIZ)
try:
data = tcpCliSock.recv(BUFSIZ)
except:
break

        #~ MyTools.MyThread(self.__recv, ()).start()
t = MyTools.MyThread(self.__recv, ())
t.daemon = True
t.start()

                elif data[index:index+3] == "\n-\n":
#~ name = data[:index]
name = data[:index-1]
for i in range(self.listboxr.size()):
#~ if self.listboxr.see(i) == name:
if self.listboxr.get(i) == name:
self.listboxr.delete(i, i+1)
break
ken_scott 2012-03-18
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 angel_su 的回复:]
引用 14 楼 ken_scott 的回复:...
我现在最想解决的: 怎么退出那个__recv线程
试试设为daemon线程,这样当主线程和其他线程(非deamon)都结束时,就会跟着自动结束。
[/Quote]
试过, 没能解决, 在__close中也加过send(""), sleep(1)这种, 服务器就是收不到这个, 无语了
angel_su 2012-03-17
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 ken_scott 的回复:]...
我现在最想解决的: 怎么退出那个__recv线程
[/Quote]试试设为daemon线程,这样当主线程和其他线程(非deamon)都结束时,就会跟着自动结束。
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 fibbery 的回复:]
你的预感是对的^_*
[/Quote]
呵呵, 我也不喜欢看大代码的帖子, 特别是不排版的(还好我排版了)

[Quote=引用 4 楼 everax 的回复:]
接分。。
菜鸟表示完全不懂
[/Quote]
我也菜鸟


谢谢两位顶贴
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 bullswu 的回复:]
10054的问题一般都是在客户端close的时候,服务器端做出了判断,因为此次连接断掉而报错
我之前都是服务端那边没配好,
因为你这段代码太长,所以我只能给你大概的建议,有空再上机帮你试试
[/Quote]
谢谢

之前也试过把Client的显式tcpCliSock.close()注释掉了, 但还是报错在这个问题

好像是这样的:
客户端x关闭后, 服务器端与x相关的线程, recv不会返回
再进一个新客户端y后, 服务器端与x相关的线程, x.recv会收到"", 关闭服务器端x的连接与结束x相关的线程
可是y却不能接收数据(能发)
这一切看起来, 就像是y登入后, 服务器收到的""(客户端x关闭)后关闭的是y的连接
好像连接串了一样, 但看代码, 又没有这种可能...
bullswu 2012-03-16
  • 打赏
  • 举报
回复
10054的问题一般都是在客户端close的时候,服务器端做出了判断,因为此次连接断掉而报错
我之前都是服务端那边没配好,
因为你这段代码太长,所以我只能给你大概的建议,有空再上机帮你试试
fibbery 2012-03-16
  • 打赏
  • 举报
回复
你的预感是对的^_*
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 fibbery 的回复:]
我大概看了一下。楼主的帖子虽然长了点,但是,为人很不错,所以还是看看:)

既然知道是资源释放的问题,我想还是先解决这个问题。不知道,在解决资源释放的问题有什么困难?当一个客户端断开连接时,服务端检测到连接已断开,就进行资源回收。

对于通知线程退出的问题,我建议使用信号量或者队列事件等的通知方式。

对你的具体问题了解不多,所以,上述只是我的一点想法和建议。希望对你有用。
[/Quote]
Client中的接收线程(__recv())不知道怎么来退出...只有当它退出来(会关闭连接)服务器才能知道客户端关闭了
我是想设计成用户关闭客户端界面时, __recv子线程能退出来, (至少在用户按了close按纽时要能达到要求), 但是__recv()中的这样一句代码: tcpCliSock.recv(), 它应该是一直阻塞着的, 所以它会一直"停"在那里, 就算是我关闭了界面, 它也不会从阻塞中退出来
这就是我现在最想解决的: 怎么退出那个__recv线程
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 kevin_zcwen 的回复:]
直接使用twisted
[/Quote]
呵呵 不会用 而且暂时想通过自己用最底层的东西来写 这样或许可以发现自己对网络方面的诸多问题
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 angel_su 的回复:]
貌似你现在的情况是客户端服务端都是被动等待对方关闭。你试试在客户端__close方法里主动关闭连接,或者送特征字段让服务器来做这动作...
[/Quote]
我是都有想设计成可以主动关闭程序的
服务器, 我想在__command中通过输入"quit"来退出, 但现在没有想到退出子线程(__recv())的方法, 暂且把它当成一直运行的, 而各__recv()子线程是通过得知对应连接断开来结束它们
客户端, 我想在用户关闭窗口来结束, 但是如你所言, __recv()子线程没有退出, 我现在也不知道怎么来写这个__recv的退出方案, 强退的方案都没有, 我试过在__close()中关闭连接, 但会有问题(子线程没退出且还在用那个socket)
tcpCliSock.recv()会一直阻塞着, 动不了了
ken_scott 2012-03-16
  • 打赏
  • 举报
回复

#!/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()

MyTools.MyThread(self.__recv, ()).start()

self.top.mainloop()

def __clear(self):
self.locker.lock()
self.listboxl.delete(0, Tkinter.END)
self.locker.unlock()

def __close(self, 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:
data = self.tcpCliSock.recv(BUFSIZ)
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]
for i in range(self.listboxr.size()):
if self.listboxr.see(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()


写出的这段代码出现了一个大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, 或者说找的方式不对, 还是我写的有问题?



挂个星期再结帖...
fibbery 2012-03-16
  • 打赏
  • 举报
回复
我大概看了一下。楼主的帖子虽然长了点,但是,为人很不错,所以还是看看:)

既然知道是资源释放的问题,我想还是先解决这个问题。不知道,在解决资源释放的问题有什么困难?当一个客户端断开连接时,服务端检测到连接已断开,就进行资源回收。

对于通知线程退出的问题,我建议使用信号量或者队列事件等的通知方式。

对你的具体问题了解不多,所以,上述只是我的一点想法和建议。希望对你有用。
angel_su 2012-03-16
  • 打赏
  • 举报
回复
貌似你现在的情况是客户端服务端都是被动等待对方关闭。你试试在客户端__close方法里主动关闭连接,或者送特征字段让服务器来做这动作...
弱水垂钓 2012-03-16
  • 打赏
  • 举报
回复
直接使用twisted
ken_scott 2012-03-16
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 angel_su 的回复:]
看了下没帮你测试,粗看下觉得你客户端有点问题,你起了个线程接受数据,不过窗口关闭时没有任何对应处理,这个线程是不是还在跑呢,所以服务器认为还没断开。另外你在这个线程里最好不要直接操作窗口,看看是触发一个自定义事件,或者修改关联窗口的变量,让窗口线程自己更新去...
[/Quote]
谢谢提示!
按照你说的, 我在help(threading)后没有找到关闭子线程之类的函数
本来我也想在recv线程中用个 while not self.closed:这种, 但是下面的那个 tcpCliSock.recv()应该会一直阻塞在那里的吧, 所以就算self.closed变成True了, 也没有机会去判断
还请明示一种退出的方法
angel_su 2012-03-16
  • 打赏
  • 举报
回复
看了下没帮你测试,粗看下觉得你客户端有点问题,你起了个线程接受数据,不过窗口关闭时没有任何对应处理,这个线程是不是还在跑呢,所以服务器认为还没断开。另外你在这个线程里最好不要直接操作窗口,看看是触发一个自定义事件,或者修改关联窗口的变量,让窗口线程自己更新去...

37,718

社区成员

发帖
与我相关
我的任务
社区描述
JavaScript,VBScript,AngleScript,ActionScript,Shell,Perl,Ruby,Lua,Tcl,Scala,MaxScript 等脚本语言交流。
社区管理员
  • 脚本语言(Perl/Python)社区
  • IT.BOB
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧