[Socket]Python用UDP协议建立带有私聊功能的网络聊天室-建立聊天工具

这篇具有很好参考价值的文章主要介绍了[Socket]Python用UDP协议建立带有私聊功能的网络聊天室-建立聊天工具。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

前些天实习面试的时候被面试官问到Socket编程的问题,即“Socket创建和释放的具体过程是什么”,当时答不上来,似乎是涉及到发送和接收缓冲区的问题。由于自己在Socket编程这一块知识较为薄弱,于是写下这篇文章,当作复习下Socket编程知识。

首先,该实验是我在大三上学期上“泛在网技术”这门课时完成的,实验题目如下所示:

实验3:建立聊天工具
  1、实验目的:
      要求学生掌握Socket编程中流套接字的技术
  2、实验内容:
      i.要求学生掌握利用Socket进行编程的技术
      ii.必须掌握多线程技术,保证双方可以同时发送
      iii.建立聊天工具
      iv.可以和多个人同时进行聊天
      v.必须使用图形界面,显示双方的语录

我们可以看到实验要求,即利用Socket编程技术建立带有图形界面的聊天工具(一个像QQ群且带私聊功能的聊天室)。

最开始我看到这个题目时比较懵,毕竟先前只是学习了基本的通信协议,结果实验里突然就要实战Socket编程,而且还是个群聊聊天室。故零基础的我也是在网上先看了很久的资料才开始有思路。

首先初学者写Socket程序的话,一般都是Python语言写,方便快捷。我这里建议先去B站看下Python语言的Socket教学视频进行快速入门下,至少理解端口绑定,等待,接收,发送等socket操作的概念。

了解完概念后,我们就可以着手构建我们的网络聊天室。(代码放在文章末尾)


提示:以下是本篇文章正文内容,仅供参考

1、网络聊天室的基本架构是什么?

1.1 客户端和服务器的架构

作为一个多人聊天室,我们要支持多个用户同步信息。
那么怎么实现多用户信息的同步呢?
比如我们现在有3个用户A,B,C,当A发送一句“Hello”的时候,怎么让B和C都能收到“Hello”呢?

那么最容易想到的方法就是,我们让ABC两两之间互相建立socket连接,当A发送信息的时候,我们让A对两个socket连接都发送一次“Hello”信息,这样用户B和C就都能接收到“Hello”了。

但其实这样有一个缺点,随着用户的增加,我们每个用户所要建立的socket连接就越来越多。比如群里有100个用户,那一个用户要发送消息的时候,就要遍历99个连接来发送信息,这样对用户的机器开销是很大的。

故我们很容易想到一个改进的方法:利用服务器转发数据。

python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
我们让ABC三个用户分别和服务器建立socket连接。然后A只要发送一次“Hello”给服务器的socket连接,然后服务器会自动帮我们把信息同时转发给B和C,这样大大提升了A的用户体验,也方便我们进行消息的同步。

当然了,在这次实验中,我把我的电脑既当作服务器,又当作客户端,其实开销还是一样大的。但是以这种思路来写这个工具会让难度减小很多,代码冗余量也会减少很多。

1.2 通信协议的选择以及多线程通信

上述提到了我们是以客户端-服务器的方式进行通信,那么在通信过程中,我们还会遇到什么样的问题呢?

1.2.1 多线程通信

假设我们服务器用的是TCP协议(后面会解释TCP和UDP区别),且服务器只负责接收用户A的消息,并将A的消息转发给B和C的话,那服务器只要和A建立连接,然后用while()循环一直监听该连接否有接收到来自A的新数据,一旦接收到A发出的数据我们就迅速将其转发给BC即可。

但实际上我们的服务器要同时监听多个用户的信息,比如在本文例子中,我们要同时监听ABC三个用户是否有数据发送。
较容易想到的方法是,在while()循环里把ABC的socket连接同时监听,同时处理。

python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
但这会带来一个问题,倘若ABC在同一时间发送数据了,那服务器还是只能先处理A再处理B再处理C,并不能达到群聊中我们想要的那种真正多人同步的效果。

所以这里我们要用到一个并行技术叫线程thread:
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
上面这个图是我找的一个网图,虽然看起来这个函数很陌生,但其实很好理解,大家看一下网上文章教程就能很容易使用起来了。简单来说,就是每当主程序新接收到一个socket请求,我们就创建一个线程(Thread)来处理该请求,创建线程的方法为threading.Thread(target=该线程要进行的函数的函数名,args=(该函数的输入参数))

