基于UDP协议的GUI聊天室-Python-Linux

1. 设计题目

基于 UDP 协议的 GUI 聊天室实现

2. 开发环境

Windows 10
阿里云 ECS 服务器 ubuntu_18_04_x64(无图形化界面)

3. 开发工具

FinalShell 3.0.10
Idle (Python 3.7.3 Shell)
ecs-workbench 工作台

4. 设计思想

4.1 服务器、系统、编程语言的选择

服务器这里选择的是阿里云 ECS(Elastic Compute Service),是一种弹性可伸缩的 计算服务,支持根据业务波动随时扩展和释放资源,简单概括其优点:

  1. 提供虚拟防火墙、角色权限控制、内网隔离、防病毒攻击及流量监控等多重安全方案;
  2. 支持通过内网访问其他阿里云服务,形成丰富的行业解决方案,降低公网流量成本;
  3. 提供行业通用标准 API,提高易用性和适用性。

系统这里选择的是基于 Linux 内核的 Ubuntu 系统,简单概括六个优点:

  1. 免费开源;
  2. 安全稳定;
  3. 模块化程度高;
  4. Liunx 系统广泛的硬件支持;
  5. 多用户,多任务;
  6. 良好的可移植性。

编程语言选择 Python,其优点有:

  1. 易于阅读和维护
  2. 有一个广泛的标准库
  3. python 支持各种主流数据库之间的交互
  4. 可扩展性和可移植性
  5. GUI 编程【图形化界面】
  6. 可嵌入型【可以将 python 程序嵌入到 c++中】
4.2 GUI(图形用户界面)实现工具

Python 的 GUI 可以通过 Tkinter、PyQt、wxPython、PySide 等实现。

  1. Tkinter 是 Python 的标准 Tk GUI 工具包的接口,实现较为简单;
  2. PyQt 是由 Python 语言调用 Qt 图形库实现的一个 Python 模块集,具有 300 多个类,近 6000 个函数和方法,分为 GPL 版本和商业版;
  3. wxPython 是跨平台 GUI库 wxWidgets 进行 Python 封装后以 Python 模块 形式提供给用户;
  4. PySide 是 PyQt 的 LGPL 版本,提供与 PyQt 类似的功能与 API 函数。 这些实现 Python GUI 的模块都可以运行在 UNIX、Linux、Windows、Mac 等主 流操作系统之上。

我这里选择实现较为简单的 Tkinter。

4.3 OSI 体系中传输层协议的选择

传输层协议有 TCP、UDP 协议,先了解一下他们:

  1. TCP 协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议;
  2. UDP 协议:用户数据报协议,无连接、不可靠。

TCP 与 UDP 基本区别:

  1. 基于连接与无连接。
  2. TCP 要求系统资源较多,UDP 较少。
  3. UDP 程序结构较简单。
  4. 流模式(TCP)与数据报模式(UDP)。
  5. TCP 保证数据正确性,UDP 可能丢包。
  6. TCP 保证数据顺序,UDP 不保证。
  7. TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发 送数据之前不需要建立连接。
  8. TCP 提供可靠的服务,传送的数据,无差错, 不丢失,不重复; UDP 尽最大努力交付,即不保证可靠交付。
  9. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP 是面向报文的,UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)。
  10. 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和 多对多的交互通信。
  11. TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道。

那么我们做聊天室用那种比较好呢,我先去了解了全国最大的社交平台之一 QQ 是如何使用协议的,查阅了一些博客,简单概括就是 UDP 为主 TCP 为辅:

  1. 用户实际上通过服务器交互,UDP 减少服务负担;
  2. 超高的并发量,海量的数据包,以效率优先,使用 UDP 协议依靠辅助的算法完成较为可靠的传输控制;
  3. 网络环境复杂,UDP 包能够穿透大部分的代理服务器;
  4. 登陆 QQ 后,依靠 TCP 连接保持在线状态。
  5. 对业务可靠性要求高的用 TCP 协议,不高的用 UDP 协议。

本次实验使用 UDP 协议实现简单的 GUI 聊天室。

4.4 程序的并发

GUI 聊天室支持多人在线聊天,所以程序一定是多进程/多线程并发的,每个进程或者线程负责与一个客户端通讯。Python 中内置了多线程功能支持,封装了对底层操作系统的调度方式,简化了 Python 的多线程编程,我们这里直接使用 socketserver 中的 Threading 模块来完成线程的实现。都说python的线程是假线程,很鸡肋,我不太了解python虚拟机,不多说了。

5. 设计步骤

5.1 访问规则之开通 UDP 协议

首先,阿里云 ECS 服务器默认没有对 UDP 协议授权。要使用 UDP 协议进行通讯就要开通 UDP 协议并设置端口范围。
在云服务器管理控制台,点击网络与安全→安全组:
在这里插入图片描述
在安全组列表选择一个实例进行修改,在访问规则中进行手动添加,设置协议类型,端口范围,优先级等等,根据提示完成操作即可。

5.2 服务器端程序实现

对于简单多人聊天室的设计,这里首先考虑对客户端的身份进行判断,是否为新用户,然后进行不同的响应,最后广播给每一个在线的客户端。

