第三模块 第25章 网络编程

https://www.cnblogs.com/linhaifeng/articles/6129246.html

思路整理:

  数据在通过网络传输之前, 需要经过层层协议的加工包装, 这些协议就是五层协议. 为了简便开发, 出现了socket模块, 对这五层协议进行了封装.

  使用socket模块进行网络通信需要注意的问题:

    1. 循环通信

    2. 服务端和多个客户端通信

    3. 解决常见bugs:

      1. 提示端口已被占用

      2. 客户端发送的消息为空

      3. 客户端单方面中断连接

    4. 黏包现象

osi七层协议:

1. 五层协议:

物理层: 传输010110的二进制信号

数据链路层: Ethernet协议

  规定数据包含数据头和数据部分, 数据头包含原地址和目标地址, 两者都是MAC地址, MAC地址存在于网卡上, 全球唯一.

  基于MAC地址的广播的形式进行通信, 只适用于局域网.

网络层: IP协议

  也分为数据头和数据部分, 数据头部分包含源IP地址和目标IP地址. 它们到了数据链路层都算作数据. IP地址标识所在子网.

  IP地址和MAC地址能找到全球唯一的计算机.

  ARP协议: 将IP地址解析成MAC地址, 仅靠IP地址就能找到计算机.

传输层: TCP/UDB协议

  基于端口工作, 能定位到计算机上的软件. 计算机上每启动一个软件就对应一个端口(0-6535). 

  格式也是数据头和数据, 两者同为下一层的数据.

  IP+端口号能定位全球范围内唯一的软件.

  包含两种协议: tcp协议和udp协议.

  tcp协议下数据以数据流的方式传输, 在传输数据之前需要先建立连接通道. 遵循三次握手和四次挥手的原则. 传输效率低.

  udp协议下数据以包的形式传输, 传输数据之前无需建立连接通道, 只负责丢出数据, 不管是否收到数据, 传输数据不可靠. 传输效率高

SYN=1表示请求建立连接, ACK=1+x, 其中1表示同意建立连接, 其中x与seq中的x相对应, 表示回复的消息适用于上一个请求.

应用层: 可以规定自己的协议: http协议, ftp协议

  产生数据, 格式为数据头和数据

以上五层可以分为两类: 自定义和他定义型.

2. 什么是socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

2.1 基础代码

# 服务端
import socket
#1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)
# <socket.socket fd=872, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
# family=AddressFamily.AF_INET表示基于网络的套接字, type=SocketKind.SOCK_STREAM表示使用的协议是tcp协议.
#2.绑定手机卡
phone.bind(('127.0.0.1', 8080))
# bind()中需要传入元祖, 第一个参数是字符串类型的IP地址, 应该为运行软件的计算机的IP地址.
# '127.0.0.1'表示本机地址, 客户端与服务端运行在同一台机器上, 供测试用.
# 第二个参数是端口号, 范围为0-65535, 0-1024是给操作系统用的.
#3.开机
phone.listen(5)
# 其中, 5表示最大挂起的连接数.
#4.等电话建立连接
# res = phone.accept()  # 运行服务端后, 服务端会卡在此处.
# print(res)
# (<socket.socket fd=816, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
# laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 15205)>, ('127.0.0.1', 15205))
# 其中, <socket.socket fd=816, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 15205)>表示链接通道
# ('127.0.0.1', 15205)为客户端的IP和端口号.
conn,client_addr = phone.accept()
#5.收发消息
data = conn.recv(1024)
# 1024代表1024个字节, 为接收数据的最大数.
print('客户端的数据',data)
conn.send(data.upper())
#6.挂电话
conn.close()
#7.关机
phone.close()
# 服务端有两种套接字:phone和conn
# 注意: 重复运行客户端, 可能或报错, 说端口被占用, 可以修改端口.
# 客户端
import socket
#1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) # 连接服务端的IP和端口
#3.发收消息
phone.send('hello'.encode('utf-8'))  # 发送的内容必须是bytes.
data = phone.recv(1024)
print(data)
#4.关闭
phone.close()
# 客户端只有一种套接字:phone

 2.2 循环通信(在基础代码的基础上加上循环)

# 服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,addr = phone.accept()
while True:
    data = conn.recv(1024).decode('utf-8')
    print('客户端消息:%s'%data)
    msg = input('>>>')
    conn.send(msg.encode('utf-8'))
conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
    msg = input('>>>').strip()
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024).decode('utf-8')
    print('服务端消息:%s'%data)
phone.close()

2.3 bug的解决方法

# 服务端
# 三种bug的解决方式:
# bug1: 提示端口已被占用      解决方案1:更换端口号, 解决方案2:...
# bug2: 客户端发送的消息为空   解决方案:让客户端继续输入, 直至不为空
# bug3: 客户端单方面中断连接   解决方案:windows系统下使用异常处理, linux系统下跳出循环
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 如果报错, 提示端口已被占用, 可以使用以下代码解决, 或者修改端口号
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,addr = phone.accept()
while True:
    try:
        data = conn.recv(1024).decode('utf-8')  #如果客户端发的是空, 操作系统拿不到消息, 则不会发送, 服务端什么也收不到, 就阻塞在了这里.
    # recv()操作是应用程序向操作系统发送请求, 让其从网卡将数据收回来.
    # 收到数据后将数据从操作系统内存拷到应用程序内存.
    # if not data:break
    # 如果客户端单方面中断连接, 可以采用以上方法, 这种解决方案仅适用于linux系统
        print('客户端消息:', data)
        msg = input('>>>').encode('utf-8')
        conn.send(msg)
    except ConnectionResetError:  # 适用于windows操作系统
        break
conn.close()
phone.close()
# 如果客户端单方面断开连接, 服务端会出现死循环(linxu), 或者报错(windows).
# 客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>').strip().encode('utf-8')  #可以发空, 但是操作系统接收到空消息后认为什么也没收到, 不会发送消息.
    if not msg:  # 防止客户端发送空消息
        continue
    phone.send(msg)  # 将信息从应用程序的内存发送到操作系统的内存,
    # 操作系统参照传输层, 网络层, 数据链路层, 物理层对数据进行层层包装, 然后经由网卡发送.
    data = phone.recv(1024).decode('utf-8')
    print(data)
phone.close()

 

2.4 服务端与多个客户端通信

# 服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:  # 链接循环:只能一个一个来, 关掉一个才能运行第二个. 要真正实现与多个客户端通信, 则需要并发编程.
    conn,addr = phone.accept()
    while True:
        try:
            data = conn.recv(1024).decode('utf-8')
            print('客户端消息:', data)
            msg = input('>>>').encode('utf-8')
            conn.send(msg)
        except ConnectionResetError:
            break
    conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>').strip().encode('utf-8')
    if not msg:
        continue
    phone.send(msg)
    data = phone.recv(1024).decode('utf-8')
    print(data)
phone.close()

2.5 模拟ssh远程执行命_项目分析

如果所使用的服务器是托管的, 则需要通过网络远程控制服务器.

 

