python套接字(二):实现一个服务器和多客户端连接

news2024/11/20 1:35:12

文章目录

  • 前言
  • 一、问题
  • 二、实现一个服务器连接多个客户端
    • 1、问题分析
    • 2、代码实现
      • a、服务器端
      • b、客户端
    • 3、运行


前言

在上一篇博客python套接字(一):socket的使用简单说明了一下套接字的使用,也实现了使用套接字来传输消息,但是也有一个问题,就是这种实现方式只能一个服务器连接一个客户端,意味着有几个个客户就要创建结果服务器,而且客户端直接还不能通信,这样就和现实生活中的情况不符,接下来讲一下如何实现一个服务器和多个客户端进行连接。本篇博客参考了python+tcp实现多人聊天室。

一、问题

在上一章的基础上在执行客户端代码,相当于有两个客户端同时向服务器发送请求,会有如下结果:
客户端:
在这里插入图片描述
服务器:
在这里插入图片描述
你会发现,服务器上面既没有打印第二个客户端的信息,也没有显示第二个客户端发送的消息,说明服务器只能处理第一个客户端的消息。

二、实现一个服务器连接多个客户端

1、问题分析

为什么两个客户端都能连接服务器,但是服务器只能处理一个客户端的消息呢?因为服务器里面只有一个主线程,该线程接收到第一个客户端的连接之后,就腾不出手来解决其他线程了。要解决这个问题,就要使用到多线程。

2、代码实现

目标:模拟创建一个多人聊天室(类似微信群),一个人在上面发消息,所有客户端都能看到。
因为有些命令有特殊的功能,因此自定义了如下规则:

命令格式说明
name -n更改用户名为name并且重新进入聊天室
message -ta发送消息给聊天室的所有成员
exit退出聊天室

a、服务器端

服务器端不仅要接收源源不断的客户端请求,而且还要接收和发送数据,所以大概的设计思路如下:主线程负责对发起请求的客户创建链接,并且将每个用户对应的链接保存到一个字典中去,方便调用。对于每个用户链接,都创建两个子线程一个子线程用来发送数据另外一个子线程用来接收数据。实现代码如下:
tcp_server.py

import socket
from threading import Thread
import time
import sys


# 创建存储对象
class Node:
    def __init__(self):
        self.Name = None    # 用户名
        self.Thr = None     # 套接字连接对象


class TcpServer:
    user_name = {}  # 存储用户信息; dict 用户名:Node对象

    def __init__(self, port):
        """
        初始化服务器对象
        port:   服务器端口
        """
        self.server_port = port      # 服务器端口
        self.tcp_socket = socket.socket()       # tcp套接字
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)       # 端口重用
        self.tcp_socket.bind(self.server_port)

    def start(self):
        """
        启动服务器
        """
        self.tcp_socket.listen(10)      # 设置服务器接受的链接数量
        print(self.get_time(), "系统:等待连接")
        while True:
            try:
                conn, addr = self.tcp_socket.accept()       # 监听客户端的地址和发送的消息
            except KeyboardInterrupt:       # 按下ctrl+c会触发此异常
                self.tcp_socket.close()     # 关闭套接字
                sys.exit("\n" + self.get_time() + "系统:服务器安全退出!")        # 程序直接退出,不捕捉异常
            except Exception as e:
                print(e)
                continue

            # 为当前链接创建线程
            t = Thread(target=self.do_request, args=(conn, ))
            t.start()

    def do_request(self, conn):
        """
        监听客户端传送的消息,并将该消息发送给所有用户
        """
        conn_node = Node()
        while True:
            recv_data = conn.recv(1024).decode('utf-8').strip()     # 获取客户端发来的数据
            info_list = recv_data.split(" ")        # 切割命令

            # 如果接收到命令为exit,则表示该用户退出,删除对应用户信息,关闭连接
            if recv_data == "exit":
                msg = self.get_time() + " 系统:用户" + conn_node.Name + "退出聊天室!"
                print(msg)
                self.send_to_other(conn_node.Name, msg)
                conn.send('exit'.encode("utf-8"))
                self.user_name.pop(conn_node.Name)
                conn.close()
                break
            else:
                try:
                    A = info_list[-2], info_list[-1]
                except IndexError:
                    conn.send((self.get_time() + ' 系统:无法识别您的指令,请重新输入!').encode('gb2312'))
                    continue

            if info_list[-1] == '-n':
                # 新用户注册
                print(self.get_time() + ' 系统:' + info_list[0] + '连接成功')
                data_info = self.get_time() + ' 系统:' + info_list[0] + '加入了聊天'
                self.send_to_all(data_info)
                conn.send('OK'.encode('utf-8'))
                conn_node.Name = info_list[0]
                conn_node.Thr = conn
                self.user_name[info_list[0]] = conn_node
            elif info_list[-1] == '-ta':
                # 群发消息
                msg = self.get_time() + ' %s:' % conn_node.Name + ' '.join(info_list[:-1])
                self.send_to_all(msg)

    def send_to_all(self, msg):
        """
        对所有用户发送消息
        """
        print(msg)
        for i in self.user_name.values():
            i.Thr.send(msg.encode('utf-8'))

    def send_to_other(self, name, msg):
        """
        对除了当前发送信息的用户外的其他用户发送消息
        """
        # print("收到消息:" + msg)
        for n in self.user_name:
            if n != name:
                self.user_name[n].Thr.send(msg.encode('utf-8'))
            else:
                continue

    def get_time(self):
        """
        返回当前系统时间
        """
        return '[' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ']'


