Bootstrap

计算机网络-Socket编程

计算机网络-Socket编程

以下代码仅限于在局域网环境或本机中执行。

1、URL请求程序

程序功能

  1. 请求网页:输入 URL 地址,程序能够向服务器发送请求,获取网页内容。
  2. 保存网页:将请求到的网页内容保存为 HTML 文件。
  3. 计算网页大小:计算所保存的 HTML 文件的大小,并输出结果。
import requests  # 导入 requests 库,用于发送 HTTP 请求
import os  # 导入 os 模块,用于操作文件路径和文件大小信息

def saveHtml(file_name, file_content):
    """
    将 HTML 内容保存到文件中的函数

    Args:
        file_name: 文件名
        file_content: 文件内容
    """
    file_content.encoding = 'utf-8'  # 设置文件内容编码为 UTF-8
    with open(file_name.replace('/', '_') + ".html", "wb") as f:  # 以二进制写入模式打开文件
        f.write(file_content.content)  # 将文件内容写入文件中
    print("保存文件名:" + save_str + ".html")  # 打印保存的文件名


url_str = input("请输入一个URL:")  # 获取用户输入的 URL
html_get = requests.get(url_str)  # 发送 GET 请求获取网页内容
print("请求的URL为:", html_get.url)  # 打印请求的 URL

print("------请求成功------")  # 打印请求成功的提示信息

save_str = url_str  # 初始化保存文件名
if save_str[-1] == '/':  # 如果 URL 末尾为 '/'
    save_str = url_str[:-1]  # 去除末尾的 '/'
save_str = save_str.split('/')[-1]  # 获取 URL 中最后一个路径片段作为保存文件名

saveHtml(save_str, html_get)  # 调用保存 HTML 内容到文件的函数

document_size = os.path.getsize(save_str + ".html")  # 获取保存的文件大小

print("文件大小:" + str(document_size) + "Byte")  # 打印文件大小
print("保存路径:" + os.getcwd())  # 打印保存文件的路径

2、系统时间查询

程序功能

  1. 客户端功能:
  • 发送字符串"Time"给服务器端。
  • 发送字符串"Exit"给服务器端。
  1. 服务器端功能:
  • 接收来自客户端的消息。
  • 如果收到的消息是"Time",则返回当前系统时间。
  • 如果收到的消息是"Exit",则发送"Bye"并关闭TCP连接。
  1. 交互过程:
  • 客户端和服务器端会打印收到和发送的消息,以及处理请求的结果。
  • 客户端发送"Time"后,会等待服务器端的响应并打印出当前系统时间。
  • 客户端发送"Exit"后,会等待服务器端的响应并打印出"Bye",然后关闭连接。
  • 服务器端会接收来自客户端的消息,并根据接收到的消息进行相应的处理,然后将处理结果发送给客户端。

客户端程序

from socket import *  # 导入所有 socket 模块的内容

clientSocket = socket(AF_INET, SOCK_STREAM)  # 创建一个 TCP 套接字对象

print('1个客户端正在运行')  # 打印提示消息,表示客户端正在运行

serverName = input('请输入要连接的服务端IP:')  # 获取用户输入的要连接的服务端 IP 地址
serverPort = int(input('请输入要连接的服务端的端口号:'))  # 获取用户输入的要连接的服务端端口号

try:
    clientSocket.connect((serverName, serverPort))  # 尝试连接到指定的服务器
except socket.error as e:  # 捕获连接失败的异常
    print(f"连接服务器端失败:{e}")  # 打印连接失败的消息
    exit()  # 退出程序

clientAddress = clientSocket.getsockname()  # 获取客户端的地址信息
serverAddress = clientSocket.getpeername()  # 获取连接的服务器端的地址信息

print('客户端地址:', clientAddress)  # 打印客户端地址
print('连接到', serverAddress)  # 打印连接到的服务器端地址

while True:  # 进入循环,持续执行以下操作
    request = input('发送一条请求:')  # 获取用户输入的请求信息

    try:
        clientSocket.send(request.encode())  # 发送用户输入的请求给服务器端
    except Exception as e:  # 捕获发送请求失败的异常
        print(f"发送请求失败:{e}")  # 打印发送请求失败的消息
        break  # 退出循环,结束程序的执行

    modifiedSentence = clientSocket.recv(1024).decode()  # 接收服务器端返回的修改后的数据

    print(modifiedSentence)  # 打印服务器端返回的修改后的数据

    if request == 'Exit':  # 如果用户输入的请求是"Exit"
        clientSocket.close()  # 关闭客户端的套接字连接
        break  # 退出循环,结束程序的执行

服务端程序

from socket import *  # 导入所有 socket 模块的内容
import time  # 导入 time 模块

def get_local_ip():
    """
    获取本地IP地址的函数
    """
    s = socket(AF_INET, SOCK_DGRAM)  # 创建一个UDP套接字
    try:
        s.connect(('8.8.8.8', 80))  # 连接到Google的DNS服务器
        ip = s.getsockname()[0]  # 获取本地IP地址
    finally:
        s.close()  # 关闭套接字
    return ip  # 返回本地IP地址

serverPort = 12000  # 服务器端口号

serverSocket = socket(AF_INET, SOCK_STREAM)  # 创建一个TCP套接字对象
serverSocket.bind((get_local_ip(), serverPort))  # 绑定服务器IP地址和端口号
serverSocket.listen(1)  # 监听连接,允许最多1个等待连接

print('----------------------------------')
print('服务器已经准备好连接了')
print('服务器地址为:', serverSocket.getsockname())

while True:  # 循环等待连接

    try:
        connectionSocket, addr = serverSocket.accept()  # 接受客户端连接请求
    except socket.error as e:
        print(f"接受客户端连接失败:{e}")
        continue

    print('----------------------------------')
    print('接收到一个新连接')
    print('连接地址为: ', serverSocket.getsockname())
    print('客户端地址为:', addr)

    while True:  # 循环处理客户端请求

        try:
            request = connectionSocket.recv(1024).decode()  # 接收客户端发送的消息
        except Exception as e:
            print(f"接收客户端请求失败:{e}")
            break

        if request == '':  # 如果收到空消息,则继续接收下一条消息
            continue

        print('收到请求: ', request)  # 打印收到的请求

        if request == 'Time':  # 如果收到的请求是"Time"

            now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())  # 获取当前系统时间

            print('发送响应:', now)  # 打印要发送的响应消息
            try:
                connectionSocket.send(('收到当前服务器上系统时间为:' + now).encode())  # 发送系统时间给客户端
            except Exception as e:
                print(f"发送响应失败:{e}")  # 打印发送响应失败的消息

        elif request == 'Exit':  # 如果收到的请求是"Exit"

            print('发送响应:  Bye')  # 打印要发送的响应消息
            try:
                connectionSocket.send('收到回复: Bye'.encode())  # 发送"Bye"给客户端
            except Exception as e:
                print(f"发送响应失败:{e}")  # 打印发送响应失败的消息

            try:
                connectionSocket.close()  # 关闭与客户端的连接
            except Exception as e:
                print(f"关闭连接失败:{e}")  # 打印关闭连接失败的消息

            break  # 结束内层循环

        else:  # 如果收到的请求不是"Time"也不是"Exit"

            print('发送响应:  输入有误,请重新输入')  # 打印要发送的响应消息
            try:
                connectionSocket.send('输入有误,请重新输入'.encode())  # 发送提示消息给客户端
            except Exception as e:
                print(f"发送响应失败:{e}")  # 打印发送响应失败的消息

3、网络文件传输

程序功能

  1. 客户端输入要请求的文件名并发送给服务器。
  2. 服务器端根据文件名传输对应的文件给客户端。
  3. 客户端接收文件并保存在硬盘上。

客户端程序

from socket import *  # 导入所有 socket 模块的内容
import struct  # 导入 struct 模块

def recv_msg(sock):
    """
    接收消息的函数
    """
    msg_len_bytes = sock.recv(4)  # 接收4字节的消息长度
    msg_len = struct.unpack('!I', msg_len_bytes)[0]  # 使用大端格式解析消息长度

    msg = sock.recv(msg_len)  # 根据消息长度接收消息内容
    return msg.decode()  # 返回解码后的消息内容

clientSocket = socket(AF_INET, SOCK_STREAM)  # 创建一个TCP套接字对象
print('-----------------------------')
print('客户端正在运行')  # 打印提示消息,表示客户端正在运行

serverName = input('请输入要连接的服务端IP:')  # 获取用户输入的要连接的服务端 IP 地址
serverPort = int(input('请输入要连接的服务端的端口号:'))  # 获取用户输入的要连接的服务端端口号

clientSocket.connect((serverName, serverPort))  # 连接到指定的服务器
print('客户端地址:', clientSocket.getsockname())  # 打印客户端地址
print('连接到', clientSocket.getpeername())  # 打印连接到的服务器端地址

filename = input('请输入所请求的文件名:')  # 获取用户输入的所请求的文件名
clientSocket.send(filename.encode())  # 将文件名编码后发送给服务器
print('已发送文件名 【', filename, '】 至服务器')

modifiedSentence = recv_msg(clientSocket)  # 接收服务器的响应消息
print(modifiedSentence)  # 打印服务器的响应消息

if modifiedSentence == 'ok':  # 如果服务器响应为'ok'

    file_size = recv_msg(clientSocket)  # 接收服务器发送的文件大小信息
    print('文件大小为:', file_size, ' 字节')  # 打印文件大小信息

    print('-----------------------------')
    f = open(filename, 'wb')  # 以二进制写模式打开文件

    while True:  # 循环接收文件数据
        data = clientSocket.recv(64)  # 接收文件数据,每次最多接收64字节

        if len(data) == 0:  # 如果接收到的数据长度为0,表示文件接收完毕
            print('-----------------------------')
            print('1个名为 【', filename, '】、大小为 ', file_size, ' 字节的文件已被保存')
            print('接收完毕!')
            print('-----------------------------')
            break  # 结束内层循环

        print('收到 ', len(data), ' 字节的数据')  # 打印接收到的数据长度
        f.write(data)  # 将接收到的数据写入文件

print('服务器已关闭')  # 打印服务器已关闭的消息
clientSocket.close()  # 关闭客户端套接字连接

服务端程序

from socket import *
import os
import struct

def send_msg(sock, msg):
    """
    发送消息给连接的套接字,先发送消息长度,然后发送消息内容
    """
    # 计算消息长度
    msg_len = len(msg)
    # 将消息长度打包为4字节无符号整数并发送
    msg_len_bytes = struct.pack('!I', msg_len)
    sock.sendall(msg_len_bytes)
    # 发送消息内容
    sock.sendall(msg.encode())

def get_local_ip():
    """
    获取本地IP地址
    """
    s = socket(AF_INET, SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()
    return ip

# 创建套接字
serverSocket = socket(AF_INET, SOCK_STREAM)

# 设置服务器端口号和地址
serverPort = 12000
serverSocket.bind((get_local_ip(), serverPort))

# 监听连接
serverSocket.listen(1)

print('-----------------------------')
print('服务器正在运行,等待连接...')
print('服务器地址为:', serverSocket.getsockname())

while True:
    # 接受客户端连接请求
    connectionSocket, addr = serverSocket.accept()
    print('----------------------------------')
    print('接收到一个新连接')
    print('连接地址为:', serverSocket.getsockname())
    print('客户端地址为:', addr)

    # 接收客户端发送的文件名
    filename = connectionSocket.recv(1024).decode()
    print('收到文件名 【', filename, '】')

    if os.path.exists(filename):  # 如果文件存在
        # 发送确认消息给客户端
        send_msg(connectionSocket, 'ok')

        # 获取文件大小并发送给客户端
        file_size = os.path.getsize(filename)
        send_msg(connectionSocket, str(file_size))
        print('文件大小为:', file_size, ' 字节')

        # 打开文件并逐段发送给客户端
        f = open(filename, 'rb')
        while True:
            data = f.read(256)
            if len(data) == 0:
                print('-----------------------------')
                print('1个名为 【', filename, '】、大小为 ', file_size, ' 字节的文件已发送')
                print('发送完毕!')
                print('-----------------------------')
                break
            # 发送数据段给客户端
            connectionSocket.send(data)
            print('发送 ', len(data), ' 字节的数据')
        f.close()  # 关闭文件

    else:
        # 如果文件不存在,发送文件不存在消息给客户端
        connectionSocket.send('文件不存在'.encode())

    # 连接断开
    print('连接已断开')
    connectionSocket.close()

4、网络聊天室

程序功能

  • 实现一个基于客户端/服务器端的网络聊天程序。
  • 使用UDP协议传输层,能够支持多个用户的群聊。
  • 客户端:发送聊天信息、显示聊天信息。
  • 服务器端:接收聊天信息、发送系统信息。

客户端程序

from socket import *
import threading
import time

def Send(client_Socket, server_Name, server_Port):
    """
    向服务器发送消息的函数
    """
    while True:
        m = input()  # 获取用户输入的消息
        client_Socket.sendto(m.encode(), (server_Name, server_Port))  # 将消息发送给服务器
        if m == 'quit':  # 如果用户输入'quit',则退出循环
            time.sleep(1)
            client_Socket.close()  # 关闭客户端套接字
            break

def Receive(client_Socket):
    """
    接收服务器发送的消息的函数
    """
    while True:
        try:
            modifiedMessage, serverAddress = client_Socket.recvfrom(2048)  # 接收服务器发送的消息
            print(modifiedMessage.decode())  # 打印收到的消息
        except:
            break  # 出现异常时退出循环

serverName = input("请输入服务器地址:")  # 获取服务器地址
name = input("请输入用户名:")  # 获取用户名
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字

clientSocket.sendto(name.encode(), (serverName, serverPort))  # 向服务器发送用户名

print('欢迎', name, ',输入 <quit> 退出群聊!')

# 创建发送消息的线程和接收消息的线程
th_Send = threading.Thread(target=Send, args=(clientSocket, serverName, serverPort))
th_Receive = threading.Thread(target=Receive, args=(clientSocket,))
th_Send.start()  # 启动发送消息的线程
th_Receive.start()  # 启动接收消息的线程

服务端程序

from socket import *

userList = {}  # 存储用户信息的字典,格式为 {客户端地址: 用户名}

def SendUsers(user_List, data):
    """
    向所有用户发送消息的函数
    """
    for user in user_List:
        serverSocket.sendto(data.encode(), user)  # 向指定用户发送消息
        print('发送给 ', user, ' ---> ', data)  # 打印发送的消息和目标用户

def get_local_ip():
    """
    获取本地IP地址的函数
    """
    s = socket(AF_INET, SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()
    return ip

serverPort = 12000  # 服务器端口号
serverSocket = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字
serverSocket.bind((get_local_ip(), serverPort))  # 绑定服务器IP和端口号
print('服务器正在运行,等待连接...')
print('服务器地址为:', serverSocket.getsockname())  # 打印服务器地址信息

while True:
    message, clientAddress = serverSocket.recvfrom(2048)  # 接收消息和客户端地址
    m = message.decode()  # 解码收到的消息
    print('收到来自 ', clientAddress, ' 的信息 ---> ', m)  # 打印收到的消息和发送方地址

    if clientAddress not in userList:  # 如果客户端地址不在用户列表中
        userList[clientAddress] = m  # 将客户端地址及用户名加入用户列表
        SendUsers(userList, m + ' 已经加入群聊!')  # 向所有用户发送消息通知有新用户加入
    elif m == 'quit':  # 如果收到的消息是 'quit'
        SendUsers(userList, userList[clientAddress] + ' 已经离开群聊!')  # 向所有用户发送消息通知有用户离开
        del userList[clientAddress]  # 从用户列表中删除该用户
    else:  # 如果收到的消息不是 'quit'
        SendUsers(userList, userList[clientAddress] + ':' + m)  # 向所有用户发送消息,包括发送方的用户名和消息内容

5、网络聊天室-基于tkinter的GUI设计

客户端程序

from socket import *  # 导入socket模块
import threading  # 导入线程模块
import time  # 导入时间模块
import tkinter  # 导入tkinter模块
from tkinter import messagebox  # 导入tkinter的消息框模块
import os  # 导入os模块
import sys  # 导入sys模块

def get_local_ip():
    """
    获取本地IP地址的函数
    """
    s = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字
    try:
        s.connect(('8.8.8.8', 80))  # 连接Google的DNS服务器
        ip = s.getsockname()[0]  # 获取本地IP地址
    except Exception as e:
        print(f"获取本地IP地址时出错:{e}")  # 打印获取IP地址时出错的异常信息
        return "无法获取IP地址"  # 返回无法获取IP地址
    finally:
        s.close()  # 关闭套接字
    return ip  # 返回本地IP地址

def Send(client_Socket, server_Name, server_Port, message):
    """
    发送消息的函数
    """
    if message != '':  # 如果消息不为空
        client_Socket.sendto(message.encode(), (server_Name, server_Port))  # 将消息编码并发送给指定地址
    if message == 'quit':  # 如果消息为 'quit'
        time.sleep(1)  # 等待1秒钟
        clientSocket.close()  # 关闭客户端套接字
        python = sys.executable  # 获取Python可执行文件的路径
        os.execl(python, python, *sys.argv)  # 重启Python解释器

def Receive(client_Socket):
    """
    接收消息的函数
    """
    while True:
        try:
            modifiedMessage, serverAddress = client_Socket.recvfrom(2048)  # 接收消息和服务器地址
            text_pad.insert('end', modifiedMessage.decode() + '\n')  # 将接收到的消息插入文本框中
        except Exception as e:
            print(f"接收消息时出错:{e}")  # 打印接收消息时出错的异常信息
            break  # 跳出循环,结束接收消息的线程

def send_message():
    """
    发送消息的函数
    """
    message = send_area.get('1.0', 'end').strip()  # 获取输入框中的消息
    send_area.delete('1.0', 'end')  # 清空输入框
    if message:  # 如果消息不为空
        th_Send = threading.Thread(target=Send, args=(clientSocket, serverName, serverPort, message))  # 创建发送消息的线程
        th_Send.start()  # 启动发送消息的线程

def login():
    """
    登录函数
    """
    global serverName, serverPort, name  # 声明全局变量
    user_name_str = user_name.get()  # 获取用户名
    server_name_str = server_name.get()  # 获取服务器地址
    server_port_str = server_port.get()  # 获取服务器端口号

    serverName = server_name_str  # 设置服务器地址
    serverPort = int(server_port_str)  # 设置服务器端口号
    name = user_name_str  # 设置用户名
    try:
        clientSocket.sendto(name.encode(), (serverName, serverPort))  # 将用户名编码并发送给服务器
        text_pad.config(state='normal')  # 设置文本框为可写入状态
        send_area.config(state='normal')  # 设置发送区为可写入状态
        send_button.config(state='normal')  # 设置发送按钮为可点击状态
        user_name_text.config(state='disabled')  # 设置用户名输入框为不可写入状态
        server_name_text.config(state='disabled')  # 设置服务器地址输入框为不可写入状态
        server_port_text.config(state='disabled')  # 设置服务器端口号输入框为不可写入状态
        yes_btn.config(state='disabled')  # 设置登录按钮为不可点击状态

        text_pad.insert('end', '欢迎' + user_name_str + '进入聊天室' + ' 输入 <quit> 退出群聊\n')  # 插入登录信息到文本框
    except:
        messagebox.showerror('错误', '服务器连接失败')  # 弹出错误消息框,提示服务器连接失败

root = tkinter.Tk()  # 创建Tkinter窗口
root.title("聊天室")  # 设置窗口标题
root.geometry("650x500+100+100")  # 设置窗口大小和位置

# Left
left_frame = tkinter.Frame(root)  # 创建左侧框架
left_frame.pack(side=tkinter.LEFT, anchor=tkinter.N, padx=5, pady=5)  # 放置左侧框架

user_frame = tkinter.LabelFrame(left_frame, text='用户信息', padx=5, pady=5)  # 创建用户信息框架
user_frame.pack()  # 放置用户信息框架

tkinter.Label(user_frame, text='用户名').pack()  # 在用户信息框架中放置用户名标签
user_name = tkinter.StringVar()  # 创建用户名称字符串变量
user_name_text = tkinter.Entry(user_frame, textvariable=user_name, justify='center')  # 创建用户名输入框
user_name_text.pack()  # 放置用户名输入框

tkinter.Label(user_frame, text='服务器地址').pack()  # 在用户信息框架中放置服务器地址标签
server_name = tkinter.StringVar()  # 创建服务器名称字符串变量
server_name_text = tkinter.Entry(user_frame, textvariable=server_name, justify='center')  # 创建服务器地址输入框
server_name_text.pack()  # 放置服务器地址输入框

tkinter.Label(user_frame, text='服务器端口').pack()  # 在用户信息框架中放置服务器端口标签
server_port = tkinter.StringVar()  # 创建服务器端口字符串变量
server_port_text = tkinter.Entry(user_frame, textvariable=server_port, justify='center')  # 创建服务器端口输入框
server_port_text.pack()  # 放置服务器端口输入框

tkinter.Label(user_frame, text='本机IP\n' + get_local_ip()).pack()  # 在用户信息框架中放置本地IP地址标签

clientSocket = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字

yes_btn = tkinter.Button(user_frame, text='登录', width=3, command=login)  # 创建登录按钮
yes_btn.pack(side=tkinter.BOTTOM, fill=tkinter.Y)  # 放置登录按钮

# Right
right_frame = tkinter.Frame(root)  # 创建右侧框架
right_frame.pack(side=tkinter.TOP, padx=5, pady=5)  # 放置右侧框架

info_frame = tkinter.LabelFrame(right_frame)  # 创建聊天信息框架
info_frame.pack()  # 放置聊天信息框架

tkinter.Label(info_frame, text='聊天信息').pack(anchor=tkinter.W)  # 在聊天信息框架中放置聊天信息标签

text_pad = tkinter.Text(info_frame, width=56, state='disabled')  # 创建文本框,设置为不可写入状态
text_pad.pack(side=tkinter.LEFT, fill=tkinter.X)  # 放置文本框

send_text_bar = tkinter.Scrollbar(info_frame)  # 创建滚动条
send_text_bar.pack(side=tkinter.RIGHT, fill=tkinter.Y)  # 放置滚动条
text_pad.config(yscrollcommand=send_text_bar.set)  # 设置文本框的垂直滚动条
send_text_bar.config(command=text_pad.yview)  # 设置滚动条的回调函数

tkinter.Label(right_frame, text='发送信息').pack(anchor=tkinter.W)  # 在右侧框架中放置发送信息标签

send_frame = tkinter.Frame(right_frame)  # 创建发送信息框架
send_frame.pack()  # 放置发送信息框架

send_area = tkinter.Text(send_frame, width=54, height=6, state='disabled')  # 创建发送区,设置为不可写入状态
send_area.pack(side=tkinter.LEFT)  # 放置发送区

send_button = tkinter.Button(send_frame, text='发送', width=3, command=send_message, state='disabled')  # 创建发送按钮,设置为不可点击状态
send_button.pack(side=tkinter.RIGHT, fill=tkinter.Y)  # 放置发送按钮

th_Receive = threading.Thread(target=Receive, args=(clientSocket,))  # 创建接收消息的线程
th_Receive.start()  # 启动接收消息的线程

root.mainloop()  # 运行Tkinter主循环

服务端程序

import tkinter  # 导入tkinter模块
from socket import *  # 导入socket模块
import threading  # 导入线程模块

userList = {}  # 存储用户信息的字典

def SendUsers(user_List, data):
    """
    发送消息给所有用户
    """
    for user in user_List:
        serverSocket.sendto(data.encode(), user)  # 将消息编码并发送给指定用户
        client_address_str = ':'.join(str(element) for element in user)  # 将客户端地址转换为字符串
        server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
        server_information_list.insert('end', '发送给 (' + client_address_str + ') : ' + data + '\n')  # 在服务器信息列表中插入发送的消息
        server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态

def get_local_ip():
    """
    获取本地IP地址
    """
    s = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字
    try:
        s.connect(('8.8.8.8', 80))  # 连接Google的DNS服务器
        ip = s.getsockname()[0]  # 获取本地IP地址
    except Exception as e:
        print(f"获取本地IP地址时出错:{e}")  # 打印获取IP地址时出错的异常信息
        return "无法获取IP地址"  # 返回无法获取IP地址
    finally:
        s.close()  # 关闭套接字
    return ip  # 返回本地IP地址

def Receive(client_Socket):
    """
    接收消息的函数
    """
    while True:
        message, clientAddress = serverSocket.recvfrom(2048)  # 接收消息和客户端地址
        m = message.decode()  # 解码消息
        client_address_str = ':'.join(str(element) for element in clientAddress)  # 将客户端地址转换为字符串
        server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
        server_information_list.insert('end', '收到来自 (' + client_address_str + ') 的信息 : ' + m + '\n')  # 在服务器信息列表中插入接收到的消息
        server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态

        if clientAddress not in userList:  # 如果客户端地址不在用户列表中
            userList[clientAddress] = m  # 将客户端地址和用户名添加到用户列表中

            user_information_list.config(state='normal')  # 设置用户信息列表为可编辑状态
            user_information_list.insert('end', m + '\n')  # 在用户信息列表中插入新加入用户的用户名
            user_information_list.config(state='disabled')  # 设置用户信息列表为不可编辑状态

            SendUsers(userList, m + ' 已经加入群聊!')  # 发送新用户加入群聊的消息给其他用户
        elif m == 'quit':  # 如果接收到的消息是'quit'
            SendUsers(userList, userList[clientAddress] + ' 已经离开群聊!')  # 发送用户离开群聊的消息给其他用户
            del userList[clientAddress]  # 从用户列表中删除离开的用户

            user_information_list.config(state='normal')  # 设置用户信息列表为可编辑状态
            user_information_list.delete('1.0', 'end')  # 清空用户信息列表

            for user in userList:  # 遍历用户列表
                user_information_list.insert('end', userList[user] + '\n')  # 在用户信息列表中插入在线用户的用户名

            user_information_list.config(state='disabled')  # 设置用户信息列表为不可编辑状态
        else:  # 如果接收到的消息不是'quit'
            SendUsers(userList, userList[clientAddress] + ':' + m)  # 发送消息给其他用户

serverPort = 12000  # 设置服务器端口号
serverSocket = socket(AF_INET, SOCK_DGRAM)  # 创建UDP套接字
serverSocket.bind((get_local_ip(), serverPort))  # 将服务器绑定到本地IP地址和端口号

root = tkinter.Tk()  # 创建Tkinter窗口
root.title("聊天室服务器")  # 设置窗口标题
root.geometry("950x700+100+100")  # 设置窗口大小和位置

# left frame
left_frame = tkinter.Frame(root)  # 创建左侧框架
left_frame.pack(side=tkinter.LEFT, anchor=tkinter.N, padx=5, pady=5)  # 放置左侧框架

server_frame = tkinter.LabelFrame(left_frame, text='服务器IP及端口', padx=5, pady=5, font=('华文楷体', 20))  # 创建服务器IP及端口框架
server_frame.pack()  # 放置服务器IP及端口框架

server_name_label = tkinter.Label(server_frame, text='服 务 器 名 称' + '\n', padx=5, pady=5, font=('华文楷体', 18), width=30)  # 创建服务器名称标签
server_name_label.pack()  # 放置服务器名称标签

server_name = tkinter.Entry(server_frame, font=('华文楷体', 18), justify='center')  # 创建服务器名称输入框
server_name.pack()  # 放置服务器名称输入框
server_name.insert(0, get_local_ip())  # 在服务器名称输入框中插入本地IP地址
server_name.config(state='disabled')  # 设置服务器名称输入框为不可编辑状态

server_port_label = tkinter.Label(server_frame, text='服 务 器 端 口' + '\n', padx=5, pady=5, font=('华文楷体', 18), width=30)  # 创建服务器端口标签
server_port_label.pack()  # 放置服务器端口标签

server_port = tkinter.Entry(server_frame, font=('华文楷体', 18), justify='center')  # 创建服务器端口输入框
server_port.pack()  # 放置服务器端口输入框
server_port.insert(0, '12000')  # 在服务器端口输入框中插入默认端口号
server_port.config(state='disabled')  # 设置服务器端口输入框为不可编辑状态

user_information_frame = tkinter.LabelFrame(left_frame, text='用户信息列表', padx=5, pady=5, font=('华文楷体', 20))  # 创建用户信息列表框架
user_information_frame.pack()  # 放置用户信息列表框架

user_information_list = tkinter.Text(user_information_frame, width=30, height=25, font=('华文楷体', 18), state='disabled')  # 创建用户信息列表
user_information_list.pack(side=tkinter.LEFT, fill=tkinter.X)  # 放置用户信息列表

user_information_bar = tkinter.Scrollbar(user_information_frame)  # 创建用户信息列表的滚动条
user_information_bar.pack(side=tkinter.RIGHT, fill=tkinter.Y)  # 放置用户信息列表的滚动条
user_information_list.config(yscrollcommand=user_information_bar.set)  # 设置用户信息列表的垂直滚动条
user_information_bar.config(command=user_information_list.yview)  # 设置用户信息列表滚动条的回调函数

# right frame
right_frame = tkinter.Frame(root)  # 创建右侧框架
right_frame.pack(side=tkinter.TOP, padx=5, pady=5)  # 放置右侧框架

server_information_frame = tkinter.LabelFrame(right_frame, text='服务器信息', padx=5, pady=5, font=('华文楷体', 20))  # 创建服务器信息框架
server_information_frame.pack()  # 放置服务器信息框架

server_information_list = tkinter.Text(server_information_frame, width=65, height=36, font=('华文楷体', 18), state='disabled')  # 创建服务器信息列表
server_information_list.pack(side=tkinter.LEFT, fill=tkinter.X)  # 放置服务器信息列表

server_information_bar = tkinter.Scrollbar(server_information_frame)  # 创建服务器信息列表的滚动条
server_information_bar.pack(side=tkinter.RIGHT, fill=tkinter.Y)  # 放置服务器信息列表的滚动条
server_information_list.config(yscrollcommand=server_information_bar.set)  # 设置服务器信息列表的垂直滚动条
server_information_bar.config(command=server_information_list.yview)  # 设置服务器信息列表滚动条的回调函数

server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
server_information_list.insert(tkinter.END, "服务器正在运行,等待连接. . .\n")  # 在服务器信息列表中插入初始消息
server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态

th_Receive = threading.Thread(target=Receive, args=(serverSocket,))  # 创建接收消息的线程
th_Receive.start()  # 启动接收消息的线程

root.mainloop()  # 进入主事件循环

6、网络聊天室-基于tkinter和ttkbootstrap美化的GUI设计

客户端程序

import tkinter
import time
from socket import *
import threading
from tkinter import messagebox
import os
import sys
import ttkbootstrap


# 函数:获取本地IP地址
def get_local_ip():
    s = socket(AF_INET, SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))  # 连接到Google DNS服务器的80端口
        ip = s.getsockname()[0]  # 获取本地IP地址
    except Exception as e:
        print(f"获取本地IP地址时出错:{e}")
        return "无法获取IP地址"
    finally:
        s.close()  # 关闭socket连接
    return ip


# 函数:发送消息至服务器
def Send(client_Socket, server_Name, server_Port, message):
    if message != '':  # 检查是否为空消息
        client_Socket.sendto(message.encode(), (server_Name, server_Port))  # 将消息编码并发送到服务器
    if message == 'quit':  # 如果消息为'quit',则关闭客户端连接
        time.sleep(1)  # 等待1秒
        clientSocket.close()  # 关闭客户端socket连接
        python = sys.executable  # 获取Python可执行文件路径
        os.execl(python, python, *sys.argv)  # 重新启动脚本


# 函数:接收服务器发送的消息
def Receive(client_Socket):
    while True:
        try:
            modifiedMessage, serverAddress = client_Socket.recvfrom(2048)  # 从服务器接收消息
            text_pad.insert('end', modifiedMessage.decode() + '\n')  # 在文本框中显示接收到的消息
        except Exception as e:
            print(f"接收消息时出错:{e}")
            break


# 函数:发送用户输入的消息
def send_message():
    message = send_area.get('1.0', 'end').strip()  # 获取发送区域中用户输入的消息,并去除首尾空格
    send_area.delete('1.0', 'end')  # 清空发送区域
    if message:  # 检查是否为空消息
        th_Send = threading.Thread(target=Send, args=(clientSocket, serverName, serverPort, message))  # 创建发送消息的线程
        th_Send.start()  # 启动发送消息的线程


# 函数:用户登录到聊天室
def login():
    global serverName, serverPort, name
    user_name_str = user_name.get()  # 获取用户输入的用户名
    server_name_str = server_name.get()  # 获取用户输入的服务器地址
    server_port_str = server_port.get()  # 获取用户输入的服务器端口

    serverName = server_name_str  # 设置全局变量:服务器地址
    serverPort = int(server_port_str)  # 设置全局变量:服务器端口
    name = user_name_str  # 设置全局变量:用户名
    try:
        clientSocket.sendto(name.encode(), (serverName, serverPort))  # 将用户名编码并发送到服务器
        text_pad.config(state='normal')  # 设置聊天信息文本框为可编辑状态
        send_area.config(state='normal')  # 设置发送消息文本框为可编辑状态
        send_button.config(state='normal')  # 设置发送按钮为可点击状态
        user_name_text.config(state='disabled')  # 设置用户名输入框为不可编辑状态
        server_name_text.config(state='disabled')  # 设置服务器地址输入框为不可编辑状态
        server_port_text.config(state='disabled')  # 设置服务器端口输入框为不可编辑状态
        yes_btn.config(state='disabled')  # 设置登录按钮为不可点击状态

        text_pad.insert('end', '欢迎' + user_name_str + '进入聊天室' + ' 输入 <quit> 退出群聊\n')  # 在聊天信息文本框中显示欢迎信息
    except:
        messagebox.showerror('错误', '服务器连接失败')  # 显示连接服务器失败的错误提示框


# 创建主窗口
root = tkinter.Tk()
style = ttkbootstrap.Style(theme='morph')  # 创建主题为'morph'的样式
style.configure('TButton', font=('翩翩体-简', 18))  # 设置按钮样式
root.title("聊天室")  # 设置窗口标题
root.geometry("750x500+100+100")  # 设置窗口大小和位置

# 左侧部分
left_frame = tkinter.Frame(root)  # 创建左侧Frame
left_frame.pack(side=tkinter.LEFT, anchor=tkinter.N, padx=5, pady=5)  # 将左侧Frame放置在主窗口左侧

user_frame = tkinter.LabelFrame(left_frame, text='用户信息', font=('华文楷体', 20), padx=5, pady=5)  # 创建用户信息Frame
user_frame.pack()  # 将用户信息Frame放置在左侧Frame中

# 添加用户名输入框
ttkbootstrap.Label(user_frame, text='用户名', font=('翩翩体-简', 18)).pack()
user_name = ttkbootstrap.StringVar()
user_name_text = ttkbootstrap.Entry(user_frame, textvariable=user_name, justify='center', font=('翩翩体-简', 18))
user_name_text.pack()

# 添加服务器地址输入框
ttkbootstrap.Label(user_frame, text='服务器地址', font=('翩翩体-简', 18)).pack()
server_name = ttkbootstrap.StringVar()
server_name_text = ttkbootstrap.Entry(user_frame, textvariable=server_name, justify='center', font=('翩翩体-简', 18))
server_name_text.pack()

# 添加服务器端口输入框
ttkbootstrap.Label(user_frame, text='服务器端口', font=('翩翩体-简', 18)).pack()
server_port = ttkbootstrap.StringVar()
server_port_text = ttkbootstrap.Entry(user_frame, textvariable=server_port, justify='center', font=('翩翩体-简', 18))
server_port_text.pack()

# 显示本机IP地址
ttkbootstrap.Label(user_frame, text='本机IP', font=('翩翩体-简', 18)).pack()
ttkbootstrap.Label(user_frame, text=get_local_ip(), font=('翩翩体-简', 18)).pack()

clientSocket = socket(AF_INET, SOCK_DGRAM)  # 创建UDP客户端socket

yes_btn = ttkbootstrap.Button(user_frame, text='登录', width=4, command=login)  # 创建登录按钮
yes_btn.pack(side=tkinter.BOTTOM, fill=tkinter.Y)  # 将登录按钮放置在用户信息Frame底部

# 右侧部分
right_frame = ttkbootstrap.Frame(root)  # 创建右侧Frame
right_frame.pack(side=ttkbootstrap.TOP, padx=5, pady=5)  # 将右侧Frame放置在主窗口顶部

info_frame = tkinter.LabelFrame(right_frame, text='聊天信息', font=('华文楷体', 20), padx=5, pady=5)  # 创建聊天信息Frame
info_frame.pack()  # 将聊天信息Frame放置在右侧Frame中

text_pad = ttkbootstrap.Text(info_frame, width=40, height=14, state='disabled', font=('翩翩体-简', 18))  # 创建聊天信息文本框
text_pad.pack(side=ttkbootstrap.LEFT, fill=tkinter.X)  # 将聊天信息文本框放置在聊天信息Frame左侧

send_text_bar = ttkbootstrap.Scrollbar(info_frame)  # 创建滚动条
send_text_bar.pack(side=ttkbootstrap.RIGHT, fill=ttkbootstrap.Y)  # 将滚动条放置在聊天信息Frame右侧
text_pad.config(yscrollcommand=send_text_bar.set)  # 设置聊天信息文本框的纵向滚动条
send_text_bar.config(command=text_pad.yview)  # 设置滚动条的命令为聊天信息文本框的yview方法

ttkbootstrap.Label(right_frame, text='发送信息', font=('华文楷体', 18)).pack(anchor=ttkbootstrap.W)  # 添加发送信息标签

send_frame = ttkbootstrap.Frame(right_frame)  # 创建发送信息Frame
send_frame.pack()  # 将发送信息Frame放置在右侧Frame中

send_area = ttkbootstrap.Text(send_frame, width=36, height=6, state='disabled', font=('翩翩体-简', 18))  # 创建发送消息文本框
send_area.pack(side=ttkbootstrap.LEFT)  # 将发送消息文本框放置在发送信息Frame左侧

send_button = ttkbootstrap.Button(send_frame, text='发送', width=4, command=send_message, state='disabled')  # 创建发送按钮
send_button.pack(side=ttkbootstrap.RIGHT, fill=ttkbootstrap.Y)  # 将发送按钮放置在发送信息Frame右侧

th_Receive = threading.Thread(target=Receive, args=(clientSocket,))  # 创建接收消息的线程
th_Receive.start()  # 启动接收消息的线程

root.mainloop()  # 运行主循环

服务端程序

import tkinter  # 导入 tkinter 模块
from socket import *  # 导入 socket 模块中的所有函数和变量
import threading  # 导入 threading 模块
import ttkbootstrap  # 导入 ttkbootstrap 模块

userList = {}  # 创建空字典,用于存储用户信息


def SendUsers(user_List, data):
    """
    向所有用户发送消息的函数

    Args:
        user_List: 用户列表,存储用户地址信息
        data: 待发送的消息内容
    """
    for user in user_List:
        serverSocket.sendto(data.encode(), user)  # 使用 UDP 套接字发送消息给用户

        client_address_str = ':'.join(str(element) for element in user)  # 将用户地址信息转换为字符串形式

        server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
        server_information_list.insert('end', '发送给 (' + client_address_str + ') : ' + data + '\n')  # 在服务器信息列表中插入发送消息的记录
        server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态


def get_local_ip():
    """
    获取本地 IP 地址的函数

    Returns:
        本地 IP 地址
    """
    s = socket(AF_INET, SOCK_DGRAM)  # 创建 UDP 套接字
    try:
        s.connect(('8.8.8.8', 80))  # 连接 Google 的 DNS 服务器
        ip = s.getsockname()[0]  # 获取本地 IP 地址
    except Exception as e:
        print(f"获取本地IP地址时出错:{e}")  # 打印获取本地 IP 地址时出错的信息
        return "无法获取IP地址"  # 返回无法获取 IP 地址的提示
    finally:
        s.close()  # 关闭套接字
    return ip  # 返回本地 IP 地址


def Receive(client_Socket):
    """
    接收消息的函数

    Args:
        client_Socket: 服务器套接字
    """
    while True:
        message, clientAddress = serverSocket.recvfrom(2048)  # 接收消息和客户端地址信息
        m = message.decode()  # 解码接收到的消息
        client_address_str = " : ".join(str(element) for element in clientAddress)  # 将客户端地址信息转换为字符串形式

        server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
        server_information_list.insert('end', '收到来自 (' + client_address_str + ') 的信息 : ' + m + '\n')  # 在服务器信息列表中插入收到消息的记录
        server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态

        if clientAddress not in userList:  # 如果客户端地址不在用户列表中
            userList[clientAddress] = m  # 将客户端地址和用户名添加到用户列表中

            user_information_list.config(state='normal')  # 设置用户信息列表为可编辑状态
            user_information_list.insert('end', m + '\n')  # 在用户信息列表中插入新用户的记录
            user_information_list.config(state='disabled')  # 设置用户信息列表为不可编辑状态

            SendUsers(userList, m + ' 已经加入群聊!')  # 发送加入群聊的提示给所有用户
        elif m == 'quit':  # 如果收到的消息是 'quit'
            SendUsers(userList, userList[clientAddress] + ' 已经离开群聊!')  # 发送离开群聊的提示给所有用户

            del userList[clientAddress]  # 从用户列表中删除离开的用户

            user_information_list.config(state='normal')  # 设置用户信息列表为可编辑状态
            user_information_list.delete('1.0', 'end')  # 清空用户信息列表中的内容

            for user in userList:
                user_information_list.insert('end', userList[user] + '\n')  # 更新用户信息列表中的用户记录

            user_information_list.config(state='disabled')  # 设置用户信息列表为不可编辑状态
        else:
            SendUsers(userList, userList[clientAddress] + ':' + m)  # 向所有用户发送收到的消息


serverPort = 12000  # 设置服务器端口号
serverSocket = socket(AF_INET, SOCK_DGRAM)  # 创建 UDP 套接字
serverSocket.bind((get_local_ip(), serverPort))  # 绑定服务器 IP 地址和端口号

root = tkinter.Tk()  # 创建 Tkinter 应用程序窗口
style = ttkbootstrap.Style(theme='morph')  # 创建主题为 'morph' 的样式
root.title("聊天室服务器")  # 设置窗口标题
root.geometry("950x700+100+100")  # 设置窗口大小和位置

# left frame
left_frame = tkinter.Frame(root)  # 创建左侧 Frame
left_frame.pack(side=tkinter.LEFT, anchor=tkinter.N, padx=5, pady=5)  # 将左侧 Frame 放置在窗口左侧

server_frame = tkinter.LabelFrame(left_frame, text='服务器IP及端口', padx=5, pady=5, font=('华文楷体', 20))  # 创建服务器信息 Frame
server_frame.pack()  # 将服务器信息 Frame 放置在左侧 Frame 中

server_name_label = ttkbootstrap.Label(server_frame, text='服 务 器 名 称' + '\n', font=('华文楷体', 18), width=32)  # 创建服务器名称标签
server_name_label.pack()  # 将服务器名称标签放置在服务器信息 Frame 中

server_name = ttkbootstrap.Entry(server_frame, font=('华文楷体', 18), justify='center')  # 创建服务器名称文本框
server_name.pack()  # 将服务器名称文本框放置在服务器信息 Frame 中
server_name.insert(0, get_local_ip())  # 在服务器名称文本框中插入本地 IP 地址
server_name.config(state='disabled')  # 设置服务器名称文本框为不可编辑状态

server_port_label = ttkbootstrap.Label(server_frame, text='服 务 器 端 口' + '\n', font=('华文楷体', 18), width=32)  # 创建服务器端口标签
server_port_label.pack()  # 将服务器端口标签放置在服务器信息 Frame 中

server_port = ttkbootstrap.Entry(server_frame, font=('华文楷体', 18), justify='center')  # 创建服务器端口文本框
server_port.pack()  # 将服务器端口文本框放置在服务器信息 Frame 中
server_port.insert(0, '12000')  # 在服务器端口文本框中插入默认端口号
server_port.config(state='disabled')  # 设置服务器端口文本框为不可编辑状态

user_information_frame = tkinter.LabelFrame(left_frame, text='用户信息列表', padx=5, pady=5, font=('华文楷体', 20))  # 创建用户信息 Frame
user_information_frame.pack()  # 将用户信息 Frame 放置在左侧 Frame 中

user_information_list = ttkbootstrap.Text(user_information_frame, width=30, height=26, font=('华文楷体', 18), state='disabled')  # 创建用户信息列表文本框
user_information_list.pack(side=ttkbootstrap.LEFT, fill=ttkbootstrap.X)  # 将用户信息列表文本框放置在用户信息 Frame 中

user_information_bar = ttkbootstrap.Scrollbar(user_information_frame)  # 创建用户信息列表滚动条
user_information_bar.pack(side=ttkbootstrap.RIGHT, fill=ttkbootstrap.Y)  # 将用户信息列表滚动条放置在用户信息 Frame 中
user_information_list.config(yscrollcommand=user_information_bar.set)  # 设置用户信息列表文本框的纵向滚动条
user_information_bar.config(command=user_information_list.yview)  # 设置用户信息列表滚动条的控制命令

# right frame

right_frame = tkinter.Frame(root)  # 创建右侧 Frame
right_frame.pack(side=tkinter.TOP, padx=5, pady=5)  # 将右侧 Frame 放置在窗口顶部

server_information_frame = tkinter.LabelFrame(right_frame, text='服务器信息', padx=5, pady=5, font=('华文楷体', 20))  # 创建服务器信息 Frame
server_information_frame.pack()  # 将服务器信息 Frame 放置在右侧 Frame 中

server_information_list = ttkbootstrap.Text(server_information_frame, width=65, height=36, font=('华文楷体', 18), state='disabled')  # 创建服务器信息列表文本框
server_information_list.pack(side=ttkbootstrap.LEFT, fill=ttkbootstrap.X)  # 将服务器信息列表文本框放置在服务器信息 Frame 中

server_information_bar = ttkbootstrap.Scrollbar(server_information_frame)  # 创建服务器信息列表滚动条
server_information_bar.pack(side=ttkbootstrap.RIGHT, fill=ttkbootstrap.Y)  # 将服务器信息列表滚动条放置在服务器信息 Frame 中
server_information_list.config(yscrollcommand=server_information_bar.set)  # 设置服务器信息列表文本框的纵向滚动条
server_information_bar.config(command=server_information_list.yview)  # 设置服务器信息列表滚动条的控制命令

server_information_list.config(state='normal')  # 设置服务器信息列表为可编辑状态
server_information_list.insert(ttkbootstrap.END, "服务器正在运行,等待连接. . .\n")  # 在服务器信息列表中插入初始提示信息
server_information_list.config(state='disabled')  # 设置服务器信息列表为不可编辑状态

th_Receive = threading.Thread(target=Receive, args=(serverSocket,))  # 创建接收消息的线程
th_Receive.start()  # 启动接收消息的线程

root.mainloop()  # 运行 Tkinter 应用程序主事件循环

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;