使用socket和socket server基于UDP协议的GUI聊天室实现

1 设计内容

用Python语言编程实现一个基于UDP协议Socket/SocketServer架构的GUI聊天室程序。服务器为多进程/多线程并发, 客户端要求具备图形界面。

2 设计要求

1. 运行在Linux系统平台上
2.  Python语言编程
3. 基于TCP/IP socket/socketserver编程
4. 多进程/多线程并发
5. 客户端使用GUI图形界面

3 设计过程及设计步骤

  • 基于socket的服务器端程序设计
#!/usr/bin python3.6
#coding: utf-8
import socket
import threading

def broadcast_msg(udp_server,msg,addr,addr_list):  #遍历客户端,向每个客户端发送消息的线程调用函数
    for each_addr in addr_list: #依次获取addr_list列表中存在的链接进行编码并发送给客户端
        udp_server.sendto(msg.encode("utf-8"),each_addr)   #向each_addr客户端发送msg消息。
udp_server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)    #创建UDP服务对象
udp_server.bind(("0.0.0.0",6666))  #绑定服务器端   这里ip用0.0.0.0是默认的路由表地址,它可以表示本机任意的ipv4地址,可以方便局域网意外的客户端连接服务器
print("udp服务器正在运行中")
addr_list=[]    #客户端地址列表
users={}      #用户名和客户端地址组成的字典
while True:
    try:
        msg,addr=udp_server.recvfrom(1024)   #接收客户端发送的数据和地址
        try:
            msg=msg.decode("utf_8")
        except:
            msg=msg.decode("gbk")    #如果utf-8解码失败则用gbk解码
        if addr not in addr_list:  #判断addr是否在addr_lisr中  如果不在则做以下操作
            addr_list.append(addr)  #将addr加入到addr_list列表中
            name_s=msg;     #将接收的数据作为该客户端的用户名
            users.setdefault(addr,name_s)  #将addr和name_s作为键值对保存到users字典中
            data='Welcome  '+name_s+'!'     #用户第一次输入的消息内容作为用户名,服务器端发出这个用户的欢迎消息
            print(data)
            data_s=''  #将发送的数据清空
        else:
            name_s=users.get(addr)   #如果列表中原来有conn这个链接  则从users字典中获取conn的值
            data_s=msg   #将接收的消息赋值给data_s
            data=name_s+': '+data_s   #将name_s和data_s组成的字符串赋值给data
t=threading.Thread(target=broadcast_msg,args=(udp_server,data,addr,addr_list))    #创建一个线程去遍历向所有客户端发送消息,线程执行函数为broadcast_msg
        t.start()
        if msg.upper()[0:3]=='BYE':#如果接收到的数据是以bye开头的   则结束该客户端的对话
            if addr in addr_list:   #如果addr在addr_list列表中,  则从users字典和addr_list列表中删除这个addr,
                addr_list.remove(addr)
                del(users[addr])
                print("%s退出聊天室"%users.get(addr))
                continue
            else:
                continue
    except Exception as ret:
        print(ret)
udp_server.close()   #关闭服务器端程序
  • 2 基于socketserver的服务器端程序设计
#基于socketserver的GUI聊天室服务器端
#!/usr/bin python3.6
#coding: utf-8
import socketserver

class Chat_server(socketserver.DatagramRequestHandler):
    def handle(self):
        try:
            (data_b,conn)=self.request   #接收客户端的请求,返回接收数据和连接服务对象
            print("client_address:",self.client_address)#打印客户端的IP和端口
            print("data_b=",data_b)  #打印接收的数据
            if c_address.count(self.client_address)==0:  #统计c_address中self.client_address包含的次数,这里判断c_address列表中是否有self.client_address这个客户端的ip和端口
                conns.setdefault(self.client_address,conn)   #将self.client_address和conn作为键值对,保存到conns字典中
                c_address.append(self.client_address)    #将self.client_address添加到c_address列表中
                name_s=data_b.decode('utf-8')  #将接收到的数据解码
                users.setdefault(self.client_address,name_s)#将self.client_address和name_s作为键值对保存到users字典中
                data='Welcome  '+name_s+'!'     #用户第一次输入的消息内容作为用户名,服务器端发出这个用户的欢迎消息
                data_s=''  #将发送的数据清空
            else:
                name_s=users.get(self.client_address)   #如果列表中原来有self.client_address这个客户端  则从users字典中获取self.client_address这个键对应的的值
                data_s=data_b.decode('utf-8')#将接收到的数据解码
                data=name_s+': '+data_s   #将name_s和data_s组成的字符串赋值给data
            for c_addr in c_address:  #循环遍历c_address列表中的客户端ip和端口,从而向每个客户端依次发送消息
                print(u"{0} sento {1}".format(conns.get(c_addr),c_addr))  #打印出哪个发送服务对象向哪个客户端发送消息
                conns.get(c_addr).sendto(data.encode("utf-8"),c_addr)   #向指定的c_addr客户端发送data消息,,conns.get(c_addr)是从conns字典中获取c_addr键所对应的值(客户端对应的线程连接服务对象)
            if data_s.upper()[0:3]=='BYE':  #如果接收到的数据是以bye开头的   则结束该客户端的对话  并从users字典和conns字典中删除这个链接,
                print('%s is exited!'%name_s)
                del(conns[self.client_address])   #删除conns字典中包含self.client_address键的键值对
                c_address.remove(self.client_address)   #从c_address列表中删除self.client_address元素
                del(users[self.client_address])  #删除users字典中包含self.client_address键的键值对
            print('data=',data)
        except Exception as e:
            print('Error is ',e)