# -*- coding: utf-8 -*-
import socketserver
#继承DatagramRequestHandler重写handle
class Chat_server(socketserver.DatagramRequestHandler):
    def handle(self):
        try:
            (data_b,conn)=self.request
            addr = self.client_address
            print('data_b=',data_b)
            #判断是否为新接收的客户端
            if addrs.count(addr)==0:
                conns.append(conn)
                addrs.append(addr)
                name_s=data_b.decode('utf-8')
               #调用字典类型函数,将组成的键值对放入字典
                users.setdefault(addr,name_s)
                data='Welcome '+name_s+'!'
                data_s=''
            else:
                name_s=users.get(addr)
                data_s=data_b.decode('utf-8')
                data=name_s+': '+data_s
                print('data=',data)
                data_b = data.encode('utf-8')
            #将两个列表组合成元组
            for cn in zip(conns,addrs):
                cn[0].sendto(data_b,cn[1])
            if data_s.upper()[0:3]=='BYE':
                print('%s is exited!' % name_s)
                #删除连接客户端的对象conn
                conns.remove(conn)
                addrs.remove(addr)
                #删除以addr为键的数据
                del(users[addr])
        except Exception as e:
            print('Error is ',e)
#定义列表变量,存储与客户端的连接对象conn
conns=[]
#定义列表变量,存储客户端的地址
addrs=[]
#定义字典变量,存储与客户端的连接对象conn和客户端用户名称name_s
users={}
ip=''
server=socketserver.ThreadingUDPServer((ip,9999),Chat_server)
print('Bind UDP on 9999...')
#以无限循环形式处理客户端请求
server.serve_forever()
5.3 客户端程序实现

对于简单多人聊天室的客户端,首先考虑设计 GUI(图形用户界面),要有输入框, 消息显示框,控制消息发送的按钮,和显示框的滑动条。 对于消息发送的控制,这里设计一个命令按钮的事件响应函数进行消息发送的控制 ,对于消息接收要使接收到的数据可视化。

import socket
import threading
import tkinter as tk
import sys
#命令按钮的事件响应函数
def send_msg():
    #从字符串对象中获取按钮的文本
    txt=bt_txt.get()
    if txt=='Logon':
        #设置按钮文本
        bt_txt.set('Send')
    msg=et_txt.get().strip()
    et_txt.set('')
    print('msg=',msg)
    #将msg内容转化为bytes字节流并保存在msg_b
    msg_b=msg.encode('utf-8')
    #向服务器发送消息
    client.sendto(msg_b, (ip, 9999))
    #判断前三个字符
    if msg.upper()[0:3]=='BYE':
        #设置按钮状态,不在响应鼠标单击
        chat_send.config(state=tk.DISABLED)
        client.close()
        sys.exit(0)
#该函数为线程执行代码,用于从服务器接收消息并显示
def receive_msg():
    while True:
        try:
            #接收服务器发来的消息
            (data_b,addr)=client.recvfrom(1024)
            #将字节流数据转换为字符串
            data_s=data_b.decode('utf-8')
            if not data_s:
                continue
            #调试和监测运行
            print('data_s=',data_s)
            #插入列表框第一行
            chat_list.insert(0,data_s)
            #使新插入的数据可视
            chat_list.see(0)
        except Exception as e:
            print('Error is ',e)
            print('Exit!')
            break
ip='服务器端ip地址'
#先在本地测试
#ip='127.0.0.1'
#建立socket对象,SOCK_STREAM为TCP方式,SOCK_DGRAM为UDP方式
client=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.connect_ex((ip,9999))
#创建线程,执行自定义函数receive_msg()
t=threading.Thread(target=receive_msg)
t.start()

#创建窗口root
root=tk.Tk()
root.title('Chatting room')
root.geometry('300x350')
root.resizable(width=False,height=True)
#在窗口root中定义框架fm
fm=tk.Frame(root,width=300,height=300)
#在框架fm中定义滚动条scrl
scrl=tk.Scrollbar(fm)
#在框架fm中定义列表框chat_list
chat_list=tk.Listbox(fm,width=300,selectmode=tk.BROWSE)
#设置列表框chat_list纵向滚动由滚动条scrl控制
chat_list.configure(yscrollcommand=scrl.set)
#设置滚动条scrl的命令响应为列表框chat_list的纵向滚动显示
scrl['command']=chat_list.yview
#定义字符串对象,作为命令按钮的文本连续变量
bt_txt=tk.StringVar(value='Logon')
et_txt=tk.StringVar(value='')
#定义单行编辑框并与et_txt绑定
chat_txt=tk.Entry(root,bd=5,width=280,textvariable=et_txt)
#定义按钮并于bt_txt绑定,事件处理函数为send_msg
chat_send=tk.Button(root,textvariable=bt_txt,command=send_msg)
#定义scrl在框架右侧,以纵向充满方式显示
scrl.pack(side=tk.RIGHT, fill=tk.Y)
lb = tk.Label(root,text='')
#显示
chat_txt.pack()
chat_send.pack()
chat_list.pack()
fm.pack()
lb.pack()
#显示主窗口root并接收操作
root.mainloop()

6. 运行结果

这里用了一个 Linux 服务端,四个 Windows 客户端进行测试。 首先,三个玩家进入多人聊天室,大家都可以很流畅的交流,中途加入四号, 依旧可以流畅的交流,然后 4 号又走了,还是可以很流畅的交流,体验感杠杠的!

下图为整体效果,黑色背景为服务端,三个白色框框为客户端,由于 4 号 byebye 了,所以它的客户端关闭了。

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43845524/article/details/106004243