Python-网络编程

网络协议 osi七层协议

1 物理层:(光纤 双绞线)
    主要基于电气特性发送高低电压(电信号) 高电压对应1 低电压对应0
2 数据链路层
    定义电信号的分组方式
    以太网协议:
        一组电信号组成一个数据包 叫做'帧' 
        每一个数据帧由 报头head和数据data两部分组成
        head(固定18个字节)
            发送者源地址 6个字节
            接收者源地址 6个字节
            数据类型    6个字节
        data(最短46个字节 最长1500个字节)
            数据包的具体内容
        head+data [64,1518] 字节 如果超过最大限制就分片发送
    mac协议:
        head中发送者源地址以及接收者源地址就是mac地址
        mac地址 长度为482进制 通常由1216进制数表示 (前六位是厂商编号 后六位是流水线号)
    广播:
        有了mac地址 同一网络下的两台主机就可以通信(一台主机通过arp协议获取另一台主机的mac地址)
        以太网通过最原始的方式 广播进行通信 
3 网络层
    功能: 引入一套新的地址来区分不同的广播域/子网 也就是网络地址
    IP协议:
        固定网络地址的协议叫做ip协议 定义的地址叫做 ip地址 v4版本 ipv4 规定网络地址由322进制表示
        范围 0.0.0.0-255.255.255.255
        一个ip地址通常写成四段十进制数 192.168.1.1
    IP地址:
        网络部分: 标识子网
        主机部分: 标识主机
    子网掩码:
        表示子网络特征的一个参数 形式上等同于ip地址 也是一个32位的2进制数字 网络部分全是1 主机部分全是0 
        例如 172.16.10.1 网络部分是前24位 主机部分是后8位
        子网掩码是 11111111.11111111.11111111.00000000 十进制就是 255.255.255.0
    判断两个ip地址是否处在同一个子网络:
        方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。
    IP协议的作用:
        1 为每台计算机分配IP地址 
        2 确定那些地址在同一个子网络
    IP数据包 直接放入以太网协议的 data部分 [[以太网头][IP头][IP数据]]
        1 head 长度2060字节
        2 data 最长为 65515字节
    ARP协议
        功能: 广播的形式发送数据包 获取目标主机的mac地址
        协议工作模式: 每一台主机的ip都是已知的
4 传输层
    功能: 建立端口到端口的通信
    端口范围: 0-65535 0-1023是系统占用端口
    1 TCP协议
        可靠传输 tcp数据包没有长度限制 理论可以无限长 但是为了保证网络传输的效率 通常TCP数据包的长度不会超过IP数据包的长度 确保单个TCP不需要再分割
        三次握手四次挥手
    2 UDP协议
        不可靠传输 报头部分只有8个字节 总长度不超过 65535字节 刚好放进一个IP数据包
5 应用层(包括应用层 表示层 会话层)
    功能: 规定应用程序的数据格式

网络编程

  1. 初始版本
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话
phone.connect(('127.0.0.1',8080)) # 与服务端链接 拨号
phone.send('alex'.encode('utf-8'))
from_server_data = phone.recv(1024)
print(from_server_data)
phone.close()
server
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话
phone.bind(('127.0.0.1',8080)) # 绑定电话卡
phone.listen(5) # 同一时刻有5个请求 但是可以有N个链接 开机
conn,client_addr = phone.accept() # 接电话
print(conn,client_addr,sep='\n')
from_client_data = conn.recv(1024) # 一次接收的最大值
print(from_client_data)
conn.send(from_client_data.upper())
conn.close() # 挂电话
phone.close() # 关机
  1. 第二版 通信循环
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while 1:
    try:
        client_data = input('>>>')
        phone.send(client_data.encode('utf-8'))
        from_server_data = phone.recv(1024)
        print(from_server_data.decode('utf-8'))
    except ConnectionError:
        break
phone.close()
server
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,client_addr = phone.accept()
print(
    conn,
    client_addr,
    sep='\n'
)
while 1:
    try:
        from_client_data = conn.recv(1024)
        print(from_client_data.decode('utf-8'))
        conn.send(from_client_data+b'sb')
    except ConnectionError:
        break
conn.close()
phone.close()

  1. 第三版 通信链接循环
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))

while 1 :
    client_data = input('>>>')
    phone.send(client_data.encode('utf-8'))
    from_server_data = phone.recv(1024)
    print(from_server_data.decode('utf-8'))
phone.close()
server
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)

while 1: # 循环连接客户端
    conn,client_addr = phone.accept()
    print(client_addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)
            print(from_client_data.decode('utf-8'))
            conn.send(from_client_data+b'alex')
        except ConnectionError:
            break
conn.close()
phone.close()

  1. 第四版 远程执行命令
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8081))
while 1 :
    to_server_msg = input("...").strip()
    phone.send(to_server_msg.encode('utf-8'))
    from_server_msg = phone.recv(1024)
    print(from_server_msg.decode('utf-8'))
phone.close()
server
import socket
import subprocess
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8081))
phone.listen(5)
while 1:
    conn,client_addr = phone.accept()
    print(client_addr)
    while 1:
        try:
            cmd = conn.recv(1024)
            print(cmd.decode('utf-8'))
            ret = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()
            conn.send(correct_msg+error_msg)
        except ConnectionError:
            break
coon.close()
phone.close()
  1. udp协议
