飞鸽传书-发送文件

main.py

import multiprocessing
import threading
import socket
import time
import FeiQCoreData
import FeiQRecv
import FeiQSend
import FeiQTcp


def create_udp_socket():
    """创建udp套接字"""
    FeiQCoreData.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 绑定端口
    FeiQCoreData.udp_socket.bind(("", FeiQCoreData.feiq_port))

    # 设置允许广播
    FeiQCoreData.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)


def print_all_online_users():
    """显示所有现在用户列表"""
    # print(FeiQCoreData.user_list)
    for user_info in FeiQCoreData.user_list:
        print(user_info)


def print_menu():
    """显示飞鸽传书的功能"""
    print("     飞鸽传书v1.0")
    print("1:上线广播")
    print("2:下线广播")
    print("3:向指定ip发送消息")
    print("4:显示所有在线的用户列表")
    print("5:向指定ip发送文件")
    print("0:退出")


def main():
    # ------新添加------
    # 创建一个队列Queue
    FeiQCoreData.file_info_queue = multiprocessing.Queue()

    # ------新添加------
    # 创建一个进程,用来完成tcp相关的功能
    tcp_process = multiprocessing.Process(target=FeiQTcp.tcp_main, args=(FeiQCoreData.file_info_queue,))
    tcp_process.start()

    # 创建套接字
    create_udp_socket()

    # 创建另外一个线程,循环接收udp数据
    recv_msg_thread = threading.Thread(target=FeiQRecv.recv_msg)
    recv_msg_thread.start()

    while True:

        print_menu()
        command_num = input("请输入要进行的操作:")
        if command_num == "1":
            # 发送上线提醒
            FeiQSend.send_broadcast_online_msg()
        elif command_num == "2":
            # 发送下线提醒
            FeiQSend.send_broadcast_offline_msg()
        elif command_num == "3":
            # 向指定ip发送消息
            FeiQSend.send_msg_2_ip()
        elif command_num == "4":
            # 显示在线用户列表
            print_all_online_users()
        elif command_num == "5":
            # 向指定ip发送文件
            FeiQSend.send_file_msg_2_ip()
        elif command_num == "0":
            FeiQSend.send_broadcast_offline_msg()
            # 关闭套接字
            FeiQCoreData.udp_socket.close()
            exit()


if __name__ == "__main__":
    main()

FeiQSend.py

import FeiQCoreData
import time
import os


def build_msg(command_num, option_data=""):
    """组装需要发送的消息"""
    # -------新添加---------
    # 将包编号存储到全局变量中,当发送是文件消息的时候,这个包编号需要
    FeiQCoreData.packet_id = int(time.time())
    msg = "%d:%d:%s:%s:%d:%s" % (FeiQCoreData.feiq_version, FeiQCoreData.packet_id,
                                 FeiQCoreData.feiq_user_name, FeiQCoreData.feiq_host_name,
                                 command_num, option_data)
    return msg


def build_file_msg(file_name):
    """构建文件消息"""
    try:
        # 获取文件大小
        file_size = os.path.getsize(file_name)
        # 获取文件的修改时间
        file_ctime = os.path.getctime(file_name)
    except:
        print("(%s)文件不存在,不能发送...." % file_name)
    else:
        # 文件序号:文件名:文件大小:修改时间:文件的属性
        option_str = "%d:%s:%x:%x:%x:" % (0, file_name, file_size, int(file_ctime), FeiQCoreData.IPMSG_FILE_REGULAR)
        command_num = FeiQCoreData.IPMSG_SENDMSG | FeiQCoreData.IPMSG_FILEATTACHOPT
        file_str = "\0" + option_str
        return build_msg(command_num, file_str)


def send_msg(send_data, dest_ip):
    FeiQCoreData.udp_socket.sendto(send_data.encode("gbk"), (dest_ip, FeiQCoreData.feiq_port))


def send_broadcast_online_msg():
    """发送上线提醒"""
    online_msg = build_msg(FeiQCoreData.IPMSG_BR_ENTRY, FeiQCoreData.feiq_user_name)
    send_msg(online_msg, FeiQCoreData.broadcast_ip)


def send_broadcast_offline_msg():
    """发送下线提醒"""
    offline_msg = build_msg(FeiQCoreData.IPMSG_BR_EXIT)
    send_msg(offline_msg, FeiQCoreData.broadcast_ip)


def send_msg_2_ip():
    """向指定的ip发送飞秋数据"""
    dest_ip = input("请输入对方的ip(输入0显示用户列表):")
    if dest_ip == "0":
        # 显示在线用户列表,然后从中选择一个
        print("="*30)
        for i, user_info in enumerate(FeiQCoreData.user_list):
            print(i, user_info)
        print("="*30)
        num = int(input("请输入要序号:"))
        dest_ip = FeiQCoreData.user_list[num]['ip']

    send_data = input("请输入要发送的数据内容:")
    if dest_ip and send_data:
        # chat_msg = "%d:%d:%s:%s:%d:%s" % (feiq_version, int(time.time()), feiq_user_name, feiq_host_name,
        #                                   IPMSG_SENDMSG, send_data)
        chat_msg = build_msg(FeiQCoreData.IPMSG_SENDMSG, send_data)
        send_msg(chat_msg, dest_ip)


def send_file_msg_2_ip():
    """向指定ip发送文件消息"""

    dest_ip = input("请输入对方的ip(输入0显示用户列表):")
    if dest_ip == "0":
        # 显示在线用户列表,然后从中选择一个
        print("="*30)
        for i, user_info in enumerate(FeiQCoreData.user_list):
            print(i, user_info)
        print("="*30)
        num = int(input("请输入序号:"))
        dest_ip = FeiQCoreData.user_list[num]['ip']

    file_name = input("请输入要发送的文件名(输入0显示当前路径下文件名):")
    if file_name == "0":
        print("="*30)
        file_name_list = os.listdir()
        for i, file_name_temp in enumerate(file_name_list):
            print(i, file_name_temp)
        print("="*30)
        num = int(input("请输入序号:"))
        file_name = file_name_list[num]

    if dest_ip and file_name:
        file_msg = build_file_msg(file_name)
        send_msg(file_msg, dest_ip)


        # -------新添加---------
        # 组织数据将其发送到子进程中,告知这个文件的名字、包编号、序号等
        file_info = dict()
        file_info['packet_id'] = FeiQCoreData.packet_id
        file_info['file_id'] = 0
        file_info['file_name'] = file_name
        FeiQCoreData.file_info_queue.put(file_info)

FeiQTcp.py


import threading
import time
import socket

tcp_server_socket = None  # 用来存储tcp服务器套接字
# -------新添加---------
g_file_infos = list()  # 用来存储需要发送的文件的信息


def deal_feiq_data(recv_data):
    """处理接收到的飞秋数据"""
    # b'1_lbt80_0#128#000C29770BAB#0#0#0#4000#9:1501516970:Administrator:DONGGE-32E5DBE1:96:597ee52c:0:0:'
    recv_data = recv_data.decode("gbk", errors="ignore")
    feiq_data_list = recv_data.split(":", 5)

    feiq_data = dict()
    feiq_data['version'] = feiq_data_list[0]
    feiq_data['packet_id'] = feiq_data_list[1]
    feiq_data['user_name'] = feiq_data_list[2]
    feiq_data['host_name'] = feiq_data_list[3]
    feiq_data['command_num'] = feiq_data_list[4]
    feiq_data['option'] = feiq_data_list[5]
    return feiq_data


def get_file_info(option):
    """提取文件信息"""
    file_info_list = option.split(":", 3)
    packet_id = file_info_list[0]
    file_id = file_info_list[1]
    # int("0x131234", 16)---->将一个16进制数的字符串 转换为 整数
    return int(packet_id, 16), int(file_id)  # 用udp发送文件消息时的包编号,文件序号


# -------新添加---------
def send_file(client_socket):
    """发送文件给客户端"""
    request_data = client_socket.recv(1024)
    # print(request_data)
    feiq_data = deal_feiq_data(request_data)
    packet_id, file_id = get_file_info(feiq_data['option'])
    print("对方请求下载文件的包编号是:%d, 序号是:%d" % (packet_id, file_id))

    for file_info_temp in g_file_infos:
        if file_info_temp['packet_id'] == packet_id and file_info_temp['file_id'] == file_id:
            # 如果包编号和文件序号都相同,那么就发送
            file_name = file_info_temp['file_name']
            try:
                f = open(file_name, "rb")
                while True:
                    content = f.read(1024)
                    if content:
                        client_socket.send(content)
                    else:
                        break
                f.close()
            except Exception as ret:
                print("发送文件异常...%s" % ret)
            else:
                print("发送文件成功")
                # 发送成功后从列表中删除这个下载文件的信息
                g_file_infos.remove(file_info_temp)
            break
    else:
        print("没有找到要发送的文件...")

    client_socket.close()


# -------新添加---------
def get_file_info_from_queue(file_info_queue):
    """从Queue中接收需要发送的文件消息"""
    while True:
        file_info = file_info_queue.get()
        print("发送新的文件,", file_info)
        g_file_infos.append(file_info)
        print("总共需要发送的文件信息如下:")
        for i, file_info_temp in enumerate(g_file_infos):
            print(i, file_info_temp)


def tcp_main(file_info_queue):
    """用来完成tcp相关的功能控制"""
    global tcp_server_socket

    # -------新添加---------
    # 创建一个子线程,专门用来从Queue中接收来自主进程中的数据
    recv_queue_thread = threading.Thread(target=get_file_info_from_queue, args=(file_info_queue,))
    recv_queue_thread.start()

    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.bind(("", 2425))
    tcp_server_socket.listen(128)
    while True:
        # print("等待新客户的链接。。。。", end="")
        client_socket, client_addr = tcp_server_socket.accept()
        send_file_thread = threading.Thread(target=send_file, args=(client_socket,))
        send_file_thread.start()

