程序设计十二:线上聊天室(网络编程+多线程)

2023-11-13

程序设计十二:网络编程

在这里插入图片描述

1.Manager类

使用Manager类实现服务器功能:

_recv()内部方法接收消息,

_broadcast()方法向所有用户广播,

_private_chat()方法发送私信(定向转发),

chat()方法实现收发消息的完整过程,并实现所有聊天记录的保存

class Manager:
    def __init__(self, conn, addr, username=''):  # 实质是建立了一个客户端对象,addr从accept中获取
        self.socket = conn
        self.ip = addr[0]
        self.port = addr[1]
        self.identify = '{}-{}'.format(self.ip, self.port)
        self.username = username

    def _recv(self, conn):  # 接收消息
        while True:
            data = conn.recv(BS).decode("utf-8")
            if not data or data == '再见!':
                break
            else:
                return data

    def _broadcast(self, message, sender):  # 发送广播
        for identify in clients.keys():
            if sender != usernames[identify]:
                clients[identify].socket.send(('%s %s:%s' % (get_time(), sender, message)).encode("utf-8"))

    def _private_chat(self, message, sender, receiver):  # 发送私信
        for identify in clients.keys():
            if usernames[identify] == receiver:
                clients[identify].socket.send(('%s %s[私信]:%s' % (get_time(), sender, message)).encode("utf-8"))
                break

    def chat(self):  # 与客户端交互:收发消息
        log = open('server_log.txt', 'a')
        log.write('\n#### 服务器数据 ####\n')
        print('{}尝试连接'.format(self.identify))
        log.write('{}尝试连接\n'.format(self.identify))
        name = self._recv(self.socket)
        if not name:
            return
        self.username = name
        clients[self.identify] = self  # 客户列表中加入客户 { identify:client }
        usernames[self.identify] = self.username
        # 处理连接成功信息
        print('用户 %s 已连接!' % self.username)
        log.write('用户 %s 已连接!\n' % self.username)
        self.socket.send("您已连接".encode("utf-8"))
        user_connect_message = ('我已进入聊天室')
        self._broadcast(user_connect_message, self.username)
        # 开始聊天
        while True:
            message = self._recv(self.socket)
            if not message:
                break
            elif message.split(' ')[0] == '@':  # 私信
                receiver = message.split(' ')[1]
                self._private_chat(message, self.username, receiver)
                print("%s (%s) %s private chat with %s: %s" %
                      (get_time(), self.identify, self.username, receiver, message))
                log.write("%s (%s) %s private chat with %s: %s\n" %
                          (get_time(), self.identify, self.username, receiver, message))
            else:  # 广播
                print("%s (%s) %s: %s" % (get_time(), self.identify, self.username, message))
                log.write("%s (%s) %s: %s\n" % (get_time(), self.identify, self.username, message))
                self._broadcast(message, self.username)
        # 断开连接:信息发送、用户删除、连接终止
        print("%s(%s) 断开连接" % (self.ip, self.port))
        log.write("%s(%s) 断开连接\n" % (self.ip, self.port))
        user_exit_message = ('再见!\n%s已退出聊天室' % self.username)
        self._broadcast(user_exit_message, self.username)
        clients.pop(self.identify)
        usernames.pop(self.identify)
        log.close()
        self.socket.close()

2.Chatter类

Chatter类实现用户端,send()发送消息并写入日志,recv()接收消息并写入日志。

class Chatter:
    def __init__(self, conn):
        self.socket = conn

    def send(self):
        username = input('用户名:')
        self.socket.send(username.encode('utf-8'))
        log.write('\n##### 用户 %s 聊天记录#####\n'%username)
        while not exit_event.is_set():
            message = input('')
            log.write(message+'\n')
            self.socket.send(message.encode('utf-8'))
            if message == '再见!':
                exit_event.set()

    def recv(self):
        while not exit_event.is_set():
            message = self.socket.recv(BS).decode('utf-8')
            print(message)
            log.write(message+'\n')

3.服务器的实现

创建多个线程,实现用同一个端口服务多个用户,代码如下:

def main():
    if len(sys.argv) != 3:
        print('usage: python my_server.py ip port')
    else:
        ip = sys.argv[1]
        port = int(sys.argv[2])
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((ip, port))  # 设置绑定监听的ip和port
        server.listen(10)
        print("服务器已开启,正在监听{}".format(server.getsockname()))

        while True:
            conn, addr = server.accept()  # 接受客户端连接,获取其地址
            client = Manager(conn, addr)  # 创建客户
            t = Thread(target=client.chat, args=())
            t.start()