conns={}
c_address=[]
users={}
ip='0.0.0.0'
server=socketserver.ThreadingUDPServer((ip,6666),Chat_server)   #创建多线程服务对象
##server.setsockopt()
print('udp服务器正在运行中.....')
server.serve_forever()
  • 客户端程序设计
#GUI聊天室客户端
#!/usr/bin python3.6
#coding: utf-8
import socket
import threading
import datetime
import sys
import tkinter as tk
def send_msg():   #发送消息
    txt=bt_txt.get()   #获取bt_txt按钮的名称
    if txt=='Logon': #如果按钮的名称为LogOn    将按钮的名称改成send
        bt_txt.set('send')
    msg=et_txt.get()     #获取聊天输入框中输入的内容
    et_txt.set('')    #将聊天输入框的内容清空
    print('msg=',msg)
    msg_b=msg.encode('utf-8')
    s.sendto(msg_b,(ip,6666))   #向服务器发送客户输入的内容
    if msg.upper()[0:3]=='BYE':
        chat_send.config(state=tk.DISABLED)   #设置发送按钮为不可点击
        s.close()
        sys.exit()
        root.destroy()          #关闭聊天框   客户端退出

def receive_msg():  #接收服务器端发送的数据
    while True:
        try:
            data_b=s.recv(1024)     #接收服务器端发送的数据
            data_s=data_b.decode('utf-8')
            print('data_s=',data_s)
            if data_s!="":   #如果数据不为空  则显示数据和当前时间
                time=u"time:{0}".format(datetime.datetime.now().strftime("%Y.%m.%d-%H:%M:%S")).encode("utf-8") #格式化当前时间
                chat_list.insert(0,data_s)    #将接收到的数据加入聊天列表框的末尾
                chat_list.see(0)    # 显示加入到末尾的聊天列表的内容
                chat_list.insert(0,time)    #显示当前时间
                chat_list.see(0)    # 显示加入到末尾的聊天列表的内容
        except:
            print("exit bye")
            break    #接收到异常表示退出  接收循环接收

ip='192.168.1.4'
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)   #创捷套接字对象
s.connect_ex((ip,6666))   #建立和服务器之间的连接
#s.bind(("",8084))
t=threading.Thread(target=receive_msg)  #创建一个线程 ,去接收服务器端发送的数据
t.start()
root=tk.Tk()  #创建一个图形界面窗口的对象
root.title('Chatting room')  #设置窗口的名字
root.geometry('300x350')  #设置窗口的大小
root.resizable(width=False,height=True)
fm=tk.Frame(root,width=300,height=300)   #在root窗口中创建一个宽300和高300的Frame容器
scrl=tk.Scrollbar(fm)  #在fm中添加一个滚动条z
chat_list=tk.Listbox(fm,height=300,width=300,selectmode=tk.BROWSE)  #在fm中添加一个选择列表
#selectmode
#1. 决定选择的模式
#2. 四种不同的选择模式:"single"(单选)、"browse"(也是单选,但拖动鼠标或通过方向键可以直接改变选项)、"multiple"(多选)和 "extended"(也是多选,但需要同时按住 Shift 键或 Ctrl 键或拖拽鼠标实现)
#3. 默认是 "browse"
chat_list.configure(yscrollcommand=scrl.set)  #将scrl滚轮条设置聊天列表中
scrl['command']=chat_list.yview
#yview(*args)
#-- 该方法用于在垂直方向上滚动 Listbox 组件的内容,一般通过绑定 Scollbar 组件的 command 选项来实现(具体操作参考:Scrollbar)
#-- 如果第一个参数是 "moveto",则第二个参数表示滚动到指定的位置:0.0 表示最顶端,1.0 表示最底端
bt_txt=tk.StringVar(value='Logon')    #设置发送按钮的值
et_txt=tk.StringVar(value='')       #设置输入文本的初值
chat_txt=tk.Entry(root,bd=5,width=280,textvariable=et_txt)  #创建聊天室文本框
chat_send=tk.Button(root,textvariable=bt_txt,command=send_msg)  #创建发送按钮
scrl.pack(side=tk.RIGHT,fill=tk.Y)
chat_txt.pack()
chat_list.pack()
chat_send.pack()
fm.pack()
root.mainloop()   #设置聊天框循环

4 测试运行

  • 运行socketserver方式的chat_UDP_s.py程序和3个chat_UDP_s.py的客户端程序,运行结果如下:

在这里插入图片描述

  • socketserver方式下,第一个客户端输入bye结束第一个客户端的会话,并且第一个客户端结束服务程序,如下图所示:

在这里插入图片描述

  • 运行socket方式的chat_UDP_s.py程序和3个chat_UDP_s.py的客户端程序,运行结果如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值