最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

练习项目09:在线聊天室(上)

Python教程 小楼一夜听春语 7279浏览 0评论

这个练习项目来自《Python基础教程(第2版)》,案例原名为“虚拟茶话会”。

其实,这个项目就是要实现一个简单的在线聊天室。

在完成这个项目之前,我们需要开启Windows系统的Telnet客户端。

在系统的【控制面板】-【程序和功能】的窗口中,点击左侧的【打开或关闭Windows功能】。

在弹出的窗口中,勾选【Telnet客户端】,然后点击确定按钮,等待系统设置完成。

这个Telnet客户端用于模拟用户和我们编写的聊天室服务器进行通信。

这个项目练习分为两个两个阶段。

第一阶段:了解服务器的搭建,实现基本通讯功能;

第二阶段:实现用户登录、发言、查看在线用户、离开聊天室以及向所有登录用户推送系统信息和用户发言等功能。

我们先来完成第一阶段的目标。

在这里,我们需要使用Python的内置模块中的aysncore模块。

使用aysncore模块主要是为了实现多用户的同时连接。

在aysncore模块包含了一个dispatcher类,通过这个类能够创建套接字对象。

除此之外,我们之后会使用到这个类中的一些方法进行事件处理。

所以,在代码中,我们让服务器类继承自dispatcher类。

1、尝试搭建一个支持多用户连接的服务器。

实现这个服务器,代码比较简单,并且在之前的课程中我们也接触过相关内容。

大家通过代码中的注释,基本就能够理解。

示例代码:(服务器)

import asyncore
from asyncore import dispatcher

class ChatServer(dispatcher):  # 定义聊天服务器类
    def __init__(self, port):  # 重写构造方法
        dispatcher.__init__(self)  # 重载超类的构造方法
        self.create_socket()  # 创建套接字对象
        self.set_reuse_addr()  # 设置地址可重用
        self.bind(('', port))  # 绑定本机地址与端口
        self.listen(5)  # 设置监听连接数

    def handle_accept(self):  # 重写处理客户端连接的方法
        ssl, addr = self.accept()  # 获取服务器端的SSL通道和远程客户端的地址
        ssl.send('您已成功连接服务器!'.encode())  # 发送欢迎信息
        print('连接来自:', addr[0], '端口:', addr[1])  # 显示输出连接的客户端信息

port = 6666  # 设置服务器端口号
server = ChatServer(port)  # 实例化聊天服务器
try:
    asyncore.loop()  # 运行异步循环
except KeyboardInterrupt:  # 捕获键盘中断异常
    print('服务器已被关闭!')

注意:代码中的set_reuse_addr()方法能够保证服务器未正常关闭时,再次开启服务器能够重用端口号。因为服务器异常关闭时,可能导致端口依然被占用,新启动的服务器无法使用该端口。

运行上方代码,开启服务器。

这个时候,我们就可以通过telnet命令和服务器进行连接。

telnet命令为:telnet 127.0.0.1 6666或者telnet localhost 6666

当然,我们也可以编写一个客户端进行连接。

示例代码:(客户端)

import socket

server = socket.socket()
host = socket.gethostname()
port = 6666
server.connect((host, port))
print(server.recv(1024).decode())

上方的客户端代码运行之后,获取到欢迎信息就自动退出了。

如果是通过Windows系统中的命令行终端启动服务器的话,可以通过快捷键<Ctrl+C>进行关闭(按完之后可能要等一小会儿),这时except语句会捕获KeyboardInterrupt异常,在命令行窗口中显示输出文字信息“服务器已关闭!”。

2、再次实现聊天服务器,添加处理连接会话的功能。

这一次服务器的实现,我们需要使用asynchat模块。

asynchat模块完成了大部分对套接字的读写操作,我们接下来只需要重写模块中的collect_incoming_data()方法和found_terminator()方法。

大家先来看再次实现的服务器代码。

示例代码:(服务器)

import asyncore
from asyncore import dispatcher
from asynchat import async_chat

class ChatSession(async_chat):
    def __init__(self, sock):
        async_chat.__init__(self, sock)
        self.set_terminator('\r\n'.encode())  # 设置数据的终止符号
        self.data = []  # 创建数据列表
        self.push('欢迎进入聊天室!'.encode('GBK'))  # 向单个客户端发送欢迎信息

    def collect_incoming_data(self, data):  # 重写处理客户端发来数据的方法
        self.data.append(data.decode())  # 将客户端发来的数据添加到数据列表

    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()
        self.set_reuse_addr()
        self.bind(('', port))
        self.listen(5)
        self.sessions = []

    def handle_accept(self):
        ssl, addr = self.accept()
        self.sessions.append(ChatSession(ssl))  # 将新的用户连接会话添加到会话列表

