170214
来源:互联网 发布:linux mv移动多个文件 编辑:程序博客网 时间:2024/06/05 02:48
1625-5 王子昂 总结《2017年2月14日》 【连续第137天总结】
A.python多人聊天室90%
B.多对多的聊天实际上只需要在全双工聊天程序的基础上,把服务器添加转发机制即可。即把收到的消息再广播给除了发送者意外的所有连接者。另外由于有多个连接,所以需要创建多组套接字和线程。使用列表来描述它们非常方便。而客户端一边可以完全不用修改,因为同样还是发送和接收两个线程。
一步一步完成,首先是套接字列表。之前因为是一对一连接,所以可以使用Send.join()方法阻塞住等待该连接断开后再等待下一个连接。现在是多对多连接,那么阻塞的地方应该是accept方法。即主线程一直在等待新客户端的接入,并为每个客户端创建两个套接字和线程来通讯。
不过有一个问题,当某个客户端退出以后,如何识别它的退出消息来清除列表中其的套接字和线程呢。在调试窗口直接关闭的话,服务器端会收到None和一个错误信息:Traceback (most recent call last):
File "F:/python/chatroom/.idea/ser.py", line 30, in run
data = self.tcpCliSock.recv(self.BUFSIZ)
error: [Errno 10054]
百度之后得知,Errno 10054的意思是远程主机强迫关闭了一个现有连接。也就是说这个错误可以识别客户端退出,但是如何使用try-except来接收这种error呢?百度了一下是用socket.error,但是自己用的时候会报AttributeError: type object '_socketobject' has no attribute 'error'翻书上偶然在创建异常的章节看到了“socket模块生成的异常叫做socket.error”,也就是说这个用法应该没错,那么为什么会报错呢…突然想起之前导入模块时使用的是书上的方法:from socket import * ,这个之前虽然注意到了但是一直没改,会不会跟这个有关呢……把from socket import * 改为import socket,并将相关的函数socket前加上模块名sokcet;果然成功了。查了一下书上关于模块的知识,“from-import"是将指定模块的内容导入到当前作用域,也就是说socket.error省略了'socket.'的部分,因此应该直接使用except error。
这样就可以识别客户端断开的问题了。清理工作分别是结束套接字和两个子线程。套接字很好结束,直接调用close()方法即可,难办的是两个线程。因为之前是使用join方法来等待其的自然结束。查到了event对象可以进行进程管理,如暂停,继续和停止。然而无论使用return,break还是event机制,查看发现它们都仍然存活。测试了良久以后发现,查看的其实是列表里的对象,只需要用remove方法清除即可。
解决完退出问题,想办法进行各个客户端的标识,也就是每个客户端应该有名字。最初的想法是把名字放在客户端的信息里,把名字视作发送信息的一部分即可。但是这样的话,在客户端断开的时候就不可能广播“用户XXX退出了房间”这样的信息,因此必须将信息存放于服务器当中。使用列表又有点麻烦,直接将名字保存在类内作为一个成员即可。调用的时候可以正常取出,清除的时候也可以跟随类一同被释放。
服务器代码:
#encoding:utf-8import socketfrom time import ctimeimport threadingimport tracebackfrom os import _exitclass Conn(threading.Thread): def __init__(self, func,socket,name='未命名'): threading.Thread.__init__(self) self.name = name self.func = func self.tcpCliSock=socket self.BUFSIZ=1024 def run(self): if self.func == 'send': while (True): data = raw_input('> ') if data=='exit': break try: for client in cli: client.send('[system message][%s]%s' % (ctime(), data)) except error: print 'error:no connection' else: while (True): try: data = self.tcpCliSock.recv(self.BUFSIZ) if data[:6] =='_name=': self.name=data[6:] continue except socket.error:#有用户退出 self.tcpCliSock.close() n=cli.index(self.tcpCliSock) cli.remove(self.tcpCliSock) Send.remove(Send[n]) Recv.remove(Recv[n]) print '[%s]%s退出了' % (ctime(), self.name) for client in cli:#广播退出 client.send('[system message][%s]%s退出了' % (ctime(), self.name)) break cli.remove(self.tcpCliSock) for client in cli: client.send('%s%s:%s'%(data[:26],self.name,data[26:])) cli.append(self.tcpCliSock) if not data: break print data[:26]+self.name+':'+data[26:]cli = []Send = []Recv = []def main(): HOST='' PORT=21568 ADDR=(HOST,PORT) tcpSerSock=socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while(True): print 'waiting for connected..' tcpCliSock, addr = tcpSerSock.accept() cli.append(tcpCliSock) print '...connected from:', addr Send.append(Conn('send',tcpCliSock)) Recv.append(Conn('recv',tcpCliSock)) Recv[-1].name=Recv[-1].tcpCliSock.recv(1024)[6:] print '['+ctime()+']'+Recv[-1].name+'进入了' Send[-1].start() Recv[-1].start()# Send.join()# tcpCliSock.close() # print 'Service Runover' tcpSerSock.close()if __name__=='__main__': main()
完成的服务器信息如:
waiting for connected..
...connected from: ('172.20.10.2', 55827)
[Wed Feb 15 01:53:54 2017]小蓝进入了
> waiting for connected..
[Wed Feb 15 01:53:58 2017]小蓝:大家好
[Wed Feb 15 01:54:05 2017]小蓝:再见
[Wed Feb 15 01:54:08 2017]小蓝退出了
客户端代码:
#encoding:utf-8from socket import *from time import ctimeimport selectimport threadingHOST='DESKTOP-ORQOQ33'PORT=21568BUFSIZ=1024ADDR=(HOST, PORT)tcpCliSock=socket(AF_INET, SOCK_STREAM)try: tcpCliSock.connect(ADDR)except error: print "connected failed"else: print "connected success"class Conn(threading.Thread): def __init__(self, func,socket,name=''): threading.Thread.__init__(self) self.name = name self.func = func self.tcpCliSock=socket self.BUFSIZ=1024 self.NAME = '小蓝' def run(self): if self.func == 'send': self.tcpCliSock.send('_name='+self.NAME) while (True): data = raw_input('> ') if data=='exit': break if data=='renamed': self.NAME=raw_input('请输入你想要修改的昵称') self.tcpCliSock.send('_name=' + self.NAME) continue try: self.tcpCliSock.send('[%s]%s' % (ctime(), data)) except error: print 'error:no connection' else: while (True): try: data = self.tcpCliSock.recv(self.BUFSIZ) except error: print 'Connecetion stopped' break if not data: break print dataSend=Conn('send',tcpCliSock)Recv=Conn('recv',tcpCliSock)Recv.setDaemon(True)Send.start()Recv.start()Send.join()print 'Client RunOver'tcpCliSock.close()
客户端信息如:
connected success
> 大家好
> 再见
> exit
Client RunOver
明天看看怎么样从外网ip接入,实现真正的使用
C.明日计划
Python多对多聊天应用
- 170214
- 170214
- 每日170214
- python对象类型之概述
- Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)(转)
- Android之Animation动画各属性的参数意思
- 在eclipse上配置有关SVN的忽略
- Lottie的使用及原理浅析
- 170214
- 图的基本存储的基本方式一
- java--09--对象与JSON与Map之间的转换
- 静态广播与动态广播的区别
- js中扩充类型的功能
- shiro的使用2 灵活使用shiro的密码服务模块
- PAT:A1095. Cars on Campus (0/30)
- 伪代码编程过程
- 设计模式 - AbstractFactory抽象工厂