python网络编程--Socket通信

python网络编程(一)Socket介绍

一:Socket简介

  • 套接字起源于20世纪70年代加利福尼亚大学伯克利分校版本的Unix,即人们所说的BSD Unix。因此,有时人们也把套接字称为“伯克利套接字"或"BSD套接字”。一开始,套接字被设计用在同 -台主机上多个应用程序之间的通讯
  • BSD Socket接口是TCP/IP网络的API
  • 在Linux,Unix和Windows均实现这个接口.BSD Socket的是目前开发网络应用主要接口.绝大部分网络应用均可Socket来开发
  • 一个Socket队列是IP应用的基本单位.两个机器通讯相当于两个机器的两个Socket互相通讯的过程
  • Socket 的本意是插座.每一个激活的socket可以看成是一个跟本地某个IP端口绑定的IP包队列
  • 接口设计者最先是将接口放在Unix操作系统里面的。因此一个激活的Socket被设计成特殊的I/O文件, Socket也是一种文件描述符。 .因此操作类似对一个普通文件操作
  • 套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的

基于文件类型的套接字家族
套接字家族的名字: AF_ _UNIX
unix-切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族
套接字家族的名字: AF_ INET
(还有AF_ INET6被用于ipv6,还有一些其他的地址家族, 不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF INET是使用最广泛的-个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_ INET)

二:Socket基本函数

1、创建套接字

socket(socket.AF_INET,socket.SOCK_STREAM)

  • socket.AF_INET:表示是基于网络的套接字家族,因而可以允许在远程 主机之间通信
  • socket.SOCK_STREAM:表示流式模块,基于tcp协议,这样会提供按顺序的,可靠,双向,面向连接的比特流

2、设置端口重用

setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
比如,当我们把程序关闭了,但是操作系统还没有释放端口,再次启动就会报端口被占用的错误,使用setsockopt就解决这种情况

3、绑定

bind((‘127.0.0.1’,8000))
里面一定要是一个tuple类型,绑定ip,端口,ip是部署服务端的ip,如果是127.0.0.1,就表示服务端和客户端要在一台服务器上,端口0-65535,0-1024是操作系统使用,1024后的端口是应用程序使用

4、侦听

listen()
在某个端口上.侦听

5、accept ()

accept () listen侦听后,有客户端进来来调用,如果一直没有客户端来,就会阻塞在这里

6、connect ()

connect((‘127.0.0.1’,8000)) 联接远程某个Socket,里面也要一个tuple类型,ip和端口都填的是服务端的ip和端口
connect函数是客户端用来同服务端连接的

7、发送数据

send(msg.encode(“utf-8”)) 发送数据,需要把字符串转换为bytes

8、接收数据

recv(1024) 接收的数据 ,单位:bytes 这里表示最大接收1024个bytes

Socket 编程模型

  • Socket当前编程模型一般都是C/S结构.即相互通信的网络程序中,一方称为客户程序(client),另一方称为服务程序(server)
  • C/S结构中,客户端向服务器发送请求,服务器作出响应.象常见的浏览器/web服务器,FTP客户端/FTP服务器. 就是典型的C/S结构
  • 一个服务器可以同时接受多个客户端请求
  • 在socket编程中,服务器和客户端的编程流程有一些不同

socket编程的流程图

在这里插入图片描述
服务端流程:创建套接口(socket)→绑定套接口(bind)→设置套接口为监听模式,进入被动接受连接请求状态(listen) →接受请求(accept),建立连接(socket)→读/写数据(recv,send)→终止连接(close)
客户端流程:创建套接口(socket)→与远程服务程序连接(connect)→写/读数据(send/recv)→终止连接 (close)

代码实现

本例子实现是循环接收客户端,只能一个一个连接客户端,还不能并发
1、服务端代码,文件名server.py

#--coding:utf-8--