if __name__ == '__main__':
    HOST = "127.0.0.1"
    POST = 9999
    server = TcpServer((HOST, POST))
    server.start()

b、客户端

客户端就简单一点,只需要不断的发送数据和接收数据即可。主线程创建链接并和服务器连接。然后创建两个子线程,分别负责数据的接收和发送。代码实现如下:
tcp_clinet.py

import socket
from threading import Thread


class TcpClient:
    server_addr = ('127.0.0.1', 9999)

    def __init__(self):
        self.tcp_cli_socket = socket.socket()

    def msg_recv(self):
        """
        接收数据
        """
        while True:
            data = self.tcp_cli_socket.recv(1024)
            if data.decode("utf-8") == "exit":
                print('客户端退出')
                self.tcp_cli_socket.close()
                break

            print(data.decode("utf-8"))

    def msg_send(self):
        """
        发送数据
        """
        while True:
            data_info = input("请发言:")
            if data_info == "exit":
                self.tcp_cli_socket.send(data_info.encode("utf-8"))
                break
            else:
                self.tcp_cli_socket.send((data_info + ' -ta').encode("utf-8"))

    def start(self):
        """
        连接服务器
        """
        try:
            self.tcp_cli_socket.connect(self.server_addr)
        except Exception as e:
            print("连接失败,请重试!")
            self.tcp_cli_socket.close()
            print(e)
            return

        while True:
            name = input("请输入用户名:")
            self.tcp_cli_socket.send((name + ' -n').encode('utf-8'))
            data = self.tcp_cli_socket.recv(128).decode('utf-8')
            print(data)
            if data == "OK":
                print("你已成功进入聊天室")
                break
            else:
                print(data)

        t = Thread(target=self.msg_recv)
        t.start()
        t1 = Thread(target=self.msg_send)
        t1.start()


if __name__ == '__main__':
    client = TcpClient()
    client.start()

3、运行

启动一个服务器和两个客户端,两个客户端之间进行交流,服务器则负责转发它们发送的消息(有点瑕疵)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到它们发送的消息对方都能收到

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641122.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习应用篇-推荐系统[12]:经典模型-DeepFM模型、DSSM模型召回排序策略以及和其他模型对比

【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍:【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化…

JavaWeb笔记(五)

JavaWeb后端 经过前面的学习,现在终于可以正式进入到后端的学习当中,不过,我们还是需要再系统地讲解一下HTTP通信基础知识,它是我们学习JavaWeb的基础知识,我们之前已经学习过TCP通信,而HTTP实际上是基于T…

使用SonarLint在开发阶段提高代码质量

使用SonarLint在开发阶段提高代码质量 SonarLint是什么 SonarLint是一个免费的IDE插件,是一个代码质量工具。 它可以在我们编写代码的时候,就帮我我们发现问题并提醒我们。可以帮助我们养成良好的代码习惯。 它支持5000条规则,可以帮助我…

如何在Microsoft Excel中使用MATCH查找值的位置

当你需要在电子表格中查找值的确切位置时,可以使用 Excel 中的 MATCH 函数。这样可以避免你手动搜索可能需要参考的位置或其他公式。 MATCH 函数通常与 INDEX 函数一起用作高级查找。但在这里,我们将介绍如何单独使用 MATCH 来找到价值所在。 一、Excel中的MATCH函数是什么 …

11. Synchronized与锁升级

11.1 面试题 ● 谈谈你对Synchronized的理解 ● Sychronized的锁升级你聊聊 ● Synchronized实现原理,monitor对象什么时候生成的?知道monitor的monitorenter和monitorexit这两个是怎么保证同步的嘛?或者说这两个操作计算机底层是如何执行的 …

【企业业务架构】LEANIX : 业务能力

业务能力是组织执行核心功能所需的能力、材料和专业知识的表达或发声。企业架构师使用业务能力来说明业务的总体需求,以便更好地制定满足这些业务需求的 IT 解决方案。 目录 介绍业务能力建模您可以通过业务能力映射实现什么?并购管理IT风险管理创新管理…

