wxPython
编写客户端需要用到wxPython,它是一个GUI工具包,
下载地址:https://www.wxpython.org/download.php
运行安装即可。
当使用命令:import wx时,未报错,安装成功。
聊天室客户端代码不能在 windows 下运行,因为代码使用 select 同时监听 socket 和输入流,在 Windows 下 select 函数是由 WinSock 库提供,不能处理不是由 WinSock 定义的文件描述符。
客户端代码还有个缺陷是,当某个客户端在输入消息但还未发送出去时,服务器也发送消息过来,这样会冲刷掉客户端正在输入的消息。这目前来看没办法解决的,唯一的解决方法是使用像 ncurses 终端库使用户输入和输出独立开,或者写一个GUI的程序。
24-1.py 迷你服务器程序
from asyncore import dispatcherimport asyncoreclass ChatServer(dispatcher): passs = ChatServer()asyncore.loop()
运行之后什么都没发生。
24-2.py 可以接收连接的服务器
# coding=utf-8from asyncore import dispatcherimport socket, asyncoreclass ChatServer(dispatcher):def handle_accept(self):# 调用允许客户端连接的self.accpet函数,并返回一个连接和一个地址conn, addr = self.accept()print 'Connection attempt from', addr[0] # addr[0]是客户端的IP地址s = ChatServer()s.create_socket(socket.AF_INET, socket.SOCK_STREAM) # 服务器初始化,使用两个参数指定所需套接字的类型s.bind(('', 5005)) # 把服务器绑定到具体的地址上,,主机名为空(即本地主机),端口号为5005s.listen(5) # 调用服务器以告诉服务器要监听连接,并指定5个连接的代办事务asyncore.loop() # 启动服务器,循环监听
客户端测试:telnet命令——通过如下步骤设置:
1. 打开控制面板
2. 程序和功能
3. 打开或关闭WINDOWS功能
4. CHECK TELNET 客户端。
5. TELNET输入如果出现乱码:按下 CTRL+] 即可正常输入
以上配置完毕后,运行24-2.py,并在cmd窗口输入测试命令:
telnet 127.0.0.1 5005或者telnet localhost 5005
客户端立即被断开,即cmd恢复到初始状态
然后服务端出现如下:Connection attempt from 127.0.0.1
24-3.py具有一些清理功能的基本服务器
from asyncore import dispatcherimport socket, asyncorePROT = 5005class ChatServer(dispatcher):def __init__(self, port): dispatcher.__init__(self)self.create_socket(socket.AF_INET, socket.SOCK_STREAM)self.set_reuse_addr()self.bind(('',port))self.listen(5)def handle_accept(self): conn, addr = self.accept()print 'Connection attempt from', addr[0]if __name__ == '__main__': s = ChatServer(PROT)try: asyncore.loop()except KeyboardInterrupt: pass运行24-3.py,并在cmd窗口输入测试命令:telnet localhost 5005
服务端出现如下:Connection attempt from 127.0.0.1
ChatSession类
基本的ChatSession类用处不大,应在代码实现中为每个连接创建一个dispatcher对象。主要任务是收集来自客户端的数据进行响应,可以使用asynchat模块。
为了让asynchat起作用,只要覆盖两个方法即可。
(1) collect_incoming_data:在从套接字中读取一些bit文本时调用。
(2) found_terminator:在读取一个结束符时调用。结束符通过set_terminiator方法设置,一般设置为"\r\n".
24-4.py 带有ChatSession类的服务器程序
# coding=utf-8from asyncore import dispatcherfrom asynchat import async_chatimport socket, asyncorePORT = 5005class ChatSession(async_chat):""" 负责和单用户通信 """def __init__(self, sock): async_chat.__init__(self, sock)self.set_terminator("\r\n")self.data = []def collect_incoming_data(self, data):self.data.append(data)def found_terminator(self): line = ''.join(self.data)self.data = []# 处理这行数据.....print lineclass ChatServer(dispatcher):def __init__(self, port): dispatcher.__init__(self)self.create_socket(socket.AF_INET, socket.SOCK_STREAM)self.set_reuse_addr()self.bind(('',port))self.listen(5)self.sessions = []def handle_accept(self): conn, addr = self.accept()self.sessions.append(ChatSession(conn))if __name__ == '__main__': s = ChatServer(PORT)try: asyncore.loop()except KeyboardInterrupt: print
运行24-4.py,并在cmd窗口输入测试命令:telnet localhost 5005
运行之后出现客户端界面,而连接没有马上断掉
输入1111并回车,在该界面无任何输出,而服务端输出如下:
输入Hello, world!并回车:
实现了同时使用两个或者更多客户端进行连接,在客户端输入的每一行都会在服务器终端打印出来。
整合
还需要将用户的发言广播给其他的用户,可以通过在服务器端遍历回话的列表,将发言行写到每一个客户端里面。此外,必须保证在客户单断开连接后,将它从会话列表中移除。通过重写事件处理方法handle_close 来实现这个功能。
24-5 simple_chat.py ——简单的聊天服务器# coding=utf-8from asyncore import dispatcherfrom asynchat import async_chatimport socket, asyncorePORT = 5005NAME= 'TestChat'class ChatSession(async_chat):""" 处理服务器和一个用户之间连接的类 """def __init__(self, server, sock):# 标准设置任务:async_chat.__init__(self, sock)self.server = serverself.set_terminator("\r\n")self.data = []# 问候用户:self.push('Welcome to %s\r\n' % self.server.name)def collect_incoming_data(self, data):self.data.append(data)def found_terminator(self):""" 如果发现了一个终止对象,也就意味着读入了一个完整的行,将其广播给每个人。 """line = ''.join(self.data)self.data = []self.server.broadcast(line)def handle_close(self): async_chat.handle_close(self)self.server.disconnect(self)class ChatServer(dispatcher):""" 接受连接并且产生单个会话的类。它还会处理到其他会话的广播。 """def __init__(self, port, name):# Standrad setup tasksdispatcher.__init__(self)self.create_socket(socket.AF_INET, socket.SOCK_STREAM)self.set_reuse_addr()self.bind(('',port))self.listen(5)self.name = nameself.sessions = []def disconnect(self, session):self.sessions.remove(session)def broadcast(self, line):for session in self.sessions: session.push(line+ '\r\n')def handle_accept(self): conn, addr = self.accept()self.sessions.append(ChatSession(self, conn))if __name__ == '__main__': s = ChatServer(PORT, NAME)try: asyncore.loop()except KeyboardInterrupt: print
运行simple_chat.py,并在cmd窗口输入测试命令:telnet localhost 5005
运行之后出现客户端界面:
输入Hello, world!并回车,客户端输出该字符串:
而服务端无输出。
聊天服务器的最终版本
24-6 稍复杂的聊天服务器 chatserver.py
运行chatserver.py之后,打开两个cmd窗口,均输入测试命令:telnet localhost 5005,回车。
login命令:第一个窗口输入login maguns,第二个输入login dilbert
look命令:
say 命令:
重复登陆:
退出: