第24章 项目5:虚拟茶话会
wxPython
编写客户端需要用到wxPython,它是一个GUI工具包,
下载地址:https://www.wxpython.org/download.php
运行安装即可。
当使用命令:import wx时,未报错,安装成功。
聊天室客户端代码不能在 windows 下运行,因为代码使用 select 同时监听 socket 和输入流,在 Windows 下 select 函数是由 WinSock 库提供,不能处理不是由 WinSock 定义的文件描述符。
客户端代码还有个缺陷是,当某个客户端在输入消息但还未发送出去时,服务器也发送消息过来,这样会冲刷掉客户端正在输入的消息。这目前来看没办法解决的,唯一的解决方法是使用像 ncurses 终端库使用户输入和输出独立开,或者写一个GUI的程序。
http://www.cnblogs.com/hazir/p/python_chat_room.html
24-1.py 迷你服务器程序
from asyncore import dispatcher
import asyncore
class ChatServer(dispatcher): pass
s = ChatServer()
asyncore.loop()
运行之后什么都没发生。
24-2.py 可以接收连接的服务器
# coding=utf-8
from asyncore import dispatcher
import socket, asyncore
class 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)) # 把服务器绑定到具体的地址上,,主机名为空(即本地主机),端口号为5005
s.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 dispatcher
import socket, asyncore
PROT = 5005
class 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-8
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 5005
class 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 line
class 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-8
from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore
PORT = 5005
NAME= 'TestChat'
class ChatSession(async_chat):
"""
处理服务器和一个用户之间连接的类
"""
def __init__(self, server, sock):
# 标准设置任务:
async_chat.__init__(self, sock)
self.server = server
self.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 tasks
dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(('',port))
self.listen(5)
self.name = name
self.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 命令:
重复登陆:
退出: