计算机网络-Socket编程
以下代码仅限于在局域网环境或本机中执行。
1、URL请求程序
程序功能
- 请求网页:输入 URL 地址,程序能够向服务器发送请求,获取网页内容。
- 保存网页:将请求到的网页内容保存为 HTML 文件。
- 计算网页大小:计算所保存的 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、系统时间查询
程序功能
- 客户端功能:
- 发送字符串"Time"给服务器端。
- 发送字符串"Exit"给服务器端。
- 服务器端功能:
- 接收来自客户端的消息。
- 如果收到的消息是"Time",则返回当前系统时间。
- 如果收到的消息是"Exit",则发送"Bye"并关闭TCP连接。
- 交互过程:
- 客户端和服务器端会打印收到和发送的消息,以及处理请求的结果。
- 客户端发送"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、网络文件传输
程序功能
- 客户端输入要请求的文件名并发送给服务器。
- 服务器端根据文件名传输对应的文件给客户端。
- 客户端接收文件并保存在硬盘上。
客户端程序
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 应用程序主事件循环