飞鸽传书-完整版

main.py

import socket
import time
import threading
import multiprocessing
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_online_user():
    """打印在线用户列表"""
    # print(FeiQCoreData.user_list)
    for i, user_info in enumerate(FeiQCoreData.user_list):
        print(i, user_info)

def print_all_waiting_files():
    """显示可以下载的文件"""
    for i, file_info in enumerate(FeiQCoreData.download_file_list):
        print(i, file_info)


def print_menu():
    """显示飞鸽传书的功能"""
    print("     飞鸽传书v1.0")
    print("1:上线广播")
    print("2:下线广播")
    print("3:给指定的ip发送数据")
    print("4:显示在线用户信息")
    print("5:给指定的ip发送文件")
    print("6:显示可以下载文件")
    print("7:下载文件")
    print("0:退出")


def main():

    FeiQCoreData.file_queue = multiprocessing.Queue()

    tcp_process = multiprocessing.Process(target=FeiQTcp.tcp_main, args=(FeiQCoreData.file_queue,))
    tcp_process.start()

    # 创建套接字
    create_udp_socket()

    # 创建一个子线程,接收数据
    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":
            # 发送消息
            FeiQSend.send_chat_msg()
        elif command_num == "4":
            # 显示在线用户信息
            print_online_user()
        elif command_num == "5":
            # 给指定的ip发送文件
            FeiQSend.send_file_msg()
        elif command_num == "6":
            # 显示可以下载的文件
            print_all_waiting_files()
        elif command_num == "7":
            # 下载文件
            FeiQSend.download_file()
        elif command_num == "0":
            FeiQSend.send_broadcast_offline_msg()
            # 关闭套接字
            FeiQCoreData.udp_socket.close()
            exit()


if __name__ == "__main__":
    main()

FeiQRecv.py

import FeiQCoreData
import FeiQSend


def deal_with_recv_msg(recv_msg):
    """处理接收到的数据"""
    # 解码
    recv_msg = recv_msg.decode("gbk", errors="ignore")

    # 切割
    feiq_info_list = recv_msg.split(":", 5)

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

    return feiq_data


def get_command_option(command_num):
    """提取命令以及命令选项"""
    command = int(command_num) & 0x000000ff
    option = int(command_num) & 0xffffff00

    return command, option


def recv_msg():
    """接收数据"""
    while True:
        recv_data, dest_addr = FeiQCoreData.udp_socket.recvfrom(1024)
        # print("(接收到的数据)%s>>>%s" % (str(dest_addr), recv_data.decode("gbk")))

        feiq_data = deal_with_recv_msg(recv_data)
        # print("(处理之后的数据)", feiq_data)

        # 0000 0001
        # 0000 0010
        # 0000 0100
        #
        # 0000 0001, 0000 0010, 0000 0100
        #
        # 0000 0111

        command, option = get_command_option(feiq_data['command_num'])
        # print(command, option)

        if command == FeiQCoreData.IPMSG_BR_ENTRY:
            # 用户上线
            print("%s上线" % feiq_data['user_name'])


            find_position = feiq_data['option'].find("\0")
            if find_position != -1:
                user_name = feiq_data['option'][:find_position]
            else:
                user_name = feiq_data['option']

            new_user_info = dict()
            new_user_info['ip'] = dest_addr[0]
            new_user_info['user_name'] = user_name

            if new_user_info not in FeiQCoreData.user_list:
                FeiQCoreData.user_list.append(new_user_info)

            # 通报给对方 我已在线
            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'])

            for user_info in FeiQCoreData.user_list:
                if dest_addr[0] == user_info['ip']:
                    FeiQCoreData.user_list.remove(user_info)
                    break

        elif command == FeiQCoreData.IPMSG_ANSENTRY:
            # 其他用户通报在线
            print("%s已经在线" % feiq_data['user_name'])

            find_position = feiq_data['option'].find("\0")
            if find_position != -1:
                user_name = feiq_data['option'][:find_position]
            else:
                user_name = feiq_data['option']

            new_user_info = dict()
            new_user_info['ip'] = dest_addr[0]
            new_user_info['user_name'] = user_name
            if new_user_info not in FeiQCoreData.user_list:
                FeiQCoreData.user_list.append(new_user_info)

        elif command == FeiQCoreData.IPMSG_SENDMSG:
            # 其他用户发送过来新的消息

            # 判断如果发送过来的是文件消息
            if option & 0x00f00000 == FeiQCoreData.IPMSG_FILEATTACHOPT:
                # 飞鸽传书中的选项为  \0 文件序号:文件名:文件大小:文件修改时间:文件类型:
                file_info_msg = dict()
                file_info_list = feiq_data['option'][1:].split(":", 5)
                file_info_msg['packet_id'] = int(feiq_data['packet_id'])
                file_info_msg['file_id'] = int(file_info_list[0])
                file_info_msg['file_name'] = file_info_list[1]
                file_info_msg['file_size'] = int(file_info_list[2], 16)
                file_info_msg['dest_ip'] = dest_addr[0]
                FeiQCoreData.download_file_list.append(file_info_msg)

            else:
                print("%s(%s)>>>%s" % (feiq_data['user_name'], str(dest_addr), feiq_data['option']))

            # 告知对方已经接收到数据
            recv_ok_msg = FeiQSend.build_msg(FeiQCoreData.IPMSG_RECVMSG)
            FeiQSend.send_msg(recv_ok_msg, dest_addr[0])

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  # 飞鸽传书的端口

# 飞秋command
IPMSG_BR_ENTRY = 0x00000001  # 上线
IPMSG_BR_EXIT = 0x00000002  # 下线
IPMSG_SENDMSG = 0x00000020  # 发送 消息
IPMSG_ANSENTRY = 0x00000003  # 应答在线
IPMSG_RECVMSG = 0x00000021  # 告知对方 已收到消息
IPMSG_GETFILEDATA = 0x00000060  # 表示下载文件 tcp发送
IPMSG_FILEATTACHOPT = 0x00200000  # 表示文件消息
IPMSG_FILE_REGULAR = 0x00000001  # 表示普通文件

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

send_file_list = list()  # 用来存储需要发送的文件

download_file_list = list()  # 用来保存需要现在的文件


file_queue = None
packet_id = 0

FeiQSend.py

import FeiQCoreData
import time
import os


def build_msg(command_num, option=""):
    """构建飞鸽传书的数据包"""

    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)

    return msg


def send_msg(msg, dest_ip):
    """发送飞鸽传书数据"""
    FeiQCoreData.udp_socket.sendto(msg.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, FeiQCoreData.feiq_user_name)
    send_msg(offline_msg, FeiQCoreData.broadcast_ip)


def send_chat_msg():
    """发送聊天信息"""

    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)

        # 从中取一个用户的ip
        try:
            num = int(input("请输入用户对应的序号:"))
        except:
            print("输入有误...")
            return
        else:
            dest_ip = FeiQCoreData.user_list[num]['ip']

    send_data = input("请输入要发送的内容:")

    # 判断是否有目的ip以及要发送的内容,有则发送
    if dest_ip and send_data:
        chat_msg = build_msg(FeiQCoreData.IPMSG_SENDMSG, send_data)
        send_msg(chat_msg, dest_ip)


def send_file_msg():
    """给指定的ip发送文件"""



    # 1:123123:dongge:ubuntu:文件消息命令字:消息内容(可以没有) \0 0:hello.py:123:12123:文件类型:

    # 获取对方的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)

        # 从中取一个用户的ip
        try:
            num = int(input("请输入用户对应的序号:"))
        except:
            print("输入有误...")
            return
        else:
            dest_ip = FeiQCoreData.user_list[num]['ip']

    # 获取要发送的文件名
    file_name = input("请输入要发送的文件名:")

    # 判断是否有目的ip以及要发送的内容,有则发送
    if dest_ip and file_name:

        try:
            file_size = os.path.getsize(file_name)
            file_ctime = os.path.getctime(file_name)
        except:
            print("没有此文件。。。。")
        else:
            # 文件序号:文件名:文件大小:文件修改时间:文件类型:
            option = "%d:%s:%x:%x:%x:" % (0, file_name, file_size, int(file_ctime), FeiQCoreData.IPMSG_FILE_REGULAR)

            option = "\0" + option


            file_msg = build_msg(FeiQCoreData.IPMSG_SENDMSG|FeiQCoreData.IPMSG_FILEATTACHOPT, option)
            send_msg(file_msg, dest_ip)


            # 向子进程中发送包编号/文件序号/文件名
            send_file_info = dict()
            send_file_info['packet_id'] = FeiQCoreData.packet_id
            send_file_info['file_id'] = 0
            send_file_info['file_name'] = file_name

            queue_info = dict()
            queue_info['type'] = "send_file"  # 添加一个key-value用来标记类型
            queue_info['data'] = send_file_info

            FeiQCoreData.file_queue.put(queue_info)