其主要功能就是能让我们的服务器能同时进行多个循环,服务器每收到一个socket请求我们就建立一个线程去处理。该线程可以独立自主地进行自己的事务,即独立自主的循环监听socket信息,这样就不用像之前的服务器一样只用一个循环依次监听ABC用户。

以下图片是不用线程技术,以及用了线程技术(且用的是TCP协议)的流程图对比:
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

故我们在聊天室代码中也是加入了线程这个技术(几行代码就行,不难)。

除了服务器要为每个socket建立一个线程,客户端本身也要启动两个线程,一个线程用于让自己发送数据给服务器,一个线程用于监听服务器是否有发送数据给自己,这样就能做到边发信息边收信息了。

Q: “不用线程是否也可以呢?”
A: 可以是可以,相当于服务器要维护一个队列,让消息依次读入依次发送,然后再频繁创建socket销毁socket,其实代码量比写几行线程要多,且不能算并行。线程在研究或工作领域都经常用到,可以趁现在学一学

1.2.2 通信协议选择

解决完多用户通信的问题后,我们要选择我们的通信协议。
最常见的是TCP和UDP协议。

TCP协议
优点:信息传输可靠,不会出现A用户发出的数据丢失,导致B和C收不到的情况。
缺点:TCP要求服务器要和用户建立连接且不允许临时断开,故我们为了保持用户能一直处于连接状态,就要让服务器为每一个socket连接开一个线程让它不停的跑(用while不停的监听信息)。所以当用户连接越多,服务器要添加的线程也就越多。比如上述图中,监听ABC用户需要用到3个线程,比较占用资源(当然你也可以检测哪个socket长时间不工作就销毁该线程)。

UDP协议
优点:传输速度快,不用管信息是否送达,即不用维持连接,所以用户发完数据就不用管了。这种情况下服务器只要建立两个线程,一个线程用于接收用户发送的数据,另一个线程用于发送数据,而不用额外的线程来维护与用户的连接。
缺点:消息无法确保可靠送达。

按理来说,作为网络聊天室,我们对信息的传输速度要求没那么高,对信息可靠性传输反而高一点,所以网上大部分教程所用的协议也都是TCP协议。
比如本篇文章参考的教程: 用Python怎么实现一个网络聊天室功能

但由于TCP实现网络聊天室的教程较多,我自己就想尝试利用UDP协议进行实现,试试新的方法。如果你想用TCP来做聊天室的话,下面的教程你同样可以参考。UDP和TCP协议的不同主要就在服务器所要开设线程数目上,其余思路基本一致。

TCP服务器所需开设的线程数=所维持的socket连接的数目(每来一个socket请求就创建一个线程,每个线程负责该socket的信息接收与发送,当某个线程长时间没有接收到数据则自己销毁)
UDP服务器所需开设的线程数=接收信息线程 + 转发信息线程(因为UDP不用维持socket连接,所以只要两个线程就够了;UDP以信息传输不可靠为代价,换来了服务器线程开销的减少)
TCP/UDP客户端所需开设的线程数=接收信息线程 + 发送信息线程(客户端只要一个线程发送数据,一个线程接收数据即可。接收到数据后再把数据插入到聊天窗口里,从而制造出别人发信息到自己窗口的效果)

下图即为UDP服务器的流程图:
客户端往服务器地址发送数据后就不用管了,服务器的接收信息线程会自己把数据存入到缓冲区中。
然后服务器转发信息线程检测到缓冲区有数据了,就会把该数据群发到所有客户端IP地址(客户端在登录到服务器的时候会携带自己的客户端IP信息,服务器会将这个信息储存到列表里,后面转发时再遍历该列表依次发送信息,从而达到群发的效果)。
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

1.3 前后端功能设计思路

选择好通信协议后,我们就要进行下一步的具体设计。

首先,我们需要带有图形界面的聊天室,那我们就需要学习如何制作前端基本界面,如何将前端界面显示内容与后端数据(如聊天信息,聊天室里的群友名)绑定起来。

这里我用的图形界面工具是tkinter,其很容易上手,不需要多少时间就能做出一个简易界面。

1.3.1 前端

前端界面包括三个页面:

  1. 登录窗口
  2. 聊天窗口
  3. 私聊窗口

1.登录窗口要能进行用户登录,比如下图所示:
目的IP地址即为服务器的IP地址,因为我们这里把自己电脑既当作服务器又当作客户端,故目的IP地址就是127.0.0.1(如果你要实现不同电脑间通信的话,则需要连接到同一局域网,并在目的IP地址一栏填入另一台作为服务器的电脑IP地址)。

