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:*****:是一个原生类没有父类

原文地址:https://www.cnblogs.com/Dana-xiong/p/14317129.html