def download_file():
    """下载文件"""

    for i, file_info in enumerate(FeiQCoreData.download_file_list):
        print(i, file_info)

    try:
        num = int(input("请输入要下载的文件序号:"))
    except:
        print("输入数据有误....")
        return
    else:
        file_info = FeiQCoreData.download_file_list[num]

        queue_info = dict()
        queue_info['type'] = "download_file"  # 添加一个key-value用来标记类型
        queue_info['data'] = file_info

        # 发送到Queue中
        FeiQCoreData.file_queue.put(queue_info)

FeiQTcp.py

import socket
import threading
import FeiQCoreData
import FeiQSend


def deal_with_recv_msg(recv_msg):
    """处理接收到的数据"""
    # 解码
    recv_msg = recv_msg.decode("gbk", errors="ignore")

    # 切割
    feiq_info_list = recv_msg.split(":", 5)

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

    return feiq_data


def deal_with_file_option(option_data):
    # 59819fd7:0:0:
    file_info_list = option_data.split(":", 3)
    return int(file_info_list[0], 16), int(file_info_list[1])


def send_file(client_socket):
    request_info = client_socket.recv(1024)

    # 1_lbt80_0#128#000C29770BAB#0#0#0#4000#9:1501684876:Administrator:DONGGE-32E5DBE1:96:59819fd7:0:0:

    # print(request_info)

    feiq_data = deal_with_recv_msg(request_info)

    packet_id, file_id = deal_with_file_option(feiq_data['option'])  # 59819fd7:0:0:

    print("下载的文件包编号是:%d, 文件序号:%d" % (packet_id, file_id))

    file_name = ""

    for file_info in FeiQCoreData.send_file_list:
        if file_info['packet_id'] == packet_id and file_info['file_id'] == file_id:
            file_name = file_info['file_name']
            break
    else:
        print("对方需要下载的文件不存在")

    f = open(file_name, "rb")
    while True:
        content = f.read(1024)
        if content:
            client_socket.send(content)
        else:
            break

    f.close()

    client_socket.close()


def download_file(file_info):
    """下载文件"""

    # 创建tcp套接字
    client_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 构建需要发送的请求数据
    option = "%x:%x:%x" % (file_info['packet_id'], file_info['file_id'], 0)
    request_info = FeiQSend.build_msg(FeiQCoreData.IPMSG_GETFILEDATA, option)

    # 链接tcp服务器
    client_tcp_socket.connect((file_info['dest_ip'], FeiQCoreData.feiq_port))

    # 发送请求
    client_tcp_socket.send(request_info.encode("gbk"))

    # 新建文件,等待数据到来后,写入到文件中
    f = open(file_info['file_name'] ,"wb")  # 因为接收到的数据是二进制,需要使用wb

    recv_data_length = 0
    while True:
        recv_data = client_tcp_socket.recv(1024)
        if recv_data:
            f.write(recv_data)
        else:
            break

        # 如果接收到的数据超过了文件大小,那么也结束
        recv_data_length += len(recv_data)
        if recv_data_length >= file_info['file_size']:
            break 

    f.close()
    print("下载(%s)ok" % file_info['file_name'])


def get_file_msg_from_queue(file_queue):
    """从Queue 获取需要下载或者发送的文件信息"""
    while True:
        data_info = file_queue.get()
        if data_info['type'] == "download_file":
            # 下载文件请求
            print("需要下载。。。。", data_info['data'])

            # 使用tcp下载文件
            download_file(data_info['data'])

        elif data_info['type'] == "send_file":
            # 发送文件请求
            print("发送文件......", data_info['data'])

            FeiQCoreData.send_file_list.append(data_info['data'])


def tcp_main(file_queue):

    # 创建一个子线程 从queue中接收数据
    get_file_thread = threading.Thread(target=get_file_msg_from_queue, args=(file_queue,))
    get_file_thread.start()

    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.bind(("", FeiQCoreData.feiq_port))
    tcp_server_socket.listen(128)

    while True:

        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()