python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
点击登录按钮后,客户端会将登录窗口里输入的目的IP地址目的端口号,找到对应的服务器进行连接。在这个连接过程中,客户端还会隐式地将自身的用户名信息用户端IP信息用户所绑定端口号等登录信息发给后端,这样后端就能将用户端IP信息用户名信息存储到服务器列表里,并供与后续使用。
所以前端的 “登录” 按键要绑定一个触发函数,当我们点击“登陆”按钮后,该函数会触发,其能将我们白色输入框里的数据打包并发送后端。

2.聊天窗口要能显示服务器转发来的数据:

原理是服务器先接收到其他用户发送的信息,并将信息群发到各个客户端,客户端再根据服务器转发的信息,将信息插入在自己的聊天室界面中,从而达到别人的用户“发送”信息到自己聊天框的效果。
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

3.私聊窗口用于跟服务器告诉要私聊的对象和内容:
由于服务器本身已经用列表存储了用户名和用户名对应IP地址的映射关系,故我们只要告诉服务器我们要私聊的用户b,服务器就会根据该名字找到用户b对应IP,然后就将这条信息私发给b,而不是群发给所有人。

python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
点击确定按钮,就会触发私聊函数,该函数用于将私聊用户名称和私聊内容打包发送给服务器。

总的简易流程图如下所示:
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

1.3.2 后端

客户端

python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

以下为登录窗口的实现:

labelIP = tkinter.Label(root0, text='目的IP地址', bg="#F5DE83")   #LightBlue
labelIP.place(x=20, y=5, width=100, height=40)
entryIP = tkinter.Entry(root0, width=60, textvariable=IP)
entryIP.place(x=120, y=10, width=100, height=30)

labelPORT = tkinter.Label(root0, text='目的端口号', bg="#F5DE83")
labelPORT.place(x=20, y=40, width=100, height=40)
entryPORT = tkinter.Entry(root0, width=60, textvariable=PORT)
entryPORT.place(x=120, y=45, width=100, height=30)

labelUSER = tkinter.Label(root0, text='用户名', bg="#F5DE83")
labelUSER.place(x=20, y=75, width=100, height=40)
entryUSER = tkinter.Entry(root0, width=60, textvariable=USER)
entryUSER.place(x=120, y=80, width=100, height=30)

def Login():
    global IP, PORT, user
    IP = entryIP.get()	#得到用户输入的服务器IP
    PORT = entryPORT.get()	#得到用户输入的端口号
    user = entryUSER.get()	#得到用户输入的用户名
#UDP连接部分
ip_port = (IP, int(PORT))		#得到IP和PORT后就可以进行socket连接
s = socket(AF_INET, SOCK_DGRAM)
if user:
    s.sendto(user.encode(), ip_port)  # 发送用户名

以下为聊天窗口的实现:
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
进入聊天室后,输入用户想发送的数据,点击发送按钮。发送按钮会触发所绑定的send()函数:

def send():
    message = entryIuput.get() + '~' + user + '~' + chat
    s.sendto(message.encode(), ip_port)		#信息要先编码再发送
    print("already_send message:",message)
    INPUT.set('')
    return 'break'  #按回车后只发送不换行

receive()函数会循环监听是否接收到服务器转发来的数据,接收到消息时,会把信息显示到聊天窗口中(比如下方的显示群里的所有用户名):

def receive():
    global uses
    while True:
        data = s.recv(1024)
        data = data.decode()			#接收服务器信息
        print("rec_data:",data)
        try:
            uses = json.loads(data)
            listbox1.delete(0, tkinter.END)
            listbox1.insert(tkinter.END, "       当前在线用户:")
            
            #用Insert函数将接收到的用户信息插入到聊天界面右侧一栏中
            for x in range(len(uses)):
                listbox1.insert(tkinter.END, uses[x])
            users.append('------Group chat-------')

以下为私聊窗口的功能实现:
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

def Priva_window():
    chat_pri = entryPriva_target.get()	#获取私聊对象名称
    message = entryPriva_talk.get()		#获取私聊内容
    if not chat_pri:
        tkinter.messagebox.showwarning('warning', message='私聊目标名称为空!')  # 目的IP地址为空则提示
    else:
        root3.destroy()					#关闭私聊窗口
        print("chat_pri", chat_pri)
        message = message + '~' + user + '~' + chat_pri
        #将用户名和私聊内容打包成 信息~自己用户名~私聊对象名,以方便服务器根据 ~ 符号进行信息切割
        s.sendto(message.encode(), ip_port)	#发送私聊信息
        INPUT.set('')
