桐花万里python路-高级篇-网络编程

  • CS架构
    • 客户端 client
    • 服务端 server
  • 网络协议
  • TCP/IP 网络通讯协议 Transmission Control Protocol/Internet Protocol
    • 互联网协议分为osi七层或tcp/ip五层或tcp/ip四层
    • 应用层,表示层,会话层
    • 传输层 
      • 建立端口到端口的通信
      • 端口范围0-65535,0-1023为系统占用端口
      • 传输层有两种协议,TCP(可靠传输)和UDP(不可靠传输)
        • TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割
        • 只要不得到确认,就重新发送数据报,直到得到对方的确认为止
      • TCP协议虽然安全性很高,但是网络开销大,而UDP协议虽然没有提供安全机制,但是网络开销小,在现在这个网络安全已经相对较高的情况下,为了保证传输的速率,我们一般还是会优先考虑UDP协议!
    • 网络层
      • 网络地址,用来区分不同的广播域/子网
      • IP协议 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址。ipv4,ipv6
      • 子网掩码 表示子网络特征的一个参数,将某个IP地址划分成网络地址和主机地址两部分。网络部分全部为1,主机部分全部为0
      • IP地址分类
        1. A类IP地址:一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”, 地址范围从1.0.0.0 到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。
        2. B类IP地址:一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机 。
        3. C类IP地址:一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。
        4. D类地址用于多点广播(Multicast): D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。
        5. E类IP地址 以“llll0”开始,为将来使用保留
      • IP报文
        • 所有的TCP,UDP,IMCP,IGCP的数据都以IP数据格式传输
        • TCP是一个可靠的协议,而UDP就没有那么可靠的区别
        • 协议头
      • ARP协议 广播的方式发送数据包,获取目标主机的mac地址
    • 网络接口层:数据链路层
      • 定义了电信号的分组方式
      • 以太网协议ethernet
        • 一组电信号构成一个数据包,叫做‘帧’
        • 每一数据帧分成:报头head和数据data两部分
        • head固定18个字节
          1. 发送者 / 源地址 6字节
          2. 接受者 / 目标地址 6字节
          3. 数据类型 6字节
        • data 数据包的具体内容 最短46字节,最长1500字节
      • mac地址
      • 广播发送方式 同一网络内的两台主机,通过arp协议获取另外一台主机的mac地址 
    • 网络接口层:物理层
      • 主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
  • Socket  是一组应用层与TCP/IP协议族通信的中间软件抽象层的接口。通过代码封装了tcp/ip协议层的各种数据封装、数据发送、接收等
      • 工作模式
    • 套接字方法 
      • 语法规范
        socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
      • family 
        • socket.AF_UNIX 本机进程间通讯
        • socket.AF_INET AF_INET6被用于ipv6
      • type
        • socket.SOCK_STREAM #for tcp
        • socket.SOCK_DGRAM #for udp
        • socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文
        • socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
      • 服务端方法
        • s.bind() 绑定(主机,端口号)到套接字
        • s.listen() 开始TCP监听
        • s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
      • 客户端方法
        • s.connect() 主动初始化TCP服务器连接
        • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
      • 公用方法
        • s.recv() 接收数据
        • s.send() 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
        • s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
        • s.recvfrom() Receive data from the socket. The return value is a pair (bytes, address)
        • s.getpeername() 连接到当前套接字的远端的地址
        • s.close() 关闭套接字
        • socket.setblocking(flag) #True or False,设置socket为非阻塞模式,以后讲io异步时会用
        • socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
        • socket.getfqdn() 拿到本机的主机名
        • socket.gethostbyname() 通过域名解析ip地址
  • 粘包 只存在于TCP中,socket缓冲区导致的,多次发送的数据包粘到一起
    • 接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
    • 解决方案
    • 服务端在处理数据的时候
      • 先将数据总长计算出
      • 先整理成报头字典,包含内容长度
      • 发送报文长度
      • 再发送报头
      • 最后发送具体数据
        import struct
        import json
        import socket
        import subprocess
        import settings
        
        serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        serv.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        serv.bind((settings.HOST, settings.TCPPORT))
        
        serv.listen(5)
        print("服务器已启动")
        while True:
            conn, addr = serv.accept()
            print("客户端:",conn)
            while True:
                content = conn.recv(1024)
                ret = content.decode("utf-8")
                print("recv:%s" % (ret))
                res = subprocess.Popen(ret,shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)
                stderr = res.stderr.read()
                stdout = res.stdout.read()
                # stdout = content
                print("res length", len(stdout))
                resp = bytes(json.dumps({'content-length':len(stdout)}),encoding="utf-8")
        
                conn.send(struct.pack('i',len(resp))) # 发送报头长度
                conn.send(resp)     # 发送报头
                conn.send(stdout)   # 发送具体数据 
        View Code
    • 客户端接受数据
      • 先接受固定长度的报头数据得到报头长度
      • 再按报头长度接受报头,解析报头得到具体内容长度
      • 循环接收具体内容到缓冲区,拼接缓冲区数据
      • 最后再将接收的数据转码
        import struct
        import json
        import socket
        import settings
        
        cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        cli.connect((settings.HOST, settings.TCPPORT))
        
        while True:
            echo = input(":>>").strip()
        
            cli.send(echo.encode('utf-8'))
            if len(echo) == 0:
                continue
            if echo == 'q':
                break
        
            head = cli.recv(4)  # 接收报头长度
            header_bytes = struct.unpack('i', head)  # 解出报头的长度
            head_info = cli.recv(header_bytes[0])   # 接收报头
            header = json.loads(head_info)          # 解析报头
            print(header)
            content_length = header.get('content-length')
            print(content_length, type(content_length))
            recv_length = 0
            content = b'' # 数据缓存区
            while recv_length < content_length:
                if recv_length >= content_length:
                    break
                _content = cli.recv(settings.BUFFSIZE)  # 循环接收数据
                content += _content
                recv_length += settings.BUFFSIZE
            #
            print(content_length, ' = ', len(content), content.decode('gbk')) # 转码接收的数据
        View Code
  • 其他
原文地址:https://www.cnblogs.com/zhujingxiu/p/8352739.html