4.用户端的实现

每个用户端启用两个线程,分别处理接收和发送代码如下:

def main():
    if len(sys.argv) != 3:
        print('usage: python my_client.py ip port')
    else:
        ip = sys.argv[1]
        port = int(sys.argv[2])
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            client.connect((ip, port))
            chat1 = Chatter(client)
            chat2 = Chatter(client)

            send_thread = Thread(target=chat1.send, args=())
            recv_thread = Thread(target=chat2.recv, args=())
            send_thread.start()
            recv_thread.start()
            send_thread.join()
            recv_thread.join()
        finally:
            print("连接已被关闭")
            client.close()

5.多机测试结果

启动多台电脑作为用户,连接服务器,构成聊天室,测试结果如下:

服务器窗口截图:

在这里插入图片描述

用户一窗口截图:

在这里插入图片描述

用户二窗口截图:

在这里插入图片描述

用户三窗口截图:

在这里插入图片描述

从上述结果可以看到:

  1. 每个人都可以向聊天室发送消息,消息会广播给除自己以外的其他人
  2. 用户之间可以发送私信,私信格式为 @ <receiver> <message>且私信不会被其他人看到
  3. 用户发送'再见!'时会自动退出聊天室,此时聊天室会发送广播告知用户已经退出

6.聊天记录保存

用户退出聊天室之后,会自动生成'client_log.txt'文件,保存该用户的聊天记录,上述跨机演示结束后,自动保存的聊天记录如下:

用户1

在这里插入图片描述

用户2

在这里插入图片描述

同时,服务器也会保存所用的聊天数据,并按照用户分类储存,截图如下:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