client
import socket
ip_port = ('127.0.0.1',8087)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr = udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)
server
import socket
udp_sk =socket.socket(type=socket.SOCK_DGRAM)
udp_sk.bind(('127.0.0.1',8087))
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)
udp_sk.close()
  1. 黏包-情况一
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while 1 :
    cmd = input('...').strip()
    phone.send(cmd.encode('utf-8'))
    from_server_data = phone.recv(1024)
    print(from_server_data.decode('utf-8'))
phone.close()

# # 由于客户端发的命令获取的结果大小已经超过1024,那么下次在输入命令,会继续取上次残留到缓存区的数据。
server
'''
接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
'''
import socket
import subprocess
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while 1 :
    conn,client_addr = phone.accept()
    print(client_addr)
    while 1 :
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()
            conn.send(correct_msg+error_msg)
        except ConnectionError:
            break
conn.close()
phone.close()

  1. 黏包-情况二
client
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send(b'alex')
phone.send(b'zxcv')

phone.close()

# # 两次返送信息时间间隔太短,数据小,造成服务端一次收取
server
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,client_addr = phone.accept()

first_data = conn.recv(1024)
print('1:',first_data.decode('utf-8'))
second_data = conn.recv(1024)
print('2:',second_data.decode('utf-8'))

conn.close()
phone.close()
  1. 黏包-解决方案
client
import socket
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))

while 1:
    cmd = input('...').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    # 1 接收固定报头
    header_size = struct.unpack('i',phone.recv(4))[0]
    # 2 解析报头长度
    header_bytes = phone.recv(header_size)
    header_dict = json.loads(header_bytes.decode('utf-8'))
    # 3 收取报头
    total_size = header_dict['total_size']
    # 4 根据报头信息 接收真实数据
    recv_size = 0
    res = b''
    while recv_size < total_size:
        recv_data = phone.recv(1024)
        res += recv_data
        recv_size += len(recv_data)
    print(res.decode('utf-8'))
phone.close()

server
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while 1 :
    conn, client_addr = phone.accept()
    print(client_addr)
    while 1 :
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()

            # 1 制作固定表头
            total_size = len(correct_msg) + len(error_msg)
            head_dict = {
                'md5':'fdsaf2143254f',
                'file_name':'f1.txt',
                'total_size':total_size
            }
            header_dict_json = json.dumps(head_dict)
            bytes_headers = header_dict_json.encode('utf-8')
            header_size = len(bytes_headers)
            header = struct.pack('i',header_size)
            # 2 发送报头长度
            conn.send(header)
            # 3 发送报头
            conn.send(bytes_headers)
            # 4 发送真实数据
            conn.send(correct_msg)
            conn.send(error_msg)
        except ConnectionError:
            break
conn.close()
phone.close()

原理
'''
整个流程的大致解释:
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下

发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容

接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
'''
  1. FTP文件
client
import socket
import struct
import json
import os

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 买电话

phone.connect(('127.0.0.1', 8001))  # 与客户端建立连接, 拨号

# 1 制定file_info
file_info = {
    'file_path': r'D:\lnh.python\pyproject\PythonReview\网络编程\08 文件的上传下载\low版\aaa.mp4',
    'file_name': 'aaa.mp4',
    'file_size': None,
}
# 2 获取并设置文件大小
file_info['file_size'] = os.path.getsize(file_info['file_path'])

# 2,利用json将head_dic 转化成字符串
head_dic_json = json.dumps(file_info)

# 3,将head_dic_json转化成bytes
head_dic_bytes = head_dic_json.encode('utf-8')

# 4,将head_dic_bytes的大小转化成固定的4个字节。
ret = struct.pack('i', len(head_dic_bytes))  # 固定四个字节

# 5, 发送固定四个字节
phone.send(ret)

# 6 发送head_dic_bytes
phone.send(head_dic_bytes)

# 发送文件:
with open(file_info['file_path'], mode='rb') as f1:
    data_size = 0
    while data_size < file_info['file_size']:
        # f1.read() 不能全部读出来,而且也不能send全部,这样send如果过大,也会出问题,保险起见,每次至多send(1024字节)
        every_data = f1.read(1024)
        data_size += len(every_data)
        phone.send(every_data)

phone.close()

server
import socket
import subprocess
import json
import struct
import os

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

phone.bind(('127.0.0.1', 8001))

phone.listen(5)
file_positon = r'd:\上传下载'

conn, client_addr = phone.accept()

# # 1,接收固定4个字节
ret = conn.recv(4)
#
# 2,利用struct模块将ret反解出head_dic_bytes的总字节数。
head_dic_bytes_size = struct.unpack('i', ret)[0]
#
# 3,接收 head_dic_bytes数据。
head_dic_bytes = conn.recv(head_dic_bytes_size)

# 4,将head_dic_bytes解码成json字符串格式。
head_dic_json = head_dic_bytes.decode('utf-8')

# 5,将json字符串还原成字典模式。
head_dic = json.loads(head_dic_json)

file_path = os.path.join(file_positon, head_dic['file_name'])
with open(file_path, mode='wb') as f1:
    data_size = 0
    while data_size < head_dic['file_size']:
        data = conn.recv(1024)
        f1.write(data)
        data_size += len(data)

conn.close()
phone.close()



  1. FTP文件plus
client
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()

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


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值