#知识点补充
'''
windows:
1. dir:查看某一个文件夹下的子文件名与子文件夹名
2. ipconfig: 查看本地网卡的ip信息
3. tasklist: 查看运行的进程
linux:
对应的分别为:
1. ls
2. ifconfig
3. ps aux
'''
# import os
# res = os.system('dir F:\python全栈开发\课件')
# print(res)
# 执行后的结果会直接交给终端, 赋值并不能拿到结果.
# 打印结果为0表示执行成功, 否则表示执行失败. 所以, 不能使用本方法.
import subprocess
obj = subprocess.Popen('dir F:\python全栈开发\课件',shell=True,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
print(obj)
print('stdout1:',obj.stdout.read().decode('gbk'))  # 从正确管道读取结果
print('stdout2:',obj.stdout.read().decode('gbk'))  # 取走后再取就取不出来了
print('stderr1:',obj.stderr.read().decode('gbk'))  # 从错误管道读取结果
# 服务端
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:  # 链接循环:只能一个一个来, 关掉一个才能运行第二个. 要真正实现与多个客户端通信, 则需要并发编程.
    conn,addr = phone.accept()
    while True:
        try:
            # 1. 接收命令
            cmd = conn.recv(1024)
            # 2. 执行命令, 拿到结果
            obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 3. 把命令的结果返回给客户端
            conn.send(stdout+stderr)
            # 使用+并不是在stdout的基础上直接加, 而是开辟新的内存将两者放进去, 可以进一步进行优化
        except ConnectionResetError:
            break
    conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while True:
    # 1. 发送命令
    msg = input('>>>').strip().encode('utf-8')
    if not msg:
        continue
    phone.send(msg)
    # 2. 拿到命令结果, 并打印
    data = phone.recv(1024).decode('gbk')
    print(data)
phone.close()

 2.6 黏包现象

2.6.1 黏包底层原理

send()操作: 应用程序想发送数据, 则需要先将自己内存中的数据copy到操作系统的内存, 然后由操作系统通过网卡发送出去. 至于如何发, 什么时候发都由操作系统控制, 不受应用程序的约束.

阶段1(copy数据): 将数据从应用程序内存copy到操作系统内存, 耗时短.

recv()操作: 应用程序通知操作系统, 调网卡接收数据, 先放在操作系统内存, 然后copy到应用程序的内存.

阶段1(等数据): 数据到达操作系统内存, 非常耗时.

阶段2(copy数据): 数据从操作系统内存到达应用程序内存.

不管是send还是recv, 都不是直接接收对方的数据, 而是操作自己的操作系统内存. 并不是一次send对应一次recv.

在发送时, 为了降低网络的IO次数, 采用Nagle优化算法, 将多次间隔小且数据量小的数据合并成一个大的数据块, 然后进行封包. 这种方式提高了效率, 但是会造成黏包现象.

黏包现象可能发生在发送端, 也可能发生在接收端.

两种黏包现象:
1. 连续的小包可能会被优化算法组合到一起进行发送. 这种黏包现象发生在输出缓冲区.
2. 第一次如果发送的数据大小 2000B, 接收端一次性接收大小为1024, 这就导致剩下的内容会被下一次recv接收到, 导致结果错乱. 这种黏包现象发生在输入缓冲区.

2.6.2 黏包现象的解决方案

2.6.2.1 简单版

# 知识点补充
import struct
res = struct.pack('i',1234)
print(res,type(res),len(res))
# b'\xd2\x04\x00\x00' <class 'bytes'> 4
obj = struct.unpack('i',res)
print(obj)
# (1234,)
# 服务端
import socket
import subprocess
import struct
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(5)
while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 把命令的结果返回给客户端
            # 第一步: 制作固定长度的报头
            total_size = len(stdout) + len(stderr)
            header = struct.pack('i',total_size)
            # 第二步: 把数据的长度发送给客户端
            conn.send(header)
            # 报头要有固定长度
            # 第三步: 再发送真实的数据
            conn.send(stdout)  # 利用黏包现象
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()
server.close()
# 客户端
import socket
import struct
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8001))
while True:
    msg = input('>>>').strip().encode('utf-8')
    if not msg:
        continue
    client.send(msg)
    # 拿命令结果
    # 第一步: 先收固定长度的报头
    header = client.recv(4)
    # 第二步: 从报头中解析出对真实数据的描述信息(数据长度)
    total_size = struct.unpack('i',header)[0]
    print('真实总长度++++++++++++++++++++++++',total_size)
    # 第三步: 接收真实的数据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_data+=data
        recv_size+=len(data)
    # 操作系统内存(缓存)不可能无限大, 所以接收的数据量写无限大没有意义.
    print('接收的总长度>>>>>>>>>>>>>>>>>>>>>>>>>',recv_size)
    print(recv_data.decode('gbk'))
client.close()
# 客户端收到的结果
>>>ipconfig
真实总长度++++++++++++++++++++++++ 1076
接收的总长度>>>>>>>>>>>>>>>>>>>>>>>>> 1076

Windows IP 配置


以太网适配器 以太网:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

以太网适配器 以太网 2:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

以太网适配器 以太网 3:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

无线局域网适配器 本地连接* 2:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

无线局域网适配器 WLAN:

   连接特定的 DNS 后缀 . . . . . . . : 
   本地链接 IPv6 地址. . . . . . . . : fe80::5a:5a50:8164:3554%11
   IPv4 地址 . . . . . . . . . . . . : 192.168.202.102
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.202.1

以太网适配器 蓝牙网络连接:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

隧道适配器 本地连接* 14:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . : 

>>>

 2.6.2.2 终极版