import socket
'''
socket.AF_INET:表示是基于网络的套接字家族
socket.SOCK_STREAM:表示流式模块,基于tcp协议
'''
#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定ip,端口,ip是部署服务端的ip,如果是127.0.0.1,就表示服务端和客户端要在一台服务器上,端口0-65535,0-1024是操作系统使用,1024后的端口是应用程序使用
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)   #设置端口重用,比如端口已经关闭了但是操作系统还没有释放,会提示端口占用
server.bind(('0.0.0.0',8000))
#监听
server.listen()

print('staring....')
while True:  #连接循环
    conn , addr = server.accept()
    print(addr)

    while True:   #通信循环
        try:
            data = conn.recv(1024)   #1、单位:bytes 2、最大接收1024个bytes
            if not data:break   #适用于linux操作系统,如果客户端断开了连接,如果不处理在linux系统上,客户端断开后服务端就会进入的无限循环
            print('客户端的数据:',data.decode('utf8'))
            conn.send(data.upper())   #服务端接收到客户端的数据后,变成大写再返回给客户端
        except ConnectionResetError:     #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
            break

    conn.close()
server.close()

2、客户端代码,文件名称client.py

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8000))

while True:
    msg = input(">>: ").strip() #去掉空格
    if not msg:continue      #如果发的是空就进入下一次循环,如果不处理,当客户端发送一个空字符后服务端就会阻塞在recv处
    client.send(msg.encode("utf-8"))   #将字符串转换为bytes
    data = client.recv(1024)
    print(data.decode("utf8"))

client.close()

 

【出处】:https://blog.csdn.net/javascript_good/article/details/131432270

=======================================================================================

python网络编程(二)模拟ssh远程执行命令

1、项目需求:

要实现一个像ssh远程连接工具一样,在终端输入命令,返回对应的结果。
比如window的dos命令:
dir :查看目录下的文件
ipconfig : 查看网卡信息
tasklist : 查看进程列表
linux的命令:
ls : 查看目录下的文件
ifconfig : 查看网卡信息
ps -aux : 查看进程列表

2、项目分析:

这就是一个典型的c/s模式,在客户端发送一个命令,服务端接收到命令后,执行命令,并获取到执行的结果,再发送给客户端。
那么如何执行命令呢,python中提供了os模块的system可以执行系统命令

import os
res = os.system('dir')
print(res)

运行结果:
在这里插入图片描述
os.system()获取的结果只是打印出来了,通过变量去获取打印出来是0,通过这样无法获取。
还有一个比os.system()模块更好的subprocess模块,他更安全,还带有管道。

3、代码实现

服务端代码,server.py

#--coding:utf-8--

import socket
import subprocess
'''
socket.AF_INET:表示是基于网络的套接字家族
socket.SOCK_STREAM:表示流式模块,基于tcp协议
'''
#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',8000))
#监听
server.listen()

print('staring....')
while True:  #连接循环
    conn , addr = server.accept()
    print(addr)

    while True:   #通信循环
        try:
            #1、接收命名
            cmd = conn.recv(1024)   #1、单位:bytes 2、最大接收1024个bytes
            if not cmd:break   #适用于linux操作系统,如果客户端断开了连接
            
            #2、执行命令
            obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            #3、把命令结果返回给客户端
            conn.send(stdout+stderr)
        except ConnectionResetError:     #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
            break

    conn.close()
server.close()

客户端代码,client.py

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8000))

while True:
    cmd = input(">>: ").strip() #去掉空格
    if not cmd:continue      #如果发的是空就进入下一次循环
    client.send(cmd.encode("gbk"))   #因为是在windows系统下,所以用gbk
    data = client.recv(1024)
    print(data.decode("gbk"))

client.close()