服务器

服务器要创建两个线程,一个用于接收数据,一个用于发送数据:

    def run(self):
        self.s.bind((IP, PORT))         #绑定监听端口
        q = threading.Thread(target=self.sendData)  
        q.start()	#开启发送数据线程
        t = threading.Thread(target=self.receive)  
        t.start()	# 开启接收信息进程
服务器接收用户信息线程的实现:

主要功能是接收用户发送的数据。比如接收到“你好~用户b~0”,并分割成
“你好”,“用户b”,“0”三个信息
第一个字符串是用户发送的聊天内容
第二个字符串是发送该聊天内容的用户名
第三个字符串的0代表是群发的意思,非私聊

    def receive(self):  # 接收消息,b'用户数据,(用户地址)
        while True:
            print('a')
            Info, addr = self.s.recvfrom(1024)  # 收到的信息
            print('b')
            Info_str = str(Info, 'utf-8')
            userIP = addr[0]
            userPort = addr[1]
            print(f'Info_str:{Info_str},addr:{addr}')
            if '~0' in Info_str:# 群聊
                data = Info_str.split('~')	#根据 ~ 符号进行信息分割
                print("data_after_slpit:", data)  # data_after_slpit: ['cccc', 'a', '0']
                message = data[0]   # data
                userName = data[1]  # name
                chatwith = data[2]  # 0
                message = userName + '~' + message + '~' + chatwith  # 界面输出用户格式
                print("message:",message)
                self.Load(message, addr)
服务器发送聊天内容的线程实现:
    def sendData(self):  # 发送数据
        print('send')
        while True:
            if not messages.empty(): #如果接收到用户的信息不为空
                message = messages.get()#获取该数据
                print("messages.get()",message)
                if isinstance(message[1], str):#判断类型是否为字符串
                    print("send str")
                    for i in range(len(users)):#遍历所有用户,进行消息群发
                        data = ' ' + message[1]
                        print("send_data:",data.encode()) 
                        self.s.sendto(data.encode(),(users[i][1],users[i][2])) #聊天内容发送过去

二、总体代码

项目分为两部分代码,一部分是UDPServer.py,一部分是UDPClient.py

2.1 如何在同一台电脑运行服务器和客户端:

如果你只有一台电脑来运行程序,那么使用步骤如下:

先启动UDPServer.py,再启动UDPClient.py。
如果你要用多个用户登录到聊天室,只要将UDPClient.py复制多几份,再分别运行即可。(Client连接的IP地址填127.0.0.1即可,即自己本地IP)
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器

2.2 如何在多台电脑运行服务器和客户端:

如果你有多台电脑来运行程序,那么使用步骤如下:

假设有3台电脑A,B,C
其中A作为服务器,B和C作为客户端
那我们首先要将ABC连接到同一个局域网或者热点中。

注:如果你在自己用自己电脑运行2.1节成功,但在多台电脑运行失败,那么原因一般是你服务器IP地址没填对,或者没将各个电脑的防火墙关闭。
我记得我之前演示的时候是将三台电脑防火墙都关了,并将网络设置成公开还是共享才能让电脑B和C连接上服务器A。

然后服务器A在自己的cmd窗口中输入ipconfig获取自己的IP地址(获取到的地址可能有很多个,用无线WIFI热点的话一般是WLAN那个地址),然后A在UDPServer.py中将IP更改成自己查到的IP。
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
python 聊天室,NUAA-物联网工程专业课程笔记,udp,网络协议,tcp,python,服务器
端口自定义即可,但建议不要用8080等其他软件可能占用的端口。

接下来在电脑B和C运行UDPClient.py并将连接的服务器目的IP地址填192.168.124.18(你查到的A地址)即可

2.3 Client.py代码

客户端代码如下:

from socket import *
import time
import tkinter
import tkinter.messagebox
import threading
import json
import tkinter.filedialog
from tkinter.scrolledtext import ScrolledText

IP = '127.0.0.1'
SERVER_PORT = 50000
user = ''
listbox1 = ''  # 用于显示在线用户的列表框
show = 1  # 用于判断是开还是关闭列表框
users = []  # 在线用户列表
chat = '0'  # 聊天对象
chat_pri = ''



