[Socket] Python uses UDP protocol to create an online chat room with private chat function-Build a chat tool


Preface

During the internship interview a few days ago, the interviewer asked me a question about Socket programming, that is, "What is the specific process of creating and releasing a Socket?" I couldn't answer it at the time. It seemed to be a question about sending and receiving buffers. Since my knowledge in Socket programming is relatively weak, I wrote this article as a review of Socket programming knowledge.

First of all, this experiment was completed when I took the "Ubiquitous Network Technology" class in the first semester of my junior year. The experimental questions are as follows:

Experiment 3: Establishing a chat tool
  1. Experiment purpose:
      Require students to master the technology of streaming sockets in Socket programming
  2. Experiment content:
      i. Require students to master the technology of programming using Socket
      ii. Must master multi-threading technology to ensure that both parties can simultaneously Send
      iii. Create a chat tool
      iv. Can chat with multiple people at the same time
      v. Must use a graphical interface to display the quotations of both parties

We can see the experimental requirement, which is to use Socket programming technology to build a chat tool with a graphical interface (a chat room like QQ group with private chat function).

I was confused when I saw this topic at first. After all, I had only learned basic communication protocols before, but suddenly I had to practice Socket programming in the experiment, and it was also a group chat room. Therefore, even though I had zero basic knowledge, I read information online for a long time before I started to have ideas.

First of all, if beginners write Socket programs, they usually write them in Python language, which is convenient and fast. I recommend that you first go to Station B to watch the Python language Socket teaching video to get started quickly, and at least understand the concepts of socket operations such as port binding, waiting, receiving, and sending.

After understanding the concepts, we can start building our online chat room. (The code is placed at the end of the article)


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

1. What is the basic structure of an online chat room?

1.1 Client and server architecture

As a multi-person chat room, we need to support multiple users to synchronize information.
So how to synchronize multi-user information?
For example, we now have three users A, B, and C. When A sends "Hello", how can we make both B and C receive "Hello"?

Then the easiest way to think of is that we let ABC establish socket connections with each other. When A sends information, we let A send a "Hello" message to both socket connections, so that users B and C both "Hello" can be received.

But in fact, this has a disadvantage. As the number of users increases, each of our users will need to establish more and more socket connections. For example, if there are 100 users in the group, when one user wants to send a message, he must traverse 99 connections to send the message, which is very expensive for the user's machine.

Therefore, we can easily think of an improved method: use the server to forward data.

Insert image description here
We let three users ABC establish socket connections with the server respectively. Then A only needs to send "Hello" once to the server's socket connection, and then the server will automatically help us forward the information to B and C at the same time. This greatly improves A's user experience and also facilitates our message synchronization.

Of course, in this experiment, I used my computer as both a server and a client. In fact, the overhead is still the same. But writing this tool with this idea will make it much less difficult and the amount of code redundancy will also be reduced a lot.

1.2 Selection of communication protocol and multi-thread communication

As mentioned above, we areclient-serverway to communicate, what kind of problems will we encounter during the communication process?

1.2.1 Multi-threaded communication

Assume that our server uses the TCP protocol (the difference between TCP and UDP will be explained later), and the server is only responsible for receiving messages from user A and forwarding messages from user A to B and C, then the server only needs to establish a connection with A, and then use The while() loop keeps monitoring whether the connection has received new data from A. Once the data from A is received, we can quickly forward it to BC.

But in fact, our server needs to monitor the information of multiple users at the same time. For example, in the example of this article, we need to monitor whether three users ABC have data sent at the same time.
The easier way to think of is to monitor and process ABC's socket connections at the same time in the while() loop.

Insert image description here
But this will bring about a problem. If ABC sends data at the same time, the server can still only process A first, then B, and then C. It cannot achieve the real multi-person synchronization effect we want in group chat. .

So here we are going to use a parallel technology called thread:
Insert image description here
The picture above is a network diagram I found. Although this function seems unfamiliar, it is actually easy to understand. You can easily use it by reading online article tutorials. . To put it simply, whenever the main program receives a new socket request, we create a thread (Thread) to process the request. The method of creating a thread is:threading.Thread(target=the function name of the function to be performed by the thread, args=(input parameters of the function))

Its main function is to enable our server to perform multiple loops at the same time. Every time the server receives a socket request, we create a thread to process it. This thread can independently carry out its own affairs, that is, it can independently and autonomously monitor socket information in a loop, so that it does not have to use only one loop to monitor ABC users in sequence like the previous server.