port = 6666
server = ChatServer(port)
try:
    asyncore.loop()
except KeyboardInterrupt:
    print('服务器已被关闭!')

在上方代码中,当服务器运行后,每一个来自客户端的连接,都会被作为ChatSession类的参数,实例化为一个会话对象。

在会话对象中,来自客户端的数据内容通过collect_incoming_data()方法进行读取、处理和暂存,并通过found_terminator()方法检测数据中是否包含设置的指定终止符号,当发现终止符号时,对所有的暂存数据进行处理(例如,将发言内容推送给聊天室中所有的在线用户)。

注意,代码中的push()方法能够像单个客户端发送数据内容,发送数据内容时注意进行编码,编码格式为“GBK”,因为不进行编码的话,当发送的内容包含中文时,通过telnet连接所收到的内容会变成乱码。

这里,为了便于测试,我们将客户端也进行更新。

同样要注意,接收内容时要进行解码,编码的格式和push()方法中的格式保持一致。

示例代码:(客户端)

import socket

server = socket.socket()
host = socket.gethostname()
port = 6666
server.connect((host, port))
print(server.recv(1024).decode('GBK'))  # 注意解码以及编码格式
while True:
    name = input('请输入发言内容:')
    server.send('{}\r\n'.format(name).encode())  # 注意将输入内容加上终止符号

启动服务器,并运行客户端。

此时,客户端会收到来自服务器的欢迎信息。

当在客户端输入内容回车之后,服务端的运行窗口会显示来自客户端的内容。

大家可以启动多个客户端进行测试,每个客户端发出的内容都会显示在服务器的运行窗口中。

3、添加广播以及客户端断开连接的功能

在聊天室中,当一个用户发言时,其他用户都能够看到这条发言。

所以,我们需要在代码中添加广播功能,这也就是我们创建会话列表的原因。

将每一个来自客户端的连接保存为一个会话,添加到会话列表中,当广播内容时,遍历这个会话列表,将广播内容推送到每一个会话的客户端。

当然,当一个用户进入或离开聊天室,也就是打开或关闭会话连接时,我们需要将这个用户的连接会话从会话列表中添加或移除,并向其他会话广播该用户进入或离开的信息。(示例代码中只以离开为例,大家可以自行添加进入的广播代码。)

上面所说的这些功能,大家可以通过示例代码中的注释进行理解。

示例代码:(服务器)

from asynchat import async_chat
from asyncore import dispatcher
import asyncore

class ChatSession(async_chat):
    def __init__(self, server, sock, addr):
        async_chat.__init__(self, sock)
        self.server = server
        self.addr = addr
        self.set_terminator('\r\n'.encode())
        self.data = []
        self.push('欢迎进入{}聊天室!\r\n'.format(server.name).encode('GBK'))

    def collect_incoming_data(self, data):
        self.data.append(data.decode())

    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)  # 从会话列表中移除当前会话
        self.server.broadcast('{}离开聊天室!\r\n'.format(self.addr[0]))  # 广播当前会话客户端离开信息

class ChatServer(dispatcher):
    def __init__(self, port, name):
        dispatcher.__init__(self)
        self.create_socket()
        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('{}\r\n'.format(line).encode('GBK'))  # 向所有会话的客户端推送内容

    def handle_accept(self):
        conn, addr = self.accept()
        self.sessions.append(ChatSession(self, conn, addr))

if __name__ == '__main__':
    port = 6666
    name = 'Python'
    server = ChatServer(port, name)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        print('服务器已关闭!')

运行上方代码启动服务器。

因为需要在客户端显示服务器推送的信息,所以这里我们需要使用Telnet连接服务器。

打开多个命令行终端,每个都通过telnet命令连接服务器,这时每个终端都会显示来自服务器的欢迎信息。

当从任意一个终端输入内容并按下回车键发送,所有的终端中都会显示这条内容。

而且,当关闭任何一个命令行终端,其他的终端中都会显示服务器推送的用户离开信息。

到这里,这个练习项目的第一阶段我们就完成了。

本节练习源代码:【点此下载

转载请注明:魔力Python » 练习项目09:在线聊天室(上)

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)