# 登陆窗口的界面实现
root0 = tkinter.Tk()
root0.geometry("300x150")
root0.title('用户登陆窗口')
root0.resizable(0, 0)
one = tkinter.Label(root0, width=300, height=150, bg="#F5DE83")
one.pack()
IP = tkinter.StringVar()
IP.set('')
PORT = tkinter.StringVar()
PORT.set('')
USER = tkinter.StringVar()
USER.set('')
##将填空处内容和实际参数绑定起来 比如将输入的IP地址绑定到entryIP,以供后续使用
labelIP = tkinter.Label(root0, text='目的IP地址', bg="#F5DE83")   #bg代表颜色
labelIP.place(x=20, y=5, width=100, height=40)
entryIP = tkinter.Entry(root0, width=60, textvariable=IP)
entryIP.place(x=120, y=10, width=100, height=30)

labelPORT = tkinter.Label(root0, text='目的端口号', bg="#F5DE83")
labelPORT.place(x=20, y=40, width=100, height=40)
entryPORT = tkinter.Entry(root0, width=60, textvariable=PORT)
entryPORT.place(x=120, y=45, width=100, height=30)

labelUSER = tkinter.Label(root0, text='用户名', bg="#F5DE83")
labelUSER.place(x=20, y=75, width=100, height=40)
entryUSER = tkinter.Entry(root0, width=60, textvariable=USER)
entryUSER.place(x=120, y=80, width=100, height=30)


#界面完成后,以下就是编写实际的登录函数
def Login():
    global IP, PORT, user
    IP = entryIP.get()	#获取前面绑定的IP地址,PORT,user信息
    PORT = entryPORT.get()
    user = entryUSER.get()
    if not IP:
        tkinter.messagebox.showwarning('warning', message='目的IP地址为空!')  # 目的IP地址为空则提示
    elif not PORT:
        tkinter.messagebox.showwarning('warning', message='目的端口号为空!')  # 目的端口号为空则提示
    elif not user:
        tkinter.messagebox.showwarning('warning', message='用户名为空!')     # 客户端用户名为空则提示
    else:
        root0.destroy()	#提交后,登录窗口要自己销毁,以便进入登录成功后的界面

#登录按钮的实现
loginButton = tkinter.Button(root0, text="登录", command=Login, bg="#FF8C00")
loginButton.place(x=135, y=120, width=40, height=25)
root0.bind('<Return>', Login)	#将按钮与Login()函数绑定

root0.mainloop()


# 聊天窗口界面的实现
root1 = tkinter.Tk()
root1.geometry("640x480")
root1.title('聊天工具')
root1.resizable(0, 0)

## 聊天窗口中的消息界面的实现
listbox = ScrolledText(root1)
listbox.place(x=5, y=0, width=485, height=320)
listbox.tag_config('tag1', foreground='blue', backgroun="white")
listbox.insert(tkinter.END, '欢迎用户 '+user+' 加入聊天室!', 'tag1')
listbox.insert(tkinter.END, '\n')
# 聊天窗口中的在线用户列表界面的实现
listbox1 = tkinter.Listbox(root1)
listbox1.place(x=490, y=0, width=140, height=320)
# 聊天窗口中的聊天内容输入框界面的实现
INPUT = tkinter.StringVar()
INPUT.set('')
entryIuput = tkinter.Entry(root1, width=120, textvariable=INPUT)
entryIuput.place(x=5, y=330, width=485, height=140)



#UDP连接部分
ip_port = (IP, int(PORT))
s = socket(AF_INET, SOCK_DGRAM)
if user:
    s.sendto(user.encode(), ip_port)  # 发送用户名
else:           #e这部分else可删除,因为已经确保用户名不为空了
    s.sendto('用户名不存在', ip_port)
    user = IP + ':' + PORT

#发送聊天内容的函数实现,与下面的“发送按钮”绑定起来
def send():
    message = entryIuput.get() + '~' + user + '~' + chat
    s.sendto(message.encode(), ip_port)
    print("already_send message:",message)
    INPUT.set('')
    return 'break'  #按回车后只发送不换行

# 私聊窗口的函数实现
def Priva_window():
    chat_pri = entryPriva_target.get()
    message = entryPriva_talk.get()
    if not chat_pri:
        tkinter.messagebox.showwarning('warning', message='私聊目标名称为空!')  # 目的IP地址为空则提示
    else:
        root3.destroy()
        print("chat_pri", chat_pri)
        #print("message", message)message
        message = message + '~' + user + '~' + chat_pri
        #message = entryIuput.get() + '~' + user + '~' + chat_pri
        s.sendto(message.encode(), ip_port)
        INPUT.set('')