The following pictures areNo threading technology,as well asUsed thread technology(and using the TCP protocol) flow chart comparison:
Insert image description here

Therefore, we also added threading technology to the chat room code (just a few lines of code, not difficult).

In addition to the server creating a thread for each socket, the client itself also needs to start two threads, one thread is used to send data to the server, and one thread is used to monitor whether the server has sent data to itself. This can be done Sending and receiving messages at the same time.

Q: "Is it possible not to use threads?"
A: Yes, it is possible. It is equivalent to the server maintaining a queue, reading messages in and sending them out, and then frequently creating and destroying sockets. In fact, the amount of code is more than writing a few lines of threads. There are many, and they cannot be considered parallel. Threads are often used in research or work, so you can learn them now

1.2.2 Communication protocol selection

After solving the problem of multi-user communication, we need to choose our communication protocol.
The most common are TCP and UDP protocols.

Advantages of TCP protocol
: Information transmission is reliable, and the data sent by user A will not be lost, causing B and C to not receive it.
Disadvantages: TCP requires the server to establish a connection with the user and does not allow temporary disconnection. Therefore, in order to keep the user connected, we must let the server open a thread for each socket connection and let it run continuously (use while instead of Stop monitoring information). So the more users connect, the more threads the server needs to add. For example, in the above figure, three threads are needed to monitor ABC users, which takes up more resources (of course, you can also detect which socket is not working for a long time and destroy the thread).

UDP protocol
advantages: fast transmission speed, no need to worry about whether the information is delivered, that is, no need to maintain the connection, so the user does not need to worry about it after sending the data. In this case, the server only needs to create two threads, one thread is used to receive data sent by the user, and the other thread is used to send data, without additional threads to maintain the connection with the user.
Disadvantages: Reliable delivery of messages is not guaranteed.

Logically speaking, as an online chat room, we do not have such high requirements on the transmission speed of information, but rather the reliability of information transmission. Therefore, the protocol used in most tutorials on the Internet is also the TCP protocol.
For example, the tutorial referenced in this article: How to implement an online chat room function using Python

But since there are many tutorials on how to implement online chat rooms with TCP, I wanted to try to implement it using UDP protocol and try a new method. If you want to use TCP to make a chat room, you can also refer to the following tutorial. The main difference between UDP and TCP protocols is the number of threads to be opened in the server, and the rest of the ideas are basically the same.

The number of threads required by the TCP server = the number of socket connections maintained (a thread is created for each socket request, and each thread is responsible for receiving and sending information for the socket. When a thread does not receive data for a long time, Destroy it yourself)
The number of threads required by the UDP server = receiving information thread + forwarding information thread (because UDP does not need to maintain a socket connection, so only two threads are enough; UDP exchanges server threads for unreliable information transmission. Reduction of overhead)
The number of threads required for the TCP/UDP client = receiving information thread + sending information thread (the client only needs one thread to send data and one thread to receive data. After receiving the data, insert the data into the chat window , thereby creating the effect of others sending messages to your own window)

The following figure is the flow chart of the UDP server:
after the client sends data to the server address, there is no need to worry about it. The server'sReceive information threadThe data will be stored in the buffer by itself.
Then the serverForward information threadWhen it is detected that there is data in the buffer, the data will be sent to all client IP addresses (clients are inLog in to the serverwill carry its own client IP information, and the server will store this information inlist, when forwarding later, the list will be traversed to send information sequentially, thereby achieving the effect of mass sending).
Insert image description here

1.3 Front-end and back-end function design ideas

After selecting the communication protocol, we will proceed to the next step of specific design.

First, we need to havegraphic interfacechat room, then we need to learn how to make a basic front-end interface, and how to bind the content displayed on the front-end interface with the back-end data (such as chat information, group friend names in the chat room).

Here I usegraphic interfaceThe tool is tkinter, which is easy to use and it doesn't take much time to create a simple interface.

1.3.1 Front-end

The front-end interface consists of three pages:

  1. Login window
  2. chat window
  3. Private chat window

1. The login window must enable user login, as shown in the figure below:
Destination IP addressIt is the IP address of the server. Because we treat our computer as both a server and a client here, the destination IP address is 127.0.0.1 (if you want to achieve communication between different computers, you need to connect to the same LAN and Fill in the IP address of another computer serving as the server in the destination IP address field).