程序设计十二:线上聊天室(网络编程+多线程) 的相关文章

  • 从数据框中按索引删除行

    我有一个数组wrong indexes train其中包含我想从数据框中删除的索引列表 0 63 151 469 1008 要删除这些索引 我正在尝试这样做 df train drop wrong indexes train 但是 代码失败
  • 如何迭代按值排序的 Python 字典?

    我有一本字典 比如 a 6 b 1 c 2 我想迭代一下by value 不是通过键 换句话说 b 1 c 2 a 6 最直接的方法是什么 sorted dictionary items key lambda x x 1 对于那些讨厌 la
  • 如何使用 Plotly 中的直方图将所有离群值分入一个分箱?

    所以问题是 我可以在 Plotly 中绘制直方图 其中所有大于某个阈值的值都将被分组到一个箱中吗 所需的输出 但使用标准情节Histogram类我只能得到这个输出 import pandas as pd from plotly import
  • 如何使用 imaplib 获取“消息 ID”

    我尝试获取一个在操作期间不会更改的唯一 ID 我觉得UID不好 所以我认为 Message ID 是正确的 但我不知道如何获取它 我只知道 imap fetch uid XXXX 有人有解决方案吗 来自 IMAP 文档本身 IMAP4消息号
  • 通过列表理解压平列表列表

    我正在尝试使用 python 中的列表理解来展平列表 我的清单有点像 1 2 3 4 5 6 7 8 只是为了打印这个列表列表中的单个项目 我编写了这个函数 def flat listoflist for item in listoflis
  • 如何在 pytest 中将单元测试和集成测试分开

    根据维基百科 https en wikipedia org wiki Unit testing Description和各种articles https techbeacon com devops 6 best practices inte
  • 填充两个函数之间的区域

    import matplotlib pyplot as plt import numpy as np def domain x np arange 0 10 0 001 f1 lambda x 2 x x 2 0 5 plt plot x
  • 如何计算numpy数组中元素的频率?

    我有一个 3 D numpy 数组 其中包含重复的元素 counterTraj shape 13530 1 1 例如 counterTraj 包含这样的元素 我只显示了几个元素 array 136 129 130 103 102 101 我
  • 使用 OLS 回归预测未来值(Python、StatsModels、Pandas)

    我目前正在尝试在 Python 中实现 MLR 但不确定如何将我找到的系数应用于未来值 import pandas as pd import statsmodels formula api as sm import statsmodels
  • Python urllib.request.urlopen:AttributeError:'bytes'对象没有属性'data'

    我正在使用 Python 3 并尝试连接到dstk 我收到错误urllib包裹 我对SO进行了很多研究 但找不到与这个问题类似的东西 api url self api base street2coordinates api body jso
  • 从 python 发起 SSH 隧道时出现问题

    目标是在卫星服务器和集中式注册数据库之间建立 n 个 ssh 隧道 我已经在我的服务器之间设置了公钥身份验证 因此它们只需直接登录而无需密码提示 怎么办 我试过帕拉米科 它看起来不错 但仅仅建立一个基本的隧道就变得相当复杂 尽管代码示例将受
  • 使用鼻子获取设置中当前测试的名称

    我目前正在使用鼻子编写一些功能测试 我正在测试的库操作目录结构 为了获得可重现的结果 我存储了一个测试目录结构的模板 并在执行测试之前创建该模板的副本 我在测试中执行此操作 setup功能 这确保了我在测试开始时始终具有明确定义的状态 现在
  • 奇怪的 MySQL Python mod_wsgi 无法连接到 'localhost' (49) 上的 MySQL 服务器问题

    StackOverflow上也有类似的问题 但我还没有发现完全相同的情况 这是在使用 MySQL 的 OS X Leopard 机器上 一些起始信息 MySQL Server version 5 1 30 Apache 2 2 13 Uni
  • Seaborn Pairplot 图例不显示颜色

    我一直在学习如何在Python中使用seaborn和pairplot 这里的一切似乎都工作正常 但由于某种原因 图例不会显示相关的颜色 我无法找到解决方案 因此如果有人有任何建议 请告诉我 x sns pairplot stats2 hue
  • 如何在 python 中没有 csv.reader 迭代器的情况下解析单行 csv 字符串?

    我有一个 CSV 文件 需要重新排列和重新编码 我想跑 line line decode windows 1250 encode utf 8 在由 CSV 读取器解析和分割之前的每一行 或者我想自己迭代行 运行重新编码 并仅使用单行解析表单
  • Python 将日志滚动到变量

    我有一个使用多线程并在服务器后台运行的应用程序 为了无需登录服务器即可监控应用程序 我决定包括Bottle http bottlepy org为了响应一些HTTP端点并报告状态 执行远程关闭等 我还想添加一种查阅日志文件的方法 我可以使用以
  • 默认情况下,Keras 自定义层参数是不可训练的吗?

    我在 Keras 中构建了一个简单的自定义层 并惊讶地发现参数默认情况下未设置为可训练 我可以通过显式设置可训练属性来使其工作 我无法通过查看文档或代码来解释为什么会这样 这是应该的样子还是我做错了什么导致默认情况下参数不可训练 代码 im
  • 限制 django 应用程序模型中的单个记录?

    我想使用模型来保存 django 应用程序的系统设置 因此 我想限制该模型 使其只能有一条记录 极限怎么办 尝试这个 class MyModel models Model onefield models CharField The fiel
  • 列表值的意外更改

    这是我的课 class variable object def init self name name alias parents values table name of the variable self name 这是有问题的函数 f
  • Scrapy Spider不存储状态(持久状态)

    您好 有一个基本的蜘蛛 可以运行以获取给定域上的所有链接 我想确保它保持其状态 以便它可以从离开的位置恢复 我已按照给定的网址进行操作http doc scrapy org en latest topics jobs html http d