运行结果:
在这里插入图片描述
可以看到客户端第一次输入ipconfig命令后返回了结果,但是在第二次输入dir 命令后,首先接收的是第一次没有接收完的网卡信息,这是因为客户端接收的数据存放在管道中,一次最大只能接收1024个字节,超出的部分依然保留在管道里面,下次再接收数据的时候,会先把积压的数据给取出来,再取后面的。这种现象就是粘包现象。还有如果发送端多次发送数据量小且间隔时间短也会出现粘包问题。

  1. TCP (transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一- -成对的socket,因此,发送端为了将多个发往接收端的包,更有
    效的发到对方,使用了优化方法(Nagle算法) ,将多次间隔较小且数据量小的数据,合并成一 个大的
    数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
    2. UDP (user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会
    使用块的合并优化算法,,由于UDP支持的是- -对多的模式,所以接收端的skbuff(套接字缓冲区)采用
    了链式结构来记录每一个到达的UDP包, 在每个UDP包中就有了消息头(消息来源地址,端口等信
    息),这样,对于接收端来说,就容易进行区分处理了。即面向消息的通信是有消息保护边界的。
    3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,
    udp协议会帮你封装.上消息头,实验略

如何解决粘包问题

目前的问题就是客户端只接收了一次数据,数据量大的时候没有接收完,如果我们知道这个数据的大小就可以通过循环来把所有的数据都读取出来了。
那么服务端在发送命令结果前要先把结果大小先传给客户端,可以在数据包的前面加一个数据报的头,这个头是一个固定大小的字节。这个头可以用一个字典来处理。

服务端改善的代码如下,文件名server.py

#--coding:utf-8--

import socket
import subprocess
import struct
import json
import threading

#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',8000))
#监听
server.listen()

print('staring....')
while True:  #连接循环
    conn , addr = server.accept()
    print(addr)

    while True:   #通信循环
        try:
            #1、接收命名
            cmd = conn.recv(2048)   #1、单位:bytes 2、最大接收1024个bytes
            if not cmd:break   #适用于linux操作系统,如果客户端断开了连接
            
            #2、执行命令
            obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            #3、把命令结果返回给客户端
            #第一步制作固定长度的包头
            header_dic = {
                'filename':'a.txt',
                'md5':'********',
                'total_size': len(stdout)+len(stderr)
            }
            #将字典转化成字符串
            header_json = json.dumps(header_dic)
            #在将字符串转换为bytes
            header_bytes = header_json.encode("gbk")
            #第二步,先发送包头的长度
            conn.send(struct.pack('i',len(header_bytes)))
            #第三步: 发送报头
            conn.send(header_bytes)
            #第四步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:     #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
            break

    conn.close()
server.close()

完善后的客户端代码,文件名client.py

import socket
import struct
import json

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8000))

while True:
    cmd = input(">>: ").strip() #去掉空格
    if not cmd:continue      #如果发的是空就进入下一次循环
    #1、发送命令
    client.send(cmd.encode("gbk"))

    #2、拿命令的结果并打印
    #第一步:接收报头的长度
    obj = client.recv(4)
    header_size = struct.unpack('i',obj)[0]

    #第二步:再收报头
    header_bytes = client.recv(header_size)
    #第三步:从包头中解析出真实数据的描述信息
    header_json = header_bytes.decode("gbk")
    header_dic = json.loads(header_json)

    #一次不能把内容接收完,那就循环接收,只要知道这个内容的总共大小
    total_size = header_dic['total_size']

    #接收真实的数据
    recv_size = 0     #接收的数据大小
    recv_data = b''    #接收的数据
    while recv_size < total_size:
        data = client.recv(1024)
        recv_data = recv_data + data
        recv_size = recv_size + len(data)
    print(recv_data.decode("gbk"))

client.close()

运行结果:
在这里插入图片描述
现在就可以一次显示完整了

 

【出处】:https://blog.csdn.net/javascript_good/article/details/131443108

=======================================================================================

python网络编程(三)实现文件下载功能

一:目标:

要实现一个客户端从服务端下载文件的功能,这个在模拟ssh远程执行命令的基础上再做修改就可以了

二:分析:

1、要规定客户端获取文件的格式:下载文件用 get 文件名, 比如要下载服务端的a.txt ,就写成 get a.txt
2、因为我目前是客户端和服务端都是在一台服务器上,我模拟的时候就把服务端的供下载的文件放到一个share的目录下,客户端下载后的文件存放在downloads的目录下,目录结构如下图:
在这里插入图片描述