Insert image description here
After clicking the login button, the client will use the destination IP address and destination port number entered in the login window to find the corresponding server to connect to. During this connection process, the client will also implicitly send its own user name information , client IP information , user-bound port number and other login information to the backend, so that the backend can send the client IP information and Username information is stored in the server list and is available for subsequent use.
So the front-end"Log in"The button needs to be bound to atrigger function, when we click the "Login" button, this function will be triggered, which can package the data in our white input box and send it to the backend.

2. The chat window must be able to display the data forwarded by the server:

The principle is that the server first receives the information sent by other users and sends the information to each client in bulk. The client then inserts the information into its own chat room interface based on the information forwarded by the server, so that other users can "send" information. to the effect of your own chat box.
Insert image description here

3. The private chat window is used to tell the server the object and content of the private chat:
Since the server itself has used a list to store the mapping relationship between the user name and the user name corresponding to the IP address, so we only need to tell the server the user b we want to chat with privately. The server will find the IP corresponding to user b based on the name, and then send this information privately to b instead of sending it to everyone in bulk.

Insert image description here
ClickSurebutton, the private chat function will be triggered , which is used to package the private chat user name and private chat content and send it to the server.

The overall simple flow chart is as follows:
Insert image description here

1.3.2 Backend

client

Insert image description here

The following is the implementation of the login window:

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)  # 发送用户名

The following is the implementation of the chat window:
Insert image description here
After entering the chat room, enter the data the user wants to send and click the send button.send buttonThe bound send() function will be triggered:

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

The receive() function will loop to monitor whether the data forwarded by the server is received. When the message is received, the information will be displayed in the chat window (such as the display below of all user names in the group):

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-------')

The following is the function implementation of the private chat window:
Insert image description here

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('')
server

The server needs to create two threads, one for receiving data and one for sending data:

    def run(self):
        self.s.bind((IP, PORT))         #绑定监听端口
        q = threading.Thread(target=self.sendData)  
        q.start()	#开启发送数据线程
        t = threading.Thread(target=self.receive)  
        t.start()	# 开启接收信息进程
Implementation of the thread for the server to receive user information:

The main function is to receive data sent by users. For example, "Hello~User b~0" is received and divided into
three pieces of information: "Hello", "User b", and "0". The first string is the chat content sent by the user , and the second string is the message sent by
the user.
The username of the chat content.
The 0 in the third string means it is a group message, not a private chat.

    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)
Thread implementation of server sending chat content:
    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])) #聊天内容发送过去

2. Overall code

The project is divided into two parts of code, one is UDPServer.py and the other is UDPClient.py

2.1 How to run the server and client on the same computer:

If you only have one computer to run the program, here are the steps:

Start UDPServer.py first, then UDPClient.py.
If you want to log in to the chat room with multiple users, just copy a few more copies of UDPClient.py and run them separately. (Just fill in 127.0.0.1 for the IP address of the Client connection, which is your local IP)
Insert image description here

2.2 How to run the server and client on multiple computers:

If you have multiple computers to run the program, the steps are as follows:

Suppose there are 3 computers A, B, and C.
A serves as the server, and B and C serve as clients.
Then we must first connect ABC to the same LAN or hotspot.

Note: If you run Section 2.1 successfully on your own computer, but fail to run on multiple computers, the reason is usually that your server IP address is not filled in correctly, or the firewall of each computer is not turned off.
I remember that when I demonstrated before, I turned off the firewalls on all three computers and set the network to public or shared so that computers B and C could connect to server A.

Then server A enters ipconfig in its own cmd window to obtain its own IP address (there may be many addresses obtained, if using a wireless WIFI hotspot, it is usually the WLAN address), and then A changes the IP to its own in UDPServer.py The IP found.
Insert image description here
Insert image description here
The port can be customized, but it is recommended not to use 8080 and other ports that may be occupied by other software.

Next, run UDPClient.py on computers B and C and fill in the destination IP address of the connected server as 192.168.124.18 (the A address you found).

2.3 Client.py code

The client code is as follows:

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 code

The server code is as follows:

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"

Summarize

This tutorial is also written with reference to the TCP network chat room tutorial on the Internet. It mainly explains the implementation ideas of UDP. If there are any errors, please correct me.

Guess you like

Origin blog.csdn.net/Bartender_VA11/article/details/126644478