if __name__ == "__main__":
    tcp_main()

FeiQCoreData.py

udp_socket = None  # 保存udp套接字
feiq_version = 1  # 飞秋的版本
feiq_user_name = "dong-test"  # 用户名
feiq_host_name = "ubuntu-64-1604"  # 主机名字
broadcast_ip = "255.255.255.255"  # 广播ip
feiq_port = 2425  # 飞鸽传书的端口
file_info_queue = None  # 用来完成进程间通信  # ------新添加------
packet_id = 0  # 用来存储发送的数据包的包编号  # ------新添加------

# 飞秋command
IPMSG_BR_ENTRY = 0x00000001
IPMSG_BR_EXIT = 0x00000002
IPMSG_SENDMSG = 0x00000020  # 表示 发送消息
IPMSG_ANSENTRY = 0x00000003
IPMSG_RECVMSG = 0x00000021  # 当告知对方 已收到消息

# option for all command
IPMSG_FILEATTACHOPT = 0x00200000  # 文件消息

# file types for fileattach command
IPMSG_FILE_REGULAR = 0x00000001  # 普通文件

user_list = list()  # 保存在线用户列

FeiQRecv.py(未修改)

import FeiQCoreData
import FeiQSend

def deal_feiq_data(recv_data):
    """处理接收到的飞秋数据"""
    # b'1_lbt80_0#128#000C29770BAB#0#0#0#4000#9:1501336422:Administrator:DONGGE-32E5DBE1:288:123123123\x00\x00'
    recv_data = recv_data.decode("gbk", errors="ignore")
    feiq_data_list = recv_data.split(":", 5)

    feiq_data = dict()
    feiq_data['version'] = feiq_data_list[0]
    feiq_data['packet_id'] = feiq_data_list[1]
    feiq_data['user_name'] = feiq_data_list[2]
    feiq_data['host_name'] = feiq_data_list[3]
    feiq_data['command_num'] = feiq_data_list[4]
    feiq_data['option'] = feiq_data_list[5]
    return feiq_data


def deal_command_option_num(command_num):
    """提起命令字中的命令以及选项"""
    command = int(command_num) & 0x000000ff
    command_option = int(command_num) * 0xffffff00
    return command, command_option


def judge_and_add_online_user(user_name, host_name, dest_ip):
    """判断这个用户是否已经存在列表中,然后添加"""
    for user_info in FeiQCoreData.user_list:
        if user_info['ip'] == dest_ip:
            break
    else:
        new_online_user = dict()
        new_online_user['ip'] = dest_ip
        new_online_user['user_name'] = user_name
        new_online_user['host_name'] = host_name
        FeiQCoreData.user_list.append(new_online_user)

def judge_and_del_online_user(dest_ip):
    """用户下线,删除"""
    for user_info in FeiQCoreData.user_list:
        if user_info['ip'] == dest_ip:
            FeiQCoreData.user_list.remove(user_info)
            break


def recv_msg():
    """接收消息"""
    while True:
        recv_data, dest_addr = FeiQCoreData.udp_socket.recvfrom(1024)
        # print("(处理之前的数据)%s>>>%s" % (dest_addr, recv_data))
        feiq_data = deal_feiq_data(recv_data)
        # print("(处理之后的数据)", feiq_data)
        command, command_option = deal_command_option_num(feiq_data['command_num'])
        # print("(提取到的命令以及命令选项为:)", command, command_option)
        if command == FeiQCoreData.IPMSG_BR_ENTRY:
            # 有用户上线
            print("%s上线" % feiq_data['option'])
            find_post = feiq_data['option'].find("\0")
            if find_post != -1:
                user_name = feiq_data['option'][:find_post]
            else:
                user_name = feiq_data['option']
            judge_and_add_online_user(user_name, feiq_data['host_name'], dest_addr[0])

            # 通报给对方 我已在线
            answer_online_msg = FeiQSend.build_msg(FeiQCoreData.IPMSG_ANSENTRY)
            FeiQSend.send_msg(answer_online_msg, dest_addr[0])

        elif command == FeiQCoreData.IPMSG_BR_EXIT:
            # 有用户下线
            print("%s下线" % feiq_data['user_name'])
            judge_and_del_online_user(dest_addr[0])

        elif command == FeiQCoreData.IPMSG_ANSENTRY:
            # 对方通报在线
            print("%s已经在线" % feiq_data['user_name'])
            judge_and_add_online_user(feiq_data['option'][:feiq_data['option'].find("\0")], feiq_data['host_name'], dest_addr[0])

        elif command == FeiQCoreData.IPMSG_SENDMSG:
            # 接收到消息
            print("收到新消息:%s" % feiq_data['option'])

            # 给对方发送 消息确认(告知对方已经收到了)
            recv_ok_msg = FeiQSend.build_msg(FeiQCoreData.IPMSG_RECVMSG)
            FeiQSend.send_msg(recv_ok_msg, dest_addr[0])