3、需要知道获取文件大小的方法:

import  os

#获取文件大小
filename = 'F:\myfile\python\code\python3进阶\chepter07\模拟ssh\server.py'
filesize = os.path.getsize(filename)
print(filesize)

4、客户端程序的流程:
发送要获取的文件名(get a.txt) —> 接收报头信息,包头固定长度---->解析报头信息,获取到文件名称和文件大小—>接收服务端发来的数据,在本地创建一个文件保存接收的数据
5、服务端的流程:
接收到客户的下载命令(get a.txt )—> 解析出文件名(a.txt)—>获取文件大小—> 制作固定长度的报头,必须包含文件名和文件大小—>发送报头长度和报头数据—>读取文件内容发送给客户端。

文件下载的服务端 server.py

#--coding:utf-8--

import socket
import subprocess
import struct
import json
import os
import threading
'''
socket.AF_INET:表示是基于网络的套接字家族
socket.SOCK_STREAM:表示流式模块,基于tcp协议
'''
#服务端可以下载的文件都放到指定的一个share目录下
share_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载\server\share'
#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8001))
#监听
server.listen()

print('staring....')
while True:  #连接循环
    conn , addr = server.accept()
    print(addr)

    while True:   #通信循环
        try:
            #1、接收命名
            res = conn.recv(2048)   # get a.txt
            if not res:break   #适用于linux操作系统,如果客户端断开了连接
            
            #2、解析出文件名称
            cmds = res.decode().split()
            filename = cmds[1]
            #3、获取到文件大小
            file_size = os.path.getsize('%s\%s' %(share_dir,filename))

            #第一步制作固定长度的包头
            header_dic = {
                'filename':filename,  #a.txt
                'md5':'********',
                'file_size': file_size
            }
            #将字典转化成字符串
            header_json = json.dumps(header_dic)
            #在将字符串转换为bytes
            header_bytes = header_json.encode("gbk")
            #第二步,先发送包头的长度
            conn.send(struct.pack('i',len(header_bytes)))
            #第三步: 发送报头
            conn.send(header_bytes)
            #第四步:读取文件内容,发送给客户端
            with open( '%s\%s' %(share_dir,filename),'rb') as fd:
                for line in fd:     #一行一行读取
                    print(len(line))
                    conn.send(line)

        except ConnectionResetError:     #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
            break

    conn.close()
server.close()

客户端代码,client.py

import socket
import struct
import json

downloads_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载\client\downloads'
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8001))

while True:
    cmd = input(">>: ").strip() #  要求下载文件的格式get a.txt
    if not cmd:continue      #如果发的是空就进入下一次循环
    #1、发送命令
    client.send(cmd.encode("gbk"))

    #2、拿下载文件报头信息
    #第一步:接收报头的长度
    obj = client.recv(4)
    header_size = struct.unpack('i',obj)[0]

    #第二步:再收报头
    header_bytes = client.recv(header_size)
    #第三步:从包头中解析出真实数据的描述信息
    header_json = header_bytes.decode("gbk")
    header_dic = json.loads(header_json)
    """
     header_dic = {
                'filename':filename,  #a.txt
                'md5':'********',
                'file_size': file_size
            }
    """
    print(header_dic)
    #一次不能把内容接收完,那就循环接收,只要知道这个内容的总共大小
    file_size = header_dic['file_size']
    filename = header_dic['filename']
    #接收服务端发来的数据,并保存到本地文件中

    with open('%s/%s' %(downloads_dir,filename),'wb') as fd:
        recv_size = 0     #接收的数据大小
        while recv_size < file_size:
            data = client.recv(1024)
            fd.write(data)
            recv_size = recv_size + len(data)
            print('总大小:%s    已下载大小:%s' %(file_size,recv_size))

client.close()

开始我的downsloads目录是空的
在这里插入图片描述
运行结果:
在这里插入图片描述
在这里插入图片描述
当总大小等于已下载大小的时候就表示下载完了。
在去看downloads目录,已经有1.jpg的图片了, 这样一个简单的下载功能就实现了。
在这里插入图片描述

 