# 私聊窗口的界面实现。为什么私聊窗口界面要在函数里实现?因为他是要点击后自己跳出来,而不是一开始就存在的。
def Priva_Chat():
    global chat_pri,root3,window,Priva_target,labelPriva_target,entryPriva_target,Priva_talk,labelPriva_talk,entryPriva_talk
    root3 = tkinter.Toplevel(root1)
    root3.geometry("300x150")
    root3.title('私聊对象')
    root3.resizable(0, 0)
    window = tkinter.Label(root3, width=300, height=150, bg="LightBlue")
    window.pack()
    Priva_target = tkinter.StringVar()
    Priva_target.set('')
    labelPriva_target = tkinter.Label(root3, text='私聊用户名称', bg="LightBlue")
    labelPriva_target.place(x=20, y=5, width=100, height=40)
    entryPriva_target = tkinter.Entry(root3, width=60, textvariable=Priva_target)
    entryPriva_target.place(x=120, y=10, width=100, height=30)

    Priva_talk = tkinter.StringVar()
    Priva_talk.set('')
    labelPriva_talk = tkinter.Label(root3, text='私聊内容', bg="LightBlue")
    labelPriva_talk.place(x=20, y=40, width=100, height=40)
    entryPriva_talk = tkinter.Entry(root3, width=60, textvariable=Priva_talk)
    entryPriva_talk.place(x=120, y=45, width=100, height=30)

    Priva_targetButton = tkinter.Button(root3, text="确定", command=Priva_window, bg="Yellow")
    Priva_targetButton.place(x=135, y=120, width=40, height=25)

# “发送按钮”的界面实现,与send()函数绑定
sendButton = tkinter.Button(root1, text="发送", anchor='n', command=send, font=('Helvetica', 18), bg='white')
sendButton.place(x=535, y=350, width=60, height=40)
# “私聊发送按钮”的界面实现,与send()函数绑定,send通过text内容判断是私聊还是群发
PrivaButton = tkinter.Button(root1, text="私聊", anchor='n', command=Priva_Chat, font=('Helvetica', 18), bg='white')
PrivaButton.place(x=535, y=400, width=60, height=40)
root1.bind('<Return>', send)

# 接收信息的函数实现
def receive():
    global uses
    while True:
        data = s.recv(1024)
        data = data.decode()
        print("rec_data:",data)
        try:
            uses = json.loads(data)
            listbox1.delete(0, tkinter.END)
            listbox1.insert(tkinter.END, "       当前在线用户:")	#往用户列表插入信息
            #listbox1.insert(tkinter.END, "------Group chat-------")
            for x in range(len(uses)):
                listbox1.insert(tkinter.END, uses[x])
            users.append('------Group chat-------')
        except:
            data = data.split('~')
            print("data_after_slpit:",data) #data_after_slpit: ['cccc', 'a', '0/1']
            userName = data[0]   #data 
            userName = userName[1:]	#获取用户名
            message = data[1]  #信息
            chatwith = data[2]  #destination 判断是群聊还是私聊
            message = '  ' + message + '\n'
            recv_time = " "+userName+"   "+time.strftime ("%Y-%m-%d %H:%M:%S", time.localtime()) + ': ' + '\n'	#信息发送时间
            listbox.tag_config('tag3', foreground='green')
            listbox.tag_config('tag4', foreground='blue')
            if chatwith == '0':  # 群聊
                listbox.insert(tkinter.END, recv_time, 'tag3')
                listbox.insert(tkinter.END, message)
            elif chatwith != '0':  # 私聊别人或是自己发出去的私聊
                if userName == user:                     #如果是自己发出去的,用私聊字体显示
                    listbox.insert(tkinter.END, recv_time, 'tag3')
                    listbox.insert(tkinter.END, message, 'tag4')
                if chatwith == user:                                    #如果是发给自己的,用绿色字体显示
                    listbox.insert(tkinter.END, recv_time, 'tag3')
                    listbox.insert(tkinter.END, message, 'tag4')

            listbox.see(tkinter.END)


r = threading.Thread(target=receive)
r.start()  # 开始线程接收信息

root1.mainloop()
s.close()

2.4 Server.py代码

服务器代码如下:

import tkinter as tk
from socket import *
import threading
import queue
import json  # json.dumps(some)打包  json.loads(some)解包
import os
import os.path
import sys