随机推荐

  • 没钱也能创业么?没钱怎样创业?

    一 先说结果不但并不是没钱就不能创业 反而是一切追求完美资本的人 无论有钱没钱 都能够且应当创业 二 界定问题我还在风投领域当上五六年兵线 眼界过上百个各式各样企业和创业者 项目投资总额度过亿人民币 也用掉过他人项目投资帮我的上百万用以创业
  • 组成最大数 华为机试

    题目描述 给定一组非负整数 重新排列它们的顺序使之组成一个最大的整数 示例 输入 10 2 输出 210 输入 3 30 34 5 9 输出 9534330 解题思路 回溯法 代码 package Huawei import java ut
  • Mysql分库分表实战(一)——一文搞懂Mysql数据库分库分表

    由于业务需要 需要对Mysql数据库进行分库分表 故而最近一直在整理分库分表的相关知识 现手上的工作也告一段落了 抽空将自己最近的学习结果转化为博文 分享给大家 本博文打算做成一个系列的 首先是分库分表的理论知识的了解 其次是基于Java编
  • 方差分析中怎么看有无显著性影响_一文带你轻松掌握,重复测量方差分析

    在某些实验研究中 常常需要考虑时间因素对实验的影响 当需要对同一观察单位在不同时间重复进行多次测量 每个样本的测量数据之间存在相关性 因而不能简单的使用方差分析进行研究 而需要使用重复测量方差分析 案例 当前有这样一项关于抑郁症的研究 共有
  • html中%20是什么意思?

    两个空格的话就是两个 20 转载于 https www cnblogs com linsx p 6943985 html
  • shell select用法

    select 是 Bash shell 中的一个命令 用于在终端中创建交互式菜单 select 语法格式如下 select varname in list do command1 command2 done 其中 varname 是一个变量
  • 竞争条件(race condition)

    在一些操作系统中 协作的进程可能共享一些彼此都能读写的公用存储区 这个公用存储区可能在内存中 可能是在内核数据结构中 也可能是一个共享文件 这里共享存储区的位置并不影响通信的本质及其带来的问题 为了理解实际中进程间通信如何工作 我们考虑一个
  • linux下五颜六色的文件——具体含义

    1 有一个伟人说过 Linux下一切都是文件 没错 他说的很对 但是文件又有很多汇总类型 Linux系统中用不同的颜色先大致区分一下 2 LINUX下不同的文件类型有不同的颜色 绿色文件 可执行文件 可执行的程序 红色文件 压缩文件或者包文
  • 【数论基础】—— 二项式定理

    二项式定理 内容 x y n
  • MySQL 使用方法简单教程

    目录 启动MySQL服务器 进入mysql交互操作界面 退出MySQL操作界面 第一条命令 多行语句 使用SHOW语句找出在服务器上当前存在什么数据库 创建一个数据库abccs 选择你所创建的数据库 创建一个数据库表 显示表的结构 查询所有
  • stm32cubemx 多路adc采集

    采用的软件是STM32CUBEMX KEIL5 硬件为stm32F103C8T6 我与原文作者做的区别在于 External Trigger Conversion Edge 我在进行配置的时间没有None选项 我选择的是默认的Regular
  • 线代矩阵相乘笔算的新简捷方法

    本矩阵相乘简捷笔算方法做法与传统本质一样 但对部分人来说这样列式子图更容易记忆和准确计算 O O 2013年12月写 2016年5月23日略修正 摘 矩阵的乘法定义 以前这种笔算方法太麻烦 难以快速看出所得矩阵的行列数或者是容易算少算多了一
  • Raki的NLP竞赛topline解读:NBME - Score Clinical Patient Notes

    Description 当你去看医生时 他们如何解释你的症状可以决定你的诊断是否准确 当他们获得执照时 医生们已经有了很多写病人笔记的练习 这些笔记记录了病人的主诉历史 体检结果 可能的诊断和后续护理 学习和评估写病人笔记的技能需要其他医生
  • MySql 生成自定义格式的编码_并更新保存到表指定的表字段

    生成出来的编码格式例子 YHCF0000000001 YHCF0000000002 YHCF0000000010 YHCF0000000011 mysql sql 获取最大的编码数据 生成新的自定义格式的编码 并更新保存到表指定的表字段 u
  • PHP加密方式

    PHP加密方式分为单项散列加密 对称加密 非对称加密这几类 像常用的MD5 hash crypt sha1这种就是单项散列加密 单项散列加密是不可逆的 像URL编码 base64编码这种就是对称加密 是可逆的 就是说加密解密都是用的同一秘钥
  • Unity3D 5 官方教程:地形设置

    地形工具栏的最后一个工具是设置 设置检视器 设置被提供用于如下描述的一些全局使用和渲染选项 基本地形 属性 功能 Draw 切换地形渲染开关 Pixel Error 在地形贴图 高度图 纹理等等 与生成地形之间的贴图精确度 更高的值为更低的
  • 剑指offer--顺时针打印矩阵

    题目描述 输入一个矩阵 按照从外向里以顺时针的顺序依次打印出每一个数字 例如 如果输入如下矩阵 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1 2 3 4 8 12 16 15 14 13
  • stop容器

    docker ps 查看所有正在运行容器 docker stop containerId containerId 是容器的ID docker ps a 查看所有容器 docker ps a q 查看所有容器ID docker stop do
  • Google Protocol Buffer 的使用和原理

    Google Protocol Buffer 的使用和原理 刘 明 软件工程师 上海交大电子与通信系 简介 Protocol Buffers 是一种轻便高效的结构化数据存储格式 可以用于结构化数据串行化 很适合做数据存储或 RPC 数据交换
  • 程序设计十二:线上聊天室(网络编程+多线程)

    程序设计十二 网络编程 1 Manager类 使用Manager类实现服务器功能 recv 内部方法接收消息 broadcast 方法向所有用户广播 private chat 方法发送私信 定向转发 chat 方法实现收发消息的完整过程 并