用socket库来实现一个文件下载和聊天软件

本文会主要用到两个库,一个是socket,还有一个是threading库.socket库用来实现文件传输,threading库用来实现多线程,因为我们将会把接受/发送分开写.

本文我们主要着重讲服务器端该怎么写(因为客户端可以几乎照抄代码)

理论基础:

        客户端:通常主动连接服务端,连接成功后可以进行交互

        服务端:绑定自己的ip地址,设置监听状态,等待客户端连接

        缓冲区:当一个TCP套接字创建成功后,操作系统会给每个套接字各自分别独立的分配两个缓冲区.一个是接收缓冲区,一个是发送缓冲区.接收缓冲区用于接收信息,发送缓冲区用于发送信息.知道你的电脑显示网速的时候嘛,通常有一个向上的箭头和一个向下的箭头..就是这两个在传输的数据

        字节流(bytes):在网络通信中,信息都是以字节流的形式传输的..通俗的说就是都是以二进制的形式传递的.因此,传输文件还好,直接传输,无需解码..但是文本就不行了,文本的话是需要你先以某种编码方式进行编码成二进制发送,然后接收方在以相同的解码方式进行解码.

socket库主要方法介绍:

        .recv()方法:这个方法主要是用于从缓冲区内部读取数据..内部的参数类型指定为int.并且代表的大小是字节..比如说.recv(1024),表示将尽可能的从缓冲区内读取1024字节(即1kb)的数据..因而假如此时缓冲区内只有300字节的数据,那么recv(1024)将只读取这300字节数据并返回..他的参数代表他可以读取的信息大小的极限...

 

问题与解决:

        缓冲区的数据特点:缓冲区是按照发送的顺序,直接堆叠到其内部的..因此比如说,你发送了一条消息,然后又发送了一个文件,他们都是二进制,都一块堆叠在缓冲区,一旦取出数据的时候,取的多了,文件数据缺失,导致文件打不开...或者数据信息数据去少了又会发生一系列问题..

        解决办法:

1.由于"+++"和"---"的二进制大小都是3个字节,那就好办了.先recv(3)判断类型,然后根据类型调用相应的自定义函数...

2.end_mark:由于你也不知道什么时候要读取结束,因而我们要加入一个end_mark标志,到时候通过if语句来进行判断何时结束读取

3.发文件的间隔符"?*?":由于我们要获取的 文件名 和下面的 文件主要内容 都是二进制编码,为了防止多读或少读取,我们设一个分隔符.到时候以分隔符为标志进行分割.

自定义函数:

(用到的库有socket,threading,os)

一.内部功能

        1.架设服务器功能

def start_server():
    global tcp_s
    global new_socket
    global client_data

    tcp_s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#创建TCP套接字
    tcp_s.bind(("", 6666))#绑定本机的IP和端口
    tcp_s.listen(1)#开启监听模式,参数为1说明最大可接受1人连接,并且使得这个套接字尽可以用于接收连接,不可以进行通信
    print("服务器已架设成功,等待连接.......")
    new_socket,client_data=tcp_s.accept()#将接受的连接返回一个元组,元组的第一个元素是与客户端连接成功后,返回的新套接字,用于通信传输
    print("IP:{};port:{}连接成功".format(client_data[0],client_data[1]))

     2.判断接收的数据的类型

        

def judge_recv_type():
    judge_data=new_socket.recv(3).decode('utf-8')
    if judge_data=='+++':
        return 'file'
    elif judge_data=='---':
        return 'info'
    elif not judge_data:
        print("客户端{}已断开连接".format(client_data[0]))
        return False
    else:
        print("无法判断类型...\n")

3.接收的文件存储到何处?

def make_download_dir():
    try:
        os.mkdir('./download_file')
    except:
        pass

二.接收传输功能

         1.接收信息

def recv_info():#完善
    while True:#这里的while True不是来多次执行功能的,是假如信息量很大,用来分片取出整个信息的
        info_data=new_socket.recv(1024).decode('utf-8')#解码
        #如果发现end_mark(即#END#),则停止继续读取
        if '#END#' in info_data:
            print("\n来自ip为{}的消息:{}\n".format(client_data[0], info_data[:-5]))#这里的[:-5]表示不会读取#END#到信息中
            break

        2.接收文件

def recv_file():#完善
    #接收文件名
    first_file_data=new_socket.recv(500)
    '''500字节对于一个文件名肯定多了,我们来处理下?*?的情况'''
    file_list_beta=first_file_data.split(b'?*?')
    file_name=file_list_beta[0].decode('utf-8')#以分隔符为标志分割后前半段为文件名,要解码
    file_data=file_list_beta[1]#这后半段其实是文件内容,保留着要写到文件里

    with open('./download_file/{}'.format(file_name), 'ab') as file:
        file.write(file_data)#将刚刚的那后半段写入文件
        while True:
            file_data_part=new_socket.recv(1024*1024)#1Mb,1Mb的存入文件夹
            if b'#END#' in file_data_part:
                file.write(file_data_part[:-5])#读取到end_mark时,先将文件数据去掉end_mark后写入文件,之后break退出循环
                break
            file.write(file_data_part)

    file_address=os.getcwd()+'\\download_file'#获取当前位置
    print("文件{}接收成功,存储在{}文件夹下".format(file_name,file_address))

        3.发送信息

def send_info():
    while True:
        w_info=input("请输入您想要发送的信息:").encode('utf-8')
        if w_info=='back'.encode('utf-8'):
            break#若不想再用发送信息的功能时,输入back退出循环
        type_mark="---".encode('utf-8')
        end_mark="#END#".encode('utf-8')
        #先发送信息种类标识type_mark
        new_socket.send(type_mark)
        # 再发送w_info
        new_socket.send(w_info)
        #最后发送end_mark
        new_socket.send(end_mark)

        4.发送文件

def send_file():
    while True:
        address=input("\n请输入要发送的文件的绝对位置:")
        address=os.path.normpath(r"{}".format(address))#用于规范化这个address
        if address=='back':
            break
        #找出文件名
        file_name=(address.split('\\')[-1]).encode('utf-8')
        #type文件类型
        type_mark='+++'.encode('utf-8')
        #先发送type_mark
        new_socket.send(type_mark)
        #再发送文件名以及间隔符""?*?
        new_socket.send(file_name+b"?*?")
        try:
            with open(address,'rb') as file:
                while True:
                    file_data=file.read(1024*1024)
                    if not file_data:
                        new_socket.send(b'#END#')
                        break
                    new_socket.send(file_data)
            print("文件{}发送成功".format(file_name))
        except:
            print("文件地址错误或文件不存在,请按照标准格式输入(例:D:\ok\hell.jpg)")

易错点:是windows复制的地址跟python的转义字符会有所冲突.比如说C:\user\lala.jpg这个地址中\u在python中是有意义的,因此会报错.所以我们要处理一下这种情况.用os.path.normpath()函数来处理

三.接受功能线程创建

(因为我们肯定是希望只要对方发过来信息我们就可以随时收到,而不是要只有点开接受功能,才可以接受数据,这样可太拉胯了)

def Recv_fun_thread():
    while True:
        data_type=judge_recv_type()#用于判断接受的东西是个什么类型
        if data_type=='info':
            recv_info()
        elif data_type=='file':
            recv_file()
        elif not data_type:
            break
        else:
            print("*****WRONG*****\n")
            time.sleep(3)
        if exit_exit==True:
            break

四.应用内部逻辑main

def main():
    global exit_exit
    exit_exit=False
    make_download_dir()#检测接受文件的文件夹是否存在
    start_server()#开启服务器
    #开启接受功能线程
    T_recv=threading.Thread(target=T_recv_thread)
    T_recv.start()

    print("功能代号:\n1.发送消息\n2.发送文件\n")
    while True:
        fun=input("请输入您想要的功能(输入代号):")
        if fun=='1':
            send_info()
        elif fun=='2':
            send_file()
        elif fun=='back':
            break
        else:
            print("***WRONG***")
    exit_exit=True#这个是确保方便想要退出应用,但由于.join()的阻塞
    T_recv.join()
    #不要忘记关闭套接字
    new_socket.close()
    tcp_s.close()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值