IP = '127.0.0.1'
#IP = '192.168.1.103'(如果是多台主机,将IP改为服务器主机的地址即可)
PORT = 8087  # 端口
messages = queue.Queue()    #存放总体数据
users = []  # 0:userName 2:str(Client_IP)  3:int(Client_PORT)定义一个二维数组
lock = threading.Lock()             #线程锁,防止多个线程占用同个资源时导致资源不同步的问题
BUFLEN=512

def Current_users():  # 统计当前在线人员,用于显示名单并发送消息
    current_suers = []
    for i in range(len(users)):
        current_suers.append(users[i][0])      #存放用户相关名字
    return  current_suers

class ChatServer(threading.Thread):
    global users, que, lock

    def __init__(self):  # 构造函数
        threading.Thread.__init__(self)
        self.s = socket(AF_INET, SOCK_DGRAM)      #用UDP连接

    # 接受来自客户端的用户名,如果用户名为空,使用用户的IP与端口作为用户名。如果用户名出现重复,则在出现的用户名依此加上后缀“2”、“3”、“4”……
    def receive(self):  # 接收消息,b'用户数据,(用户地址)
        while True:
            print('a')
            Info, addr = self.s.recvfrom(1024)  # 收到的信息
            print('b')
            Info_str = str(Info, 'utf-8')
            userIP = addr[0]
            userPort = addr[1]
            print(f'Info_str:{Info_str},addr:{addr}')
            if '~0' in Info_str:# 群聊
                data = Info_str.split('~')
                print("data_after_slpit:", data)  # data_after_slpit: ['cccc', 'a', '0']
                message = data[0]   # data
                userName = data[1]  # name
                chatwith = data[2]  # 0
                message = userName + '~' + message + '~' + chatwith  # 界面输出用户格式
                print("message:",message)
                self.Load(message, addr)
            elif '~' in Info_str and '0' not in Info_str:# 私聊
                data = Info_str.split('~')
                print("data_after_slpit:", data)  # data_after_slpit: ['cccc', 'a', 'destination_name']
                message = data[0]  # data
                userName = data[1]  # name
                chatwith = data[2]  # destination_name
                message = userName + '~' + message + '~' + chatwith  # 界面输出用户格式
                self.Load(message, addr)
            else:# 新用户
                tag = 1
                temp = Info_str
                for i in range(len(users)):  # 检验重名,则在重名用户后加数字
                    if users[i][0] == Info_str:
                        tag = tag + 1
                        Info_str = temp + str(tag)
                users.append((Info_str, userIP, userPort))
                print("users:", users)  # 用户名和信息[('a', '127.0.0.1', 65350)]
                Info_str = Current_users()  # 当前用户列表
                print("USERS:", Info_str)  # ['a']
                self.Load(Info_str, addr)
        # 在获取用户名后便会不断地接受用户端发来的消息(即聊天内容),结束后关闭连接。
    # 将地址与数据(需发送给客户端)存入messages队列。
    def Load(self, data, addr):
        lock.acquire()
        try:
            messages.put((addr, data))
            print(f"Load,addr:{addr},data:{data}")
        finally:
            lock.release()

    # 服务端在接受到数据后,会对其进行一些处理然后发送给客户端,如下图,对于聊天内容,服务端直接发送给客户端,而对于用户列表,便由json.dumps处理后发送。
    def sendData(self):  # 发送数据
        print('send')
        while True:
            if not messages.empty():                        #如果信息不为空
                message = messages.get()
                print("messages.get()",message)
                if isinstance(message[1], str):             #判断类型是否为字符串
                    print("send str")
                    for i in range(len(users)):
                        data = ' ' + message[1]
                        print("send_data:",data.encode())         #send_data:b' a:cccc~a~------Group chat-------'
                        self.s.sendto(data.encode(),(users[i][1],users[i][2])) #聊天内容发送过去

                if isinstance(message[1], list):        #是否为列表
                    print("message[1]",message[1])      #message[1]为用户名 message[0]为地址元组
                    data = json.dumps(message[1])
                    for i in range(len(users)):
                        try:
                            self.s.sendto(data.encode(), (users[i][1], users[i][2]))
                            print("send_already")
                        except:
                            pass
        print('out_send_loop')
    def run(self):
        self.s.bind((IP, PORT))         #绑定端口
        q = threading.Thread(target=self.sendData)  #开启发送数据线程
        q.start()
        t = threading.Thread(target=self.receive)  # 开启接收信息进程
        t.start()

#入口
if __name__ == '__main__':
    print('start')
    cserver = ChatServer()
cserver.start()
#netstat -an|find /i "50000"

总结

