• python基础之网络及网络编程


    一、网络基础认知


    1.1网络认识

        (1)操作系统:调用硬件资源的,硬件----操作系统----程序 
        (2)网络基础
                物理层:(电缆 双绞线  无线电波)---二边通过这个线只能相互发高低电压,高是1 低是0
                数据链路层:(把物理层得到的0101封装成组,多少位为一组
                         eg:以太网的格式定义:一组电信号构成一个数据包叫帧,每一数据帧分成:报头head和数据data两部分
                            head(包含发送者地址6个字节,接收者地址6个字节,数据类型6个字节)
                            data(数据包具体内容,最短46个字节,最长1500个字节)
                            head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
                            head包含的源和目标地址:ethernet规定接入internet的设备都必须具备网卡,mac地址是每块网卡出厂
                            被烧制上世界唯一的mac地址,长度为48位2进制,12位16进制(6个字节02:88:65:3e:a1:ec)一个字节是一个组合,一个组合
                            就可以被认为与ASSIC对应,后来跟unicon来对应
                     数据链路层如何发送? 
                         物理层决定了是隔离的,在一个局域网里所有的节点都用线连在一起,在这层通信只能用广播的方式
                         广播(广播域):发送自己是MAC及目标MAC及数据,这样所有的人都能看到,占资源。如何不隔离?看网络层
                     arp协议来获取对方的mac地址?
                     mac地址学习?
                     
                网络层:定义了IP协议,点分十进制来表示IP,每个点段都是8位二进制,2的32次方,4亿多IP
                     ipv4用完了,所以出现了ipv6,IP分为网络位与主机位
                     ip协议:为了跨子网(局域网)通信,如下
                          网络部分:在哪个局域网,通过子网掩码来算哪些位是网络部分哪些是主机部分,IP与掩码每二进制相乘
                          主机部分:局域网里的哪个主机
                     局域网之间如何互联:通过路由器连不同网络,通过路由表来找其它局域网并找到对应的主机,路由器如何知道这个主机
                                         这时用的就是MAC通信,通过广播来找(发出去用的是广播,到另一个网络还是用广播)
                                         
                     包:分为head和data部分
                          head:20到60个字节
                          data:最长为65515字节
                          所以以太网数据包的‘数据’部分最长只有1500字节,所以IP数据包超过1500字节它就要分割成几个以太网数据包分开发送
                          以太网头(最外层,这里的MAC一直会再变,不断的换路由器的MAC最后才是真目标MAC)--ip头---ip数据
                     arp协议:在同一网络时
                            数据链路head                              data
                         源mac 目标mac(FF:FF:FF:FF:FF:FF) 源ip  目标ip   数据---当目标地址看目标Ip是自己时就返回自己的mac,第一次要拆到Ip,
                         但设备收到后返回数据,有自己的MAC与目标MAC这就是数据链路层拆到MAC就可以,若是在本局域网就不用走路由
                     
                     网络层要连路由器(二层交换机有路由功能)
                         路由器之间通过路由表来连接 
                     
                传输层:
                        网络层只能找到对应主机,如何通信?
                        基于端口的传输 (源MAC 目标MCA (源IP 目标IP (head(源端口 目标端口)   data)))
                        1.端口:就是0-65535,0-1023被系统占用,这些端口号与网卡绑定起来,每一个程序起来就会在网卡上有个固定的端口号
                        2.TCP/UDP
                            套接字是网卡IP与端口绑定就叫套接字
    
                                     用户进程----------------------------应用层 
                                         |
                                  socket抽象层(进程端口与网络IP绑定,一个端口一个服务)
                                  |    |     |
                                  |      TCP    UDP-------------------------传输层 
                                  |           |
                                ICMP      IP-----------------------------网络层
                                        |
                                ARP    硬件接口--------------------------链路层    
                                        |
                                       硬件                                 
    
                        3.TCP协议要三次握手和四次断开,有链接的而UDP无链接
                            TCP报文:源端口 目标源口 序号(不固定) 确认号(每通信一个加个1) 数据偏移(多少位是什么数据)
                                  保留(扩展用) syn(发起新链接) ack:确认  fin请求断开   窗口(按什么尺寸发) 检验和(检验数据有没有改过)
                                  紧急指针(是否紧急处理)  选项与填充   data                              
                     
                            三次握手:
                                  客                    服
                                 1 syn=1 seq=x--------->(客的套接字)
                                 2    <--------------ack=1+x 3 syn=1 seq=y(服务的套接字)
                                 4 ack=1+y------------->
                                实际2与3合一起做了二件事,syn=2与ack 
                                虽然发起了二次连接,实际是客户端的套接字,客与服都在这一条路上申请,能相互发数据
                            四次断开
                                有二个连接都是要断开
                                客           服
                               fin=1 seq=x------->
                                 <---------ack=1+x
                                <---------fin=1    seq=y                 
                                ack=1+y------> 
                        4.udp没链接而TCP有链接 
                             
             
                应用层:
                    1.ftp  http 等协议         
             
             
                    总结:
                        1.设备             
                         传输层:四层交换机、四层的路由器
                         网络层:路由器  三层交换机
                         数据链路层:网桥  以太网交换机 网卡
                         物理层:中继器 集线器  双绞线
                        2.数据过程
                        用户数据--装应用层头--TCP头-->IP头-->MAC头
                        3.由于以太网MAC最多能传1500个字节(MTU值,网卡的传输限制),所以数据为4600个字节就要分四次发
                           MAC头  IP头  TCP头   data(1500)
                           MAC头  IP头  TCP头   data(1500)
                           MAC头  IP头  TCP头   data(1500)
                           MAC头  IP头  TCP头   data(100)
             
                  
        (3)dhcp:是UDP,局域网里要有dhcp服务器(家里的DHCP是集成在路由器里的,还有二层交换层是工作在数据链路层的MAC通信,三层交换器集成了路由功能,它即能路由也能MAC通信)    
                dhcp工作模式:
                  新设备开机要获取IP
                 以太网头:DHCP的MAC    FF:FF:FF:FF:FF:FF
                    传输层:      0.0.0.0    255.255.255.255---是一个获取IP的包
                 udp头:      发送方68    接收方67
                 
                 广播发出去,所有的设备都能拿到,只有dhcp服务器拿到打开IP后才知道是找自己的
                dhcp服发数据给客,有地址池是租的IP发给客端,IP 掩码 网关IP都是dhcp服务器发给这个客户端的
        (4)DNS的IP(DNS服务器是通过UDP协议传的,根就封装在UDP协议中) 
               1.域名解析
        (5)子网划分
                A类地址:网络是前8位  0*******.主机地址(共有126个网络(1-126),2的24次方个主机,127开头的IP都不可以用)  
                B类址:网络位是前16位 10******.********.主机地址(前一位128-191,后一位网络可变)
                C类地址:网络位前24位 110*****.********.********.主机地址(前一位192-223,后二位网络位可变) 
                D类地址:1110MMMM.多播组.多播组.多播组
                E类地址:11110RRR.保留.保留.保留
              私有址私:
                 A类私有地址:10.0.0.0-10.255.255.255(有一个A类私有地址)
                 B类私有地址:172.16.0.0-172.31.255.255(第二位可变网络位,所以有16个网段可做为私有)
                 C类私有地址:192.168.0.0-192.168.255.255(前二位固定,第三位是可变网络位,所以有255个网段可所私有)
                    私有地址作用:私有地址不能路由的,只有公网的才能路由,所以私有地址要能映射到公网地址,是通过NAT方式
                                  映射
                    内网                                                  网络
                    私2---NAT---公1(网关)      路由         公2---NAT----私2
                    私3                                                    私3
                    
                    
                内网发出去到网关,网关会把源IP转换掉做了SNAT,到达对方私网,对方会把目标IP转换掉,网关做了DNAT转换
        (6)子网划分 
                   eg:C类向主机会借二位,主机就变少了但网络就变多了,子网掩码
                    11111111.11111111.11111111.11000000(255.255.255.192)
                    借二位多出几个网段:2^2-2=2---00与11不能做为网络
               如:A类地址:一个网络有2^24-2个主机,广播通信太难,所以向主机借几位,这样A类网络变多了,可以多卖,主机也不会那么多
                   所以要用子网掩码算出网络,IP一样不一定是同一个网络的,划分子网后,子网之间是不同网络不能直接通信,需要路由器
            注:为什么前后二个IP不能用,主机位都是0的表这个网络的网段,主机位都是1的表这个网络的广播地址(eg:客向dhcp发广播就用的是广播IP)
        (7)vlan是交换机上划分网络
             如一个交换机上有10个网口,5个做为一组,vlan1与vlan2,由于二层是不隔离的能广播通信,所以不同的vlan连不用网络
          
            192.168.1.1/25---vlan1---192.168.1.130/25    
            这是二个子网所以这二个网络就不能通信了,虽然是在一个vlan上但是不能通信

    二、网络通信


     2.1 网络编程三要素

        网络通信三要素:ip(找到主机)    端口(定位进程,找进程)      协议(语言规范来通信,TCP/UDP)

    2.2 socket编程流程图

            TCP服务端                                                   TCP客户端
            socket()--创建socket                                        socket()--创建socket
               |                                                            |
             bind()--为socket绑定IP与端口                                     |
               |                                                            |
             Listen()--监听设置端口等待客端请求                                 |
               |                                                            |
             Accept()--ACcept阻塞,直到有客端过来                              |       
               |                                                         connect()--连接指定计算机端口
               |<------------------------------------------------------------
               |
              Recv()<----------------------------------------------------send()
               |
               |
               |
              Send()------------------------------------------------------>Recv()
               |                                                            |
               |                                                            |
               close()---关闭socket                                         close()---关闭socket

      服务端编程框架:

         serve.py---如下
           import socket---二台设备连接的中介(通道),这是个模块,是应用层与传输层的抽象层,二个设备都要创建 
                           源码是socketserver.py
           #family=AF_INET---是ipv4的参数,服务器之间的通信 AF_UNIX:Unix不同进程之间的通信,AF_INET6:ipv6
           #type=SOCK_STREAM----TCP的参数,SOCK_Dgram---数据socket,是UDP参数
            以上这二个参数是socket里的_init_里定义好的
    
    
           sk=socket.socket()----创建套接字对象,上面已有二个参数定好了,直接创建socket对象,默认是Ipv4与tcp,要换可以改参数,套接字
           address=('127.0.0.1',8888 )
           sk.bind(address)---绑定了IP与端口
           sk.listen(3)----监听,默认等待3个,再多就接不上了,客户端就报错了
           conn,addr=sk.accept()----阻塞在这只要没人连他就不向下走,等待客来接,里面是元组,一个是客户端socket对象的地址一个是IP+端口
           当客户端连后就进行后面的通信,这之前三次握手已完成,我们只写握手之后的,三次握手不是我们写的
           recv()---收数据
           send()--发数据
           sendall()---如果发不完就用这个会一直发,在python3中这里参数一定要是bytes类型
           发数据到客:
           conn.send(bytes('约啊','utf8'))-----这个conn就是通道
           conn.close()这个只关一个客户端  sk.close()---全关了
           sk.settimeout()---超时时间
           sk.fileno()--文件描述符

      客户端框架:

        client.py -----当server运行后,会卡在那边当client启动后server才能再运行
           import socket
           sk1=socket.socket()---socket对象
           address=('127.0.0.1',8888)---服务端的IP+端口
           sk1.connect(address)   
           连上server后就开始向server请求
           recv()---收数据
           send()--发数据
           sendall()---如果发不完就用这个会一直发
           收服务端的数据
           data=sk1.recv(1024)---收到1024个字节,这个sk1是conn
           print(str(data,'utf-8'))---变为unicon,把bytes类型,在Python3里str编码的是unicon
           sk1.colse()

      实例之聊天软件:

            client.py
                import socket 
                sk1=socket.socket()
                print(sk1)
                address=('127.0.0.1',8888)
                sk1.connect(address)//联上服务端
                while True:
                    inp=input('>>>')
                    if inp == 'exit'---退出,这时不给server发信息了,但server.py还在等,处理如下
                        break
                    sk1.send(bytes(inp,'utf-8'))
                    data=sk1.recv(1024)
                    print(str(data,'utf8'))
                sk.close()
    
    
            ****如上如果多于3个等待其它客户端会报错,如何做到不报错
            server.py
                import socket
                sk=socket.socket()
                address=('127.0.0.1',8888 )
                sk.bind(address)
                sk.listen(3)    ------等待3个,再多客端就会报错,只有连的那个客端断开了这三个才能聊天          
                conn,addr=sk.accept()
                while True:
                    try:
                        data=conn.recv(1024)----超过的3个报错是这步产生的,想让不产生就捕捉这个错误让他退出循环重新请求
                    except Exception as e:
                        break                  
                    if not data:----这时客发的是空的
                        conn.close()
                        conn,addr = sk.accept()-----可接受其它客户端
                        print(addr)
                        continue
                    print(str(data,'utf8'))---看到client发的信息
                    inp=input('>>>')
                    conn.send(bypes(inp,'utf8'))
                sk.close()        

      实例之ssh,客户端向服务端发命令

            client.py
                import socket 
                sk1=socket.socket()
                print(sk1)
                address=('127.0.0.1',8888)
                sk1.connect(address)
                while True:
                    inp=input('>>>')----拿到的是str的
                    if inp == 'exit'---退出,这时不给server发信息了,但server.py还在等,处理如下
                        break
                    sk1.send(bytes(inp,'utf-8'))
                    result_len1=int(str(sk1.recv(1024),'utf8'))---收到一个数字,是一个byte类型,转成str类型是union值,再转成int值,
                    data=bytes()----是一个空bytes
                    while len(data)!= result_len1:----len(data)与result_len1都是Int类型
                        data=sk1.recv(1024)
                        data+=sk1.recv(1024)
                      
                    #data=sk1.recv(1024)---会出现的问题,如果这个结果很大,这里只能接受1024个,太大会有问题,直接改recv也不行,有些视频很多改也不行,只能计算cmd_result有多大,看上处理如何循环接受数据
                    print(str(data,'gdk'))---把data变为字符串类型
                sk.close()
        
        过程:客输入dir命令,服收到subprocess.Popen,执行后结果用send发出去,注,数据只能传bytes
        
        
    
            server.py如何改?
                  import subprocess----调用shell命令的模块,里面只有一个类Popen
                  import socket
                  import time
                  sk=socket.socket()
                  address=('127.0.0.1',8888 )
                  sk.bind(address)
                  sk.listen(3)    ------等待3个,再多客端就会报错,只有连的那个客端断开了这三个才能聊天          
                  conn,addr=sk.accept()
                  while True:
                      try:
                            data=conn.recv(1024)----超过的3个报错是这步产生的,想让不产生就捕捉这个错误让他退出循环重新请求,data接受的都是byte类型,
                      except Exception as e:
                         break                  
                      if not data:----这时客发的是空的
                         conn.close()
                         conn,addr = sk.accept()-----可接受其它客户端
                         print(addr)
                         continue
                      print(str(data,'utf8'))---看到client发的信息,把bytes解码出来成str
                        //相当于把客户端的数据放在处理shell模块里会执行
                      obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)--对象,data是接受客户端的值,它要是个字符串,所以要解成字符串,
                                                                                                 这是个地址,这里就在本地执行了shell命令                  
                      cmd_result=obj.stdout.read()--会出现的问题,如果这个结果很大,是bytes类型,是上步转成bytes的,因为是在windows里执行的所以默认是gbk编码的
                      result_len=(bytes(str(len(cmd_result),'utf8')--是一个长度,是int类型不能直接转成byte,要先转成str
                      conn.sendall(result_len)
                      time.sleep(0.5)---由于前后二个发送数据会有粘包现象,这里可以给个暂时,用下面的粘包来解决
                      conn.send(cmd_result)
                      print(str(obj.stdout.read)),'gbk')---结果是byte类型转为str类型,所以要用gbk解码
                  sk.close()

    2.3  粘包

    2.4 编码拾遗

            py3只有二种数据类型:str(存unicode)
                                 bytes(存16进制)
                s='hello丹丹'------存在内存里的是unicode,是str类型,但存的是unicode编码
                但传输内容与存在磁盘里只能用bytes类型,因为bytes与底层更近,所以要把s转成bytes类型,这个是编码,如何转?
                bytes(s,'utf8')-----bytes类型每国语言都能编码,gdk utf8等,但utf8是公认的,都能按自己的规则编码
                =s.encode('utf8')
    
            还有一种方式:字符串方法encode   decode
                b=bytes(s,'utf8')=s.encode('utf8')----由于bytes可用utf8  gdk等来编码
                str(b,utf8)---bytes用utf8解码=b.decode('utf8')
            把b做成gdk    
               b1=s.encode('gbk')

    2.5 实例之上传文件

      1.实例

            post_client(执行时要有个命令与文件名)--如post|11.jpg
                import socket 
                import os
                sk1=socket.socket()
                print(sk1)
                address=('127.0.0.1',8888)
                sk1.connect(address)
                BASE_DIR=os.path.dirname(os.path.abspath(__file__))              
                while True:
                    inp=input('>>>').strip()            
                    cmd,path=inp.split('|')--以|分隔--path可以是文件也可以是一个路径
                    path=os.path.join(BASE_DIR,path)
                    filename=os.path.basename(path)
                    file_size=os.stat(path).st_size---文件大小
                    file_info='post|%s|%s'%(filename,file_size)
                    sk.sendall(bytes(file_info,'utf8'))
                    with open(path,'rb') as f:----rb,这个是字节类型发数据,把文件里的数据取出来当字符串发过去,rb是读出字节数据,wb是写入字节类型
                    has_sent=0
                        while  has_sent!=file_size:
                            data=f.read(1024)--每1024取一次
                            sk.sendall()                      
                            has_sent+=len(data)--data的长度
                    print('上传成功')
    
            post_server
                 import subprocess
                 import socket
                 import time
                  sk=socket.socket()
                  address=('127.0.0.1',8888 )
                  sk.bind(address)
                  sk.listen(3)    
                  conn,addr=sk.accept()
                  BASE_DIR=os.path.dirname(os.path.abspath(__file__))              
                  while True:
                      conn,addr = sk.accept()
                      while 1:
                            data=conn.recv(1024)
                            cmd,filename,filesize=str(data,'utf8').split('|')                        
                            path=os.path.join(BASE_DIR,'new','filename')
                            filesize=int(filesize)
                            f=open(path,'ab')
                            has_receice=0
                            while  has_receice!=filesize: 
                                data=conn.recv(1024)
                                f.write(data)
                                has_receive+=len(data)
                            f.close
            框架:把重复的内容用框架调用,如上创建socket的过程

      2.搭socket框架并实现并发的过程

            server并发例:
              server.py
                 import socketserver
            3步     class MyServer(socketserver.BaseRequestHandler):---继承socketserver的一个类
                         def handle(self):---父类有这个方法,要重写父类的方法,这个就是需求的内容,是listen之后的内容
                             print("服务端启动")
                             while True:
                                 conn=self.request-------------就是接收的客户端的sk
                                 print(self.client_address)
                                 while True:
                                     client_data=conn.recv(1024)
                                     print(str(client_data,'utf8'))
                                     print('wait...')
                                     conn.sendall(client_data)
                                conn.close()
                 if __name__ == '__main__'
            1步       server=socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)--调的这个类是多线程TCP服务端的,这里创建socket等过程,这就是个框架,它封装到listen这步,默认listen是5个,这步可走ThreadingTCPServer的构造方法    
            2步       server.server_forever()----启动程序从server里的方法开始,    里面会实例化Myserver,里面没有__init__,会去找父类的__init__执行,父类时会调用handle,并发是在这里实现的            
            结果:这时多个客户端能跟server聊天了,多个客户端能同时跟服务器聊天,服务器是多线程的了,每个客户端都有一个线程接受conn 

      3.socketserver是如何实现上面这个并发过程的

        有5个server类:

          class ThreadingTCPServer(ThreadingMixIn,TCPServer): pass----TCPserver类,二个父类,是TCP协议的socket连接的类,用于windows

          class ThreadingUDPServer(ThreadingMixIn,UDPServer)----UDP协议的socket连接

          class ForkingTCPServer(ThreadingMixIn,TCPServer)---用于Linux的TCPserver

          class ForkingUDPServer(ThreadingMixIn,UDPServer)

          class BaseServer:*****:是一个原生类没有父类

  • 相关阅读:
    Android之退出整个应用方法
    自定义popupwindow(解决位置控制困惑)
    日期格式转换
    简单用于测试的listview的视图
    复制res下文件进sd卡
    自定义九宫格式的弹出menu
    动画隐藏或者显示控件
    截取p3片段
    微信几种动画进入退出应用
    codeforce Round On Sum of Fractions + stl 的应用
  • 原文地址:https://www.cnblogs.com/Dana-xiong/p/14317129.html
Copyright © 2020-2023  润新知