一个python写的p2p 文件传输和数据交流的程序
- 使用了socket套接字的知识
- threading多线程的知识
本代码上传已上传github
地址:https://github.com/lokey-t/p2p-socket-
花了三天时间从零开始学习python写出来的的,要是有不规范的地方还请见谅
不得不说python确实挺容易的,上手很轻松
话不多说上源码
import socket
import os
import json
import hashlib
import threading
IP = socket.gethostbyname(socket.gethostname())#获取本机ip
PORT_TCP = 9999#默认端口号
PORT_UDP = 9998#默认端口号
LISTEN=3#tcp同时可连接的数量
def md5(path):
with open(path, 'rb') as file:
#必须以二进制的方式填入md5函数
file_byte = file.read()
fmd5 = hashlib.md5(file_byte).hexdigest()
#hexdigest()以16进制返回,默认返回的是tuple,包含各种信息
return fmd5
def send_message(ip,port):#tcp send
sk = socket.socket()
sk.connect((ip,port))
while True:
print("to",(ip,port))
msg=input("-->")
sk.sendall(msg.encode("utf-8"))
#tcp传输是sendall,udp不一样
if msg=="over":
print("---" , (ip, port) , "has been exit---")
break
sk.close()
def send_file(ip,port):
date = {}#创建字典
sk = socket.socket()
sk.connect((ip, port))
print("to", (ip, port))
path = input("-->input:path")
date['name'] = path.split('\\')[-1]#用split以\\为分割,并以-1取最后一个作为文件名
date['size'] = os.path.getsize(path)#os的获取文件大小
date['md5']= md5(path)
json_string = "000data00#"+json.dumps(date)#添加数据头,方便识别为数据传输,json.dump转换为json字符串
sk.sendall(json_string.encode('utf-8'))
sk.recv(1024)#收到确认信息,开始传输数据
size=0
while True:
with open(path, 'rb') as file:#二进制打开文件并只读
while size < date['size']:
fileDate = file.read(1024)
sk.send(fileDate)
size += 1024
if sk.recv(1024).decode("utf-8")=="successed":#等待接收方检查哈希值并决定是否重发
break
print("file is broken!! retarying")
sk.close()
def udp_send_message(ip,port):#用作广播
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#AF_INET:ipv4通信 SOCK_DGRAM:UDP数据报
address=(ip,port)
if str(ip)[-3:]=="255":
print("to",address)
msg=input("-->")
sendmsg="#broadcast"+msg
s.sendto(sendmsg.encode("utf-8"),address)
else:
print("to", address)
msg = input("-->")
sendmsg = "#message##" + msg
s.sendto(sendmsg.encode("utf-8"), address)
class udp_receive(threading.Thread):#继承threading用做多线程
def run(self):#创建线程后自动运行run(self)中的内容
while over_flag:#over flag 但由于中断,不可实现直接退出循环,使用“daemon=True”的方法强制退出
data,address=s.recvfrom(1024)
if data.decode("utf-8")[0:10]=="#broadcast":
print("Broadcast from",address,"-->",data.decode("utf-8")[10:])
continue
elif data.decode("utf-8")[0:10]=="#message##":
print("udp message from",address,"-->",data.decode("utf-8")[10:])
continue
print("!error udp data :"+"from",address,"-->"+data.decode("utf-8"))
# print("udp listen process exit successes!")
# msg="received0#"+data
# s.sendto(msg.encode("utf-8"),address)
class tcp_connect(threading.Thread):
my_lock = threading.Lock()#进程同步,对addr和conn可能会导致混乱,获取进程锁
def run(self):
self.my_lock.acquire()#请求锁
temp_addr=addr
temp_conn=conn
print("---", temp_addr, "has connected you---")
self.my_lock.release()#释放锁
rec = temp_conn.recv(1024).decode("utf-8")
if rec == "over":#收到over结束连接
print("---",temp_addr, "has been exit---")
# temp_conn.close()
elif rec[0:10] == "000data00#":#data标记头,开始准备接受数据
jsonobj = json.loads(rec[10:])#接收数据用json转换成字典
print("from", temp_addr, "receiveing data", jsonobj)
# os.mknod("./jsonobj['name']")
if not os.path.exists("./recv_file"):#创建目录
os.makedirs("./recv_file")
msg = 'ready'
temp_conn.sendall(msg.encode('utf-8'))#发送准备好接收的信号
size = 0#当前数据大小
sizeValue = int(jsonobj['size'])#接受数据的大小
while True:
with open("./recv_file/" + jsonobj['name'], 'wb+') as file:#二进制打开并且可读写,若已经有了,就直接覆盖
while size < sizeValue:
value = sizeValue - size
if value > 1024:
getdate = temp_conn.recv(1024)
else:
getdate = temp_conn.recv(value)
file.write(getdate)
file_length = len(getdate)#解决了一个很大的问题,每次接受的数据包并不一定为1024,1024只是最大大小,很容易文件没有接收完就被认定已经接受完,导致文件只有一半
size += file_length
print("test md5 number")
if md5(".//recv_file//" + jsonobj['name']) != jsonobj['md5']:#用提供的值对文件进行md5uibi
msg = "hash_wrong"
temp_conn.sendall(msg.encode("utf-8"))
print("file has broken!! retrying")
continue
else:
msg = "successed"
temp_conn.sendall(msg.encode("utf-8"))
print('receive successfully!')
break
print('over')
# continue
else:
print("from", temp_addr, "-->" + rec)#若接受的是其他数据就直直接显示
# continue
temp_conn.close()
class tcp_connect_control(threading.Thread):#用以控制tcp的监听,检测到连接就再开启一个新线程交流
def run(self):
global conn
global addr
while over_flag:
conn, addr = SK.accept()
tcp_process = tcp_connect()#用class tcp_connect创建对象
tcp_process.daemon=True#该参数设置这个进程如果退出后不检测进程是否结束,直接退出
tcp_process.start()
# print("tcp listen process exit successes!")
#开始程序
over_flag=1#已废弃,用daemon=True的方法强制退出
print("your ---ip:|",IP,"|---tcp port:|",PORT_TCP,"|---udp port:|",PORT_UDP,"|\n")
select=input("'1'=>change ip | '2'=>change tcp port | '3'=>chang udp port \n other to exit")
if select=="1" :
input_msg=input("input ip you want chang=>")
IP=input_msg
elif select=="2" :
input_msg=input("input tcp port you want chang=>")
PORT_TCP=int(input_msg)#port类型为int,转换类型
elif select=="3" :
input_msg=input("input udp port you want chang=>")
PORT_UDP=int(input_msg)
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#同上
s.bind((IP,PORT_UDP))#必须为tuple格式
udp_process=udp_receive()
udp_process.daemon=True#该参数设置这个程序徐如果退出后不检测进程是否结束,直接退出
udp_process.start()
print("Successfully created udp listen")
SK=socket.socket()
SK.bind((IP,PORT_TCP))
SK.listen(LISTEN)
conn=socket.socket()#新建全局变量,用于进程中的函数传递,传递的是已连接的对象,用于tcp的进程
addr=(1,1)#全局变量,初始化为tuple类型,用于进程中传递参数,传递的是已经连接的对象的ip和端口号,用于tcp的进程
tcp_connect_control_procss=tcp_connect_control()
tcp_connect_control_procss.daemon=True#该参数设置这个程序徐如果退出后不检测进程是否结束,直接退出
tcp_connect_control_procss.start()
print("Successfully created tcp listen")
while True:
select=input("input '1'=>udp_broadcast '2'=>tcp_send_file '3'=>tcp_send_message '0'=>exit\n")
if select=="1" or select=="2" or select=="3":
input_ip = input("input ip you want to send=>")
input_port = input("input port you want to send=>")
elif select=="0":
over_flag = 0
print("waiting process exit")
break
else:
continue
int_port=int(input_port)
if select == "1":
udp_send_message(input_ip, int_port)
elif select == "2":
send_file(input_ip, int_port)
elif select == "3":
send_message(input_ip, int_port)
exit()
转载请标明出处