【出处】:https://blog.csdn.net/javascript_good/article/details/131453458

=======================================================================================

python网络编程(四)用面向对象方式实现文件上传下载

一:背景

在之前已经实现了文件的下载,现在再来完善上传功能,并且使用面向对象来封装,让代码看起来更加清楚明了。

二: 使用规则和运行结果

  • 下载文件,下载格式 get 文件名
    get空格后面直接接文件名称,在服务端存放的文件名

  • 上传文件,上传格式 put 文件路径+文件名
    因为是上传,上传的时候需要加上文件的路径和文件的名字,客户端程序可以直接根据路径去读取文件内容发送给服务端

  • 以下是本地运行的结果
    上传
    在这里插入图片描述
    下载:
    在这里插入图片描述
    在这里插入图片描述

三:部分函数说明

1、客户端

我们把套接字家族、协议类型、最大传输字节数、编码格式,和下面目录都设置为类变量

class MyTcpClient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding = 'gbk'
    downloads_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\client\downloads'

还把每一个连接步骤也封装成为一个方法,在初始化实例的时候,要传入服务端地址

  def __init__(self,server_address,connect=True):
        self.server_address = server_address
        self.socket = socket.socket(self.address_family, self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise

run函数就是处理键盘输入的命令,先解析数是put 还是get,把他存入到cmd中。
hasattr() 函数用于判断对象是否包含对应的属性。
getattr其实是使用的反射,获取到cmd 是put ,func(l) 就表示是调用put(l)方法
在run 方法中只去分析命令,并调用对应的方法。

    def run(self):
        while True:
            inp = input(">>: ").strip()  # 要求下载文件的格式get a.txt,  上传的时候带文件路径如 put e:\selenium.png
            if not inp: continue  # 如果发的是空就进入下一次循环
            l = inp.split()
            cmd = l[0]
            if hasattr(self,cmd):
                func = getattr(self,cmd)
                func(l)

get方法,传入的参数是[‘get’,‘a.txt’], 不管是get 还是put都把命令都加上一个报头,如果{‘cmd’:cmd,‘filename’:filename,‘file_size’:10},cmd和文件名称都可以从参数中获取到,file_size在这里是没有用的,所以随便写一个都无所谓。先把报头发过去,在接收服务端返回的数据。
服务端也是先返回一个报头,告诉你文件的大小,获取到文件大小之后,再把文件写入到本地目录。

    def get(self,args):
        cmd = args[0]
        filename = args[1]
        header_dic = {'cmd':cmd,'filename':filename,'file_size':10}
        #print(header_dic)
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode(self.coding)
        self.socket.send(struct.pack('i', len(header_bytes)))
        self.socket.send(header_bytes)   #发送命令

        #接收报头
        head_struct = self.socket.recv(4)
        if not head_struct: return      # 适用于linux操作系统,如果客户端断开了连接

        header_len = struct.unpack('i', head_struct)[0]
        header_json = self.socket.recv(header_len).decode(self.coding)
        header_dic = json.loads(header_json)
        print(header_dic)
        filename = header_dic['filename']
        file_size = header_dic['file_size']

        with open('%s/%s' % (self.downloads_dir, filename), 'wb') as fd:
            recv_size = 0  # 接收的数据大小
            while recv_size < file_size:
                data = self.socket.recv(self.max_packet_size)
                fd.write(data)
                recv_size = recv_size + len(data)
                print('总大小:%s    已下载大小:%s' % (file_size, recv_size))

put方法,这次参数是 [‘put’,‘e:\a.txt’] 这样的,filepath就表示带路径的文件,先获取到文件大小
其实在服务端是不需要你客户端的文件路径的,只需要文件名,服务端会在指定目录下创建新的文件,所以我通过\ 来切出文件的名称,传给服务端的报头就只有文件名
把包头发送过去后,再发送文件内容。

    def put(self,args):
        cmd = args[0]
        filepath = args[1]
        if not os.path.isfile(filepath):
            print('file: %s is not exists' %filepath)
            return
        else:
            file_size = os.path.getsize(filepath)

        filename = str(filepath).split('\\')[-1]
        header_dic = {'cmd':cmd,'filename':filename,'file_size':file_size}
        print(header_dic)
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode(self.coding)
        self.socket.send(struct.pack('i', len(header_bytes)))
        self.socket.send(header_bytes)
        send_size = 0
        with open(filepath, 'rb') as fd:
            for line in fd:  # 一行一行读取
                self.socket.send(line)
                send_size = send_size + len(line)
                print('总大小:%s    已发送大小:%s' % (file_size, send_size))

2、服务器端

其实上传和下载的方法,在服务端跟客户端只是反一下,变化不大
服务端的run方法会比较不同
因为客户端先发的是一个报头,所以服务端就先接受4字节的包头,并且客户解析出是上传还是下载,如果是上传就调用上传的方法,如果是下载就调用下载的方法,传入的参数都是报头,如header_dic = {‘cmd’: ‘get’, ‘filename’: filename, ‘file_size’: file_size }

    def run(self):
        while True:  # 连接循环
            self.conn, self.client_addr = self.get_accept()
            print(self.client_addr)

            while True:  # 通信循环
                try:
                    # 1、接收报头的长度
                    head_struct = self.conn.recv(4)
                    if not head_struct: break  # 适用于linux操作系统,如果客户端断开了连接

                    header_len = struct.unpack('i',head_struct)[0]
                    header_json = self.conn.recv(header_len).decode(self.coding)
                    header_dic = json.loads(header_json)
                    print(header_dic)

                    # 2、解析出文件名称
                    #header_dic = {'cmd': 'get', 'filename': filename, 'file_size': file_size  }
                    cmd = header_dic['cmd']
                    if hasattr(self,cmd):
                        func = getattr(self,cmd)
                        func(header_dic)
                except ConnectionResetError:  # 适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
                    break

            self.conn.close()

四:完整代码

服务端代码

#--coding:utf-8--
import socket
import struct
import json
import os

class MyTcpServer:
    '''
    文件上传下载的服务器端
    '''
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding = 'gbk'
    request_queue_size = 5
    server_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\server\share'


    def __init__(self,server_address,bind_and_activate=True):
        """
        socket 初始化
        :param server_address: 服务器地址,(ip,端口)
        :param bind_and_activate:
        """
        self.server_address = server_address
        self.socket = socket.socket(self.address_family,self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_listen()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """绑定"""
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()
        print(self.server_address)

    def server_listen(self):
        """监听"""
        self.socket.listen(self.request_queue_size)
        print("Staring")

    def server_close(self):
        """关闭socket"""
        self.socket.close()

    def get_accept(self):
        """获取跟客户端绑定的信息"""
        return self.socket.accept()


    def run(self):
        while True:  
            self.conn, self.client_addr = self.get_accept()
            print(self.client_addr)

            while True: 
                try:
                    # 1、接收报头的长度
                    head_struct = self.conn.recv(4)
                    if not head_struct: break  # 适用于linux操作系统,如果客户端断开了连接

                    header_len = struct.unpack('i',head_struct)[0]
                    header_json = self.conn.recv(header_len).decode(self.coding)
                    header_dic = json.loads(header_json)
                    print(header_dic)

                    # 2、解析出文件名称
                    #header_dic = {'cmd': 'get', 'filename': filename, 'file_size': file_size  }
                    cmd = header_dic['cmd']
                    if hasattr(self,cmd):
                        func = getattr(self,cmd)
                        func(header_dic)
                except ConnectionResetError:  # 适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
                    break

            self.conn.close()

    def get(self,args):
        """读取服务端文件发送给客户端"""
        #获取文件名:
        filename = args['filename']
        # 3、获取到文件大小
        file_size = os.path.getsize('%s\%s' % (self.server_dir, filename))

        # 第一步制作固定长度的包头
        header_dic = {
            'cmd': args['cmd'],
            'filename': filename,  # a.txt
            'file_size': file_size
        }
        # 将字典转化成字符串
        header_json = json.dumps(header_dic)
        # 在将字符串转换为bytes
        header_bytes = header_json.encode(self.coding)
        # 第二步,先发送包头的长度
        self.conn.send(struct.pack('i', len(header_bytes)))
        # 第三步: 发送报头
        self.conn.send(header_bytes)
        # 第四步:读取文件内容,发送给客户端
        with open('%s\%s' % (self.server_dir, filename), 'rb') as fd:
            for line in fd:  # 一行一行读取
                self.conn.send(line)

    def put(self,args):
        filename = args['filename']    # e:\selenium.png
        file_size = args['file_size']

        with open('%s\%s' % (self.server_dir, filename),'wb') as fd:
            recv_size = 0  # 接收的数据大小
            while recv_size < file_size:
                data = self.conn.recv(self.max_packet_size)
                fd.write(data)
                recv_size = recv_size + len(data)
                print('总大小:%s    已下载大小:%s' % (file_size,recv_size))
      
if __name__ == '__main__':
    tcpserver1 = MyTcpServer(('127.0.0.1',8891))
    tcpserver1.run()

客户端代码

import socket
import struct
import json
import os

class MyTcpClient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding = 'gbk'
    downloads_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\client\downloads'

    def __init__(self,server_address,connect=True):
        self.server_address = server_address
        self.socket = socket.socket(self.address_family, self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise

    def client_connect(self):
        self.socket.connect(self.server_address)

    def client_close(self):
        self.socket.close()

    def run(self):
        while True:
            inp = input(">>: ").strip() 
            if not inp: continue  # 如果发的是空就进入下一次循环
            l = inp.split()
            cmd = l[0]
            if hasattr(self,cmd):
                func = getattr(self,cmd)
                func(l)

    def put(self,args):
        cmd = args[0]
        filepath = args[1]
        if not os.path.isfile(filepath):
            print('file: %s is not exists' %filepath)
            return
        else:
            file_size = os.path.getsize(filepath)

        filename = str(filepath).split('\\')[-1]
        header_dic = {'cmd':cmd,'filename':filename,'file_size':file_size}
        print(header_dic)
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode(self.coding)
        self.socket.send(struct.pack('i', len(header_bytes)))
        self.socket.send(header_bytes)
        send_size = 0
        with open(filepath, 'rb') as fd:
            for line in fd:  # 一行一行读取
                self.socket.send(line)
                send_size = send_size + len(line)
                print('总大小:%s    已发送大小:%s' % (file_size, send_size))

    def get(self,args):
        cmd = args[0]
        filename = args[1]
        header_dic = {'cmd':cmd,'filename':filename,'file_size':10}
        #print(header_dic)
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode(self.coding)
        self.socket.send(struct.pack('i', len(header_bytes)))
        self.socket.send(header_bytes)   #发送命令

        #接收报头
        head_struct = self.socket.recv(4)
        if not head_struct: return      # 适用于linux操作系统,如果客户端断开了连接

        header_len = struct.unpack('i', head_struct)[0]
        header_json = self.socket.recv(header_len).decode(self.coding)
        header_dic = json.loads(header_json)
        print(header_dic)
        filename = header_dic['filename']
        file_size = header_dic['file_size']

        with open('%s/%s' % (self.downloads_dir, filename), 'wb') as fd:
            recv_size = 0  # 接收的数据大小
            while recv_size < file_size:
                data = self.socket.recv(self.max_packet_size)
                fd.write(data)
                recv_size = recv_size + len(data)
                print('总大小:%s    已下载大小:%s' % (file_size, recv_size))

if __name__ == '__main__':
    tcpclient1 = MyTcpClient(('127.0.0.1',8891))
    tcpclient1.run()

 

【出处】:https://blog.csdn.net/javascript_good/article/details/131461941

=======================================================================================

posted on 2023-12-01 10:36  jack_Meng  阅读(189)  评论(0编辑  收藏  举报

导航