手把手教你入门 Docker

博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家✌ Java知识图谱点击链接:体系化学习Java(Java面试专题) 💕💕 感兴趣的同学可以收…

DAY 76 分布式监控平台:zabbix

市场上常用的监控软件: 传统运维:zabbix、 Nagios云原生环境: Prometheus (go语言开发的) zabbix概述 作为一个运维,需要会使用监控系统查看服务器状态以及网站流量指标,利用监控系统的数据去…

国内云服务器全面对比

想要领取优惠券购买云服务可以前往我的云服务器领券购买。 经过疫情三年,大多行业开始复苏,企业开始布局以后得发展,云服务器作为企业发展几乎是必须的,一个企业从无到有,要经历很多,比如企业官网搭建&…

GaussDB云数据库SQL应用系列—索引管理

目录 一、前言 二、注意事项 三、索引创建 1、创建普通索引 2、创建唯一索引 3、创建多字段索引 4、创建部分索引 5、创建表达式索引 四、索引管理 1、查看索引信息 2、删除索引 总结 一、前言 随着互联网的快速发展,数据量呈现爆炸式增长。如何高效地…

PLC现场安装时需要注意的几个关键点

PLC适用于大多数工业现场,但它对使用场合、环境温度等还是有一定要求。控制PLC的工作环境,可以有效地提高它的工作效率和寿命。 在安装PLC时,要避开下列场所: 1.环境温度超过0 ~ 50℃的范围; 2.相对湿度超过85%或者…

Coggle 30 Days of ML 打卡任务二:苹果病害数据加载与数据增强

Coggle 30 Days of ML 打卡任务二:苹果病害数据加载与数据增强 任务二:苹果病害数据加载与数据增强 难度/分值:中/2 打卡内容: 参赛选手名称:AppleDoctor完成日期:2023.6.9任务完成情况: 使…

第四章 完型填空

第四章 完型填空 第一节 真题 2020-完型填空- Section I Use of English Directions: Read the following text. Choose the best word (s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Being a good parent is, of cour…

Vue中使用editor.md(1):简单使用

0. 背景 在Vue项目中添加一个markdown编辑器,选择使用editor.md,记录在Vue项目中的简单使用。 1. 环境配置 1.1 下载editor.md 官网地址:http://pandao.github.io/editor.md/ 项目文件解压后放入:public/static/内 1.2 下…

【Linux】进程间的通信之共享内存

进程间的通信之共享内存 一、system V 内存共享原理二、共享内存的使用1、ftok函数2、shmget函数3、shmat函数4、shmdt函数5、shmctl函数6、代码使用 三、一些细节的补充 一、system V 内存共享原理 利用内存共享进行进程间的通信的原理其实分为以下几个步骤: 在物…

chatgpt赋能python:Python如何将英文转化为中文的最佳方法

Python如何将英文转化为中文的最佳方法 介绍 在现代全球化社会中,国与国之间的交流越来越频繁,相应的语言翻译工具的需求也愈发迫切。Python是一种易于学习、快速上手的编程语言,适合初学者和经验丰富的程序员使用,在语言翻译方…

技术很牛逼,不会讲PPT,可惜了!

怎样才能做好一场技术分享呢?结合我的经历,做了一些总结。 2015年,我出版《技术管理之巅》以后,先后收到QCon、CSDN、IT168等业界知名技术大会的邀请担任分享嘉宾,几年下来发表了近百场技术及管理相关话题的分享&#…

工业4G路由器 小体积4G LTE通信模块转有线转WiFi充电桩视频安防监控物联网路由器上网CPE

4G LTE代表第四代长期演进,这是一种用于通过蜂窝网络提供高速数据传输的无线通信技术。它是移动网络技术的最新标准,提供比其前身3G更快的数据传输速度。它广泛用于移动设备、物联网设备和机器对机器通信。 近年来,随着物联网技术的快速发展…

探索现代软件架构:揭秘单体、SOA和微服务的进化的之路

1、单体服务、SOA、微服务区别 单体服务 是指一个应用程序中所有的功能都集成在一个单一的代码库中。这种设计模式简单易用,开发人员可以快速地开发和维护应用程序,但是也存在一些问题。例如,当应用程序需要添加新功能时,需要对整…

OpenCV项目开发实战--对图像种的对象进行无缝克隆-附Python、C++的代码实现

文末附基于Python和C++两种方式实现的测试代码下载链接 图 1:无缝克隆示例:一架飞机被克隆到傍晚天空的图片中。 OpenCV 3 中引入的令人兴奋的新功能之一称为无缝克隆。有了这个新功能,您可以从一个图像中复制一个对象,然后将其粘贴到另一个图像中,从而使构图看起来无缝…