该教程也是参考网上的TCP网络聊天室教程写的,主要是解释UDP的实现思路为主,如有错误,欢迎指正。文章来源地址https://www.toymoban.com/news/detail-768825.html

到了这里,关于[Socket]Python用UDP协议建立带有私聊功能的网络聊天室-建立聊天工具的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 python socket 实现UDP/TCP网络通信

    目录 目录 1.socket简介 2.创建socket 2.1创建UDPSocket 2.2创建TCPSocket 3.使用UDPSocket发送数据并接收 4.使用UDPSocket发送广播 5.UDPSocket聊天器 (多线程实现消息的收发功能) 6.使用TCPSocket建立客户端 7.使用TCPSocket建立服务端        socket(简称:套接字),是支持TCP和UDP(网络传输方式

    2023年04月10日
    浏览(62)
  • 基于Springboot整合Socket仿微信实现群聊、私聊功能。实现客户端client,服务端server心跳维护、超时机制【一文通】

    博主介绍: ✌java资深开发工程师、Java领域优质创作者,博客之星、专注于Java技术领域和学生毕业项目实战,面试讲解跟进,高校老师/讲师/同行交流合作✌ 胡广愿景: \\\"比特星球\\\",致力于帮助底层人员找到工作, 让每个底层人员都能找到属于自己的星球。 拓展学习领域,获

    2024年02月19日
    浏览(51)
  • 【socket】从计算机网络基础到socket编程——Windows && Linux C语言 + Python实现(TCP+UDP)

    简单讲一下基础知识,便于后面代码的理解,建议大概浏览一下这一小节内容。这里讲的只是冰山一角,建议大家学习计算机网络相关知识,推荐几本书: 《计算机网络》(谢希仁) 《计算机网络 自顶向下方法》 《计算机网络技术》 《计算机网络基础及应用》 《Linux C从入

    2024年02月08日
    浏览(56)
  • 多人聊天室(带私聊功能)Linux网络编程基础

    在和同学一起努力下终于完成了期末作业哈哈哈哈 文章目录 目录 前言 一、需求分析 二、功能设计 1.服务器端: 2.客户端: 三、流程图: 编程流程图: 服务器流程图: 客户端流程图: 四、运行效果: 项目源码: 服务器源码 客户端源码: 总结: Linux网络编程是我们这学

    2024年02月09日
    浏览(54)
  • Python网络编程之UDP协议

    Hello,大家好。本期来和大家一起学习一下网络编程UDP的相关知识。 ip ip地址:用来在网络中标记一台电脑,在本地局域网上是唯一的。 查看电脑ip地址:ipconfig(win)/ifconfig(linux/mac) 端口 端口:设备与外界通讯交流的出口 一共有65536个(0 - 65535) 端口 知名端口是众所周知的

    2024年02月03日
    浏览(34)
  • Python中两种网络编程方式:Socket和HTTP协议

    本文分享自华为云社区《Python网络编程实践从Socket到HTTP协议的探索与实现》,作者:柠檬味拥抱。 在当今互联网时代,网络编程是程序员不可或缺的一项技能。Python作为一种高级编程语言,提供了丰富的网络编程库,使得开发者能够轻松地实现各种网络应用。本文将介绍P

    2024年04月16日
    浏览(41)
  • Python中的网络编程Socket与HTTP协议的实践【第158篇—网络编程】

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在当今互联网时代,网络编程是程序员不可或缺的一项技能。Python作为一种高级编程语言,提供了丰富的网络编程库,使得开发者能够轻松地

    2024年04月09日
    浏览(85)
  • Socket通信讲解及C/S结构实现UDP协议通信

    1.1 什么是套接字 所谓套接字( Socket ),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制,是支持 TCP/IP 协议的路通信的基本操作单元 1.2 套接字主要类型 TCP 流套接字

    2024年02月08日
    浏览(41)
  • 基于Socket简单的UDP网络程序

    ⭐ 小白苦学IT的博客主页 ⭐ 初学者必看:Linux操作系统入门 ⭐ 代码仓库:Linux代码仓库 ❤关注我一起讨论和学习Linux系统 网络编程前言 网络编程是连接数字世界的桥梁,它让计算机之间能够交流信息,为我们的生活和工作带来便利。从简单的网页浏览到复杂的分布式系统,

    2024年04月12日
    浏览(36)
  • 网络通信(Socket/TCP/UDP)

    Socket(又叫套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接协议,客户端的IP地址,客户端的端口,服务器的IP地址,服务器的端口。 一个Socket是一对IP地址和端口。 Socket可以看

    2024年01月22日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包