#知识补充
import struct
res = struct.pack('i',1234)
# i模式对数字大小有限制, 4bytes. l模式能够支持的范围更大, 8bytes.
print(res,type(res),len(res))
# b'\xd2\x04\x00\x00' <class 'bytes'> 4
obj = struct.unpack('i',res)
print(obj)
# (1234,)
# 服务端
import socket
import subprocess
import struct
import json
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(5)
while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 第一步: 制作报头
            total_size = len(stdout) + len(stderr)
            header = {
                'filename':'a.txt',
                'md5':'xxxx',
                'total_size':total_size
            }
            header_bytes = json.dumps(header).encode('utf-8')
            # 第二步: 先发送报头的长度
            conn.send(struct.pack('i',len(header_bytes)))# 第三步: 发送报头
            conn.send(header_bytes)
            # 第四步: 发送真实的数据
            conn.send(stdout)  # 利用黏包现象
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()
server.close()
# 客户端
import socket
import struct
import json
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8001))
while True:
    msg = input('>>>').strip().encode('utf-8')
    if not msg:
        continue
    client.send(msg)
    # 第一步: 先收固定长度的报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]
    print(header_size)
    # 第二步: 收报头
    header_bytes = client.recv(header_size)
    # 第三步: 从报头中解析出对真实数据的描述信息(数据长度)
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    total_size = header_dic['total_size']
    print('真实总长度++++++++++++++++++++++++',total_size)
    # 第四步: 接收真实的数据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_data+=data
        recv_size+=len(data)
    # 操作系统内存(缓存)不可能无限大, 所以接收的数据量写无限大没有意义.
    print('接收的总长度>>>>>>>>>>>>>>>>>>>>>>>>>',recv_size)
    print(recv_data.decode('gbk'))
client.close()

 2.7 实现文件传输功能

2.7.1 基础版

# 服务端
import socket
import subprocess
import struct
import json
import os
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(5)
path = 'F:\python全栈开发\python\day25 网络编程\文件传输功能实现\server\share'
while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            file_name = cmd.decode('utf-8').split()[1]
            # 把命令的结果返回给客户端
            # 第一步: 制作报头
            total_size = os.path.getsize(r'%s\%s'%(path,file_name))
            header = {
                'file_name':file_name,
                'total_size':total_size
            }
            header_json = json.dumps(header)
            header_bytes = header_json.encode('utf-8')
            # 第二步: 先发送报头的长度
            conn.send(struct.pack('i',len(header_bytes)))
            print(len(header_bytes))
            # 第三步: 发送报头
            conn.send(header_bytes)
            # 第四步: 再发送真实的数据
            with open(r'%s\%s'%(path,file_name),mode='rb') as f:
                for line in f:
                    conn.send(line)
        except ConnectionResetError:
            break
    conn.close()
server.close()
# 客户端
import socket
import struct
import json
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8001))
path = 'F:\python全栈开发\python\day25 网络编程\文件传输功能实现\client\download'
while True:
    msg = input('>>>').strip().encode('utf-8')
    if not msg:
        continue
    client.send(msg)
    # 第一步: 先收固定长度的报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]
    print(header_size)
    # 第二步: 收报头
    header_bytes = client.recv(header_size)
    # 第三步: 从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    total_size = header_dic['total_size']
    file_name = header_dic['file_name']
    print('真实总长度++++++++++++++++++++++++',total_size)
    # 第四步: 接收真实的数据
    recv_size = 0
    with open(r'%s\%s'%(path,file_name),mode='wb') as f:
        while recv_size < total_size:
            data = client.recv(1024)
            f.write(data)
            recv_size+=len(data)
    # 操作系统内存(缓存)不可能无限大, 所以接收的数据量写无限大没有意义.
    print('接收的总长度>>>>>>>>>>>>>>>>>>>>>>>>>',recv_size)
client.close()

2.7.2 函数版

# 服务端
import socket
import subprocess
import struct
import json
import os
def get(conn, cmd, path):
    file_name = cmd.decode('utf-8').split()[1]
    total_size = os.path.getsize(r'%s\%s' % (path, file_name))
    header = {
        'file_name': file_name,
        'total_size': total_size
    }
    header_json = json.dumps(header)
    header_bytes = header_json.encode('utf-8')
    conn.send(struct.pack('i', len(header_bytes)))
    conn.send(header_bytes)
    with open(r'%s\%s' % (path, file_name), mode='rb') as f:
        for line in f:
            conn.send(line)
def put(conn, cmd, path):
    pass
def run():
    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(5)
    path = 'F:\python全栈开发\python\day25 网络编程\文件传输功能实现\函数版\server\share'
    while True:
        conn,addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if cmd.decode('utf-8').split()[0] == 'get':
                    get(conn, cmd, path)
                elif cmd.decode('utf-8').split()[0] == 'put':
                    put(conn, cmd, path)
            except ConnectionResetError:
                break
        conn.close()
    server.close()
if __name__ == '__main__':
    run()
# 客户端
import socket
import struct
import json
def get(client,path):
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]
    header_bytes = client.recv(header_size)
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    total_size = header_dic['total_size']
    file_name = header_dic['file_name']
    recv_size = 0
    with open(r'%s\%s' % (path, file_name), mode='wb') as f:
        while recv_size < total_size:
            data = client.recv(1024)
            f.write(data)
            recv_size += len(data)
def put(client,path):
    pass
def run():
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8001))
    path = 'F:\python全栈开发\python\day25 网络编程\文件传输功能实现\函数版\client\download'
    while True:
        msg = input('>>>').strip()
        if not msg:
            continue
        client.send(msg.encode('utf-8'))
        if msg.split()[0] == 'get':
            get(client,path)
        elif msg.split()[0] == 'put':
            put(client,path)
    client.close()
if __name__ == '__main__':
    run()

 2.7.3 面向对象版

# 服务端
import socket
import struct
import json
import subprocess
import os

class MYTCPServer:
    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    allow_reuse_address = False

    max_packet_size = 8192

    coding='utf-8'

    request_queue_size = 5

    server_dir='file_upload'

    def __init__(self, server_address, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        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_activate()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """Called by constructor to bind the socket.
        """
        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()

    def server_activate(self):
        """Called by constructor to activate the server.
        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.
        """
        self.socket.close()

    def get_request(self):
        """Get the request and client address from the socket.
        """
        return self.socket.accept()

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()

    def run(self):
        while True:
            self.conn,self.client_addr=self.get_request()
            print('from client ',self.client_addr)
            while True:
                try:
                    head_struct = self.conn.recv(4)
                    if not head_struct:break

                    head_len = struct.unpack('i', head_struct)[0]
                    head_json = self.conn.recv(head_len).decode(self.coding)
                    head_dic = json.loads(head_json)

                    print(head_dic)
                    #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                    cmd=head_dic['cmd']
                    if hasattr(self,cmd):
                        func=getattr(self,cmd)
                        func(head_dic)
                except Exception:
                    break

    def put(self,args):
        file_path=os.path.normpath(os.path.join(
            self.server_dir,
            args['filename']
        ))

        filesize=args['filesize']
        recv_size=0
        print('----->',file_path)
        with open(file_path,'wb') as f:
            while recv_size < filesize:
                recv_data=self.conn.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size+=len(recv_data)
                print('recvsize:%s filesize:%s' %(recv_size,filesize))


tcpserver1=MYTCPServer(('127.0.0.1',8080))

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='utf-8'

    request_queue_size = 5

    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]
        filename=args[1]
        if not os.path.isfile(filename):
            print('file:%s is not exists' %filename)
            return
        else:
            filesize=os.path.getsize(filename)

        head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
        print(head_dic)
        head_json=json.dumps(head_dic)
        head_json_bytes=bytes(head_json,encoding=self.coding)

        head_struct=struct.pack('i',len(head_json_bytes))
        self.socket.send(head_struct)
        self.socket.send(head_json_bytes)
        send_size=0
        with open(filename,'rb') as f:
            for line in f:
                self.socket.send(line)
                send_size+=len(line)
                print(send_size)
            else:
                print('upload successful')




client=MYTCPClient(('127.0.0.1',8080))

client.run()

 

 2.8 基于UDP协议的套接字介绍

# 服务端
# udp协议又称数据报协议
# 一次send必须对应一次recv
# 不会黏包, 即便发空也是一个完整的包.
# QQ是基于udp协议写的, 有些查询操作是基于udp协议写的
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8001))
while True:
    data,client_addr = server.recvfrom(1024)
    # 如果数据长度为5, 而只接收1个, 则只会收1个, 其余的数据会丢失. windows中会报错.
    print(data)  # (b'hello', ('127.0.0.1', 53762))
    server.sendto(data.upper(),client_addr)
server.close()
# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
    msg = input('>>>').strip()
    client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8001))
    data,server_addr = client.recvfrom(1024)
    print(data, server_addr)
client.close()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @ 2020-08-03 11:19  自由者妍  阅读(228)  评论(0)    收藏  举报