话不多说先看代码!
需要网络调试助手配合
import socket
import time
import os
import threading
from matplotlib import pyplot as plt
class VoteSys(object):
"""
投票系统+数据分析
"""
def __init__(self, theme, num_info, time_info):
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建套接字
self.udp_socket.bind(("", 6666)) # 绑定接受端口
self.send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 广播套接字
self.theme = theme # 投票主题(例:第三次项目投票)
self.num = num_info # 投票上限
self.cycle = time_info # 计时周期
self.Dataset = list() # 数据列表
self.id_list = list()
self.save_data_info = dict() # 数据分析的得分
self.team_name = None
def end_sys(self):
"""
主要负责计时和负责发送广播
:return:
"""
self.team_name = input("请输入本轮投票的团队名称(例:一组)")
# self.send_info(team_name)
for i in range(self.cycle):
content = self.cycle - i
if i % 15 == 0:
self.send_info(self.team_name)
time.sleep(1)
print("Remaining time for voting:(%ds) Current participants:(%d)" % (content, len(self.Dataset)), end="\r")
self.udp_socket.close() # 关闭接受套接字
self.send_socket.close()
def send_info(self, team_name):
"""启动方法"""
# 设置UDP套接字允许其广播(注意如果udp套接字需要广播,则一定要添加此语句,否则不用添加)
self.send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
dest_info = ("<broadcast>", 10001) # <broadcast>会自动改为本语句网的广播ip
send_info = "正在进行:<" + team_name + ">项目投票!回复本ip固定接收端口:6666(已投请忽略)"
# 此时只要是本局域网中的电脑上有 用10001端口的udp程序 它就会收到此数据
self.send_socket.sendto(send_info.encode('utf-8'), dest_info)
def rec_info(self):
"""
接收投票信息筛选
"""
# 多线程计时
end_time = threading.Thread(target=self.end_sys)
end_time.start()
# print("投票通道创建成功!")
while True:
try:
# 接收消息
contents, client_info = self.udp_socket.recvfrom(100)
# 是否是数字,是否超过上限,判断是否重复投票,
if contents.isdigit() and client_info[0] not in \
self.id_list and int(contents) <= self.num:
# 将数据存入数据组
self.Dataset.append(int(contents))
# 将ip存入列表判断是否重复
self.id_list.append(client_info[0])
# 回送数据收到信息
self.remind_info(client_info[0], client_info[1])
# print("Got a vote from %s Current participants:%d" % (client_info[0], len(self.Dataset)))
else:
# 不满足条件发送数据错误信息
self.remind_error(client_info[0], client_info[1], contents)
except OSError:
print("")
# print("投票通道关闭")
break
self.math_data()
def remind_error(self, temp_id, temp_port, temp_content):
"""报错方法"""
# 1. 创建一个udp套接字
temp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if temp_id in self.id_list:
temp.sendto("Error:重复投票无效(一次就好)".encode("utf-8"), (temp_id, temp_port))
elif not temp_content.isdigit():
temp.sendto("Error:请输入有效数字(整数)".encode("utf-8"), (temp_id, temp_port))
elif int(temp_content) >= self.num:
temp.sendto(("Error:超过上限(上限:%d)" % self.num).encode("utf-8"), (temp_id, temp_port))
temp.close()
def save_data(self, temp_name, temp_data):
try:
with open("Vote_data.txt", "r") as r1:
# 读取数据中的字典
self.save_data_info = eval(r1.read())
# 将结果保存
except FileNotFoundError:
pass
finally:
# 向字典中添加数据
self.save_data_info[temp_name] = temp_data
# 保存字典
with open("Vote_data.txt", "w") as w1:
w1.write(str(self.save_data_info))
print("The data has been saved!")
def math_data(self):
self.Dataset.sort()
if len(self.Dataset) == 0:
# print("无人投票")
self.save_data(self.team_name, 0)
elif 0 < len(self.Dataset) <= 3:
result = 0
for num in self.Dataset:
result += num
result = result / len(self.Dataset)
self.save_data(self.team_name, result)
else:
del self.Dataset[0] # 删除最小值
del self.Dataset[-1] # 删除最大值
result = 0
for num in self.Dataset:
result += num
result = result / len(self.Dataset)
self.save_data(self.team_name, result)
@staticmethod
def remind_info(temp_id, temp_port):
"""投票成功"""
# 1. 创建一个udp套接字
t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
t.sendto("Author: 投票成功!谢谢参与!".encode("utf-8"), (temp_id, temp_port))
# 3. 关闭
t.close()
class Initiate(object):
def __init__(self):
"""初始化方法:创建读取数据"""
self.init_info = None
self.judge() # 判断是否有正在进行的投票
def main(self):
if self.init_info:
# 这种情况是已经有投票的数据
temp_judge = input("当前正在进行(%s)投票,是否进入(y/n)<打印投票结果请输入:print>" % self.init_info[0])
if temp_judge.lower() == "y":
print("主题:%s 上限:%d 周期:%d" % (self.init_info[0], self.init_info[1], self.init_info[2]))
test = VoteSys(self.init_info[0], self.init_info[1], self.init_info[2])
test.rec_info() # 等待接收
elif temp_judge.lower() == "n":
# 删除历史数据重新开始
os.remove("./user_info.txt")
os.remove("./Vote_data.txt")
self.reset()
test = VoteSys(self.init_info[0], self.init_info[1], self.init_info[2])
test.rec_info() # 等待接收
elif temp_judge == "print":
self.draw_show()
else:
# 直接重新开始
self.reset()
test = VoteSys(self.init_info[0], self.init_info[1], self.init_info[2])
test.rec_info() # 等待接收
def judge(self):
"""
判断是否有初始化设置
:return:
"""
try:
with open("user_info.txt", "r") as t1:
content_info = t1.read()
self.init_info = eval(content_info)
except FileNotFoundError:
self.init_info = None
def reset(self):
theme_info = input("请输入本次投票的主题(例:Python1/2第一次项目竞赛):")
num_info = int(input("请输入本次头票上限(例:5):"))
temp_time_info = int(input("请输入本次投票周期(秒):"))
self.init_info = [theme_info, num_info, temp_time_info]
with open("user_info.txt", "w") as t3:
t3.write(str([theme_info, num_info, temp_time_info]))
@staticmethod
def draw_show():
a = list()
b = list()
with open("Vote_data.txt", "r") as r1:
content = eval(r1.read())
for temp_a, temp_b in content.items():
a.append(temp_a)
b.append(temp_b)
with open("user_info.txt", "r") as r6:
temp_title = eval(r6.read())[0]
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.figure(figsize=(20, 8), dpi=80)
plt.bar(range(len(a)), b, width=0.25, color="orange", label="组别")
plt.xticks(range(len(a)), a, fontsize=12) # , rotation=45
# 添加描述
plt.xlabel("组名") # x轴描述
plt.ylabel("得分") # y轴描述
plt.title(temp_title) # 标题
# 添加图例
# plt.legend(loc=0) # loc=位置参数(默认识别位置)
# 绘制网格(默认对应)
plt.grid(alpha=0.4) # alpha=透明度
plt.show()
if __name__ == "__main__":
"""
说明:
"""
t1 = Initiate()
t1.main()
效果展示:
开始输入投票主题作为条形统计图的主题,然后输入投票上限和时间
然后输入项目的名称作为统计图的x轴,然后会程序后发送广播,局域网中端口在10001的调试助手都会收到消息,然后向端口6666(可自己修改)回送结果,程序会记录下ip地址避免重复投票,然后会筛选删除最高分和最低分给出平均分
这是投票成功后的反馈:如果重复投票也会收到反馈
这是输出的结果:
结语:
这个项目主要是为了练习我们的python基础多线程和python的网络编程能力,还有一点点的数据处理的相关知识,matplotlib是一个python底层的绘图库直接pip install 即可,欢迎交流!有GUI非常厉害的朋友能不能教教我把这个做成一个有界面的程序呢?有什么问题欢迎交流!共同学习,共同进步