网络编程部分

8.1 网络编程基础概念

  1. 网络应用开发架构:

    • c/s 即 client (客户端) / server (服务端)

      飞秋、git、百度云、输入法、qq、迅雷

    • b/s 即 browser(浏览器)/ server(服务器)

      淘宝、邮箱、百度、知乎、豆瓣、博客园

    • b/s 是特殊的 c/s 架构

  2. 网卡:每一个实际存在在计算机硬件里的

  3. mac地址:每一块网卡上都有一个全球唯一的mac地址

  4. 交换机:是连接多台机器并帮助通讯的物理设备,只可以识别mac地址

  5. 协议:两台物理设备之间对于要发送的内容,长度和顺序做的一些规范

  6. ip地址(规格):

    • ipv4协议,位的点分十进制,32位的2进制表示

      范围:0.0.0.0 ~ 255.255.255.255

    • ipv6协议,8位的冒分十六制,128位十进制来表示 (点分十进制不足以表示)

    • 范围:0:0:0:0:0:0:0:0 ~ FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF

  7. ip地址使用:

    • 如果一个ip地址想被所有人都看到,这个ip地址是必须要申请的,即公网IP

    • 提供内网ip,供局域网使用:

      192.168.0.0 - 192.168.255.255

      172.16.0.0 - 172.31.255.255

      10.0.0.0 - 10.255.255.255

  8. 交换机实现的arp协议(地址解析协议)

    • 通过ip地址获取一台机器的mac地址

    • 交换机工作原理:收到一个请求,通知所有连接他的ip地址,获取对应ip地址的mac地址并返回给请求的ip地址

  9. 网关ip:一个局域网的网络出口,访问局域网以外的区域都需要经过路由器的网关

  10. 网段: 一般是一个末尾为0的地址段

  11. 子网掩码: 判断两个机器是否在同一个网段

    屏蔽一个IP地址的网络部分的“全1”比特模式。对于A类地址来说,默认的子网掩码是255.0.0.0;对于B类地址来说默认的子网掩码是255.255.0.0;对于C类地址来说默认的子网掩码是255.255.255.0。

    通过将请求的计算的子网掩码和两个要匹配的计算机的二进制按位与运算

    ip = 255.255.255.255
    11111111.11111111.11111111.11111111
       192.168.12.87
       11000000.10101000.00001100.00000111
       192.168.12.7
  12. ip地址可以确认一台机器,port端口可以确认一台机器的一个应用,一般有65535个端口

  13. 一般实现互联,使用127.0.0.1 是自己的地址 不过交换机 ip地址是可以过交换机的

8.2 tcp协议

8.2.1 tcp协议特点

  1. 面向连接的,可靠,但是慢,可以实现全双工通信,即双方都是实时的,区别于半双工(传呼机)

  2. 无边界,流式传输(导致粘包问题)

  3. 长连接:会一直占用双方的端口

  4. 能够传输的数据长度几乎没有限制

8.2.2 三次握手和四次挥手

  1. 具体的三次握手(连接方式):

    • accept接受过程中等待客户端的连接

    • connect客户端发起了一个syn连接请求(附带了一个随机数)

    • 如果得到了server端的响应ack的同时还会再收到一个由server端发来的syn链接请求

    • client端进行回复ack之后,就建立起了一个tcp协议的链接

    • 备注:三次握手的过程再代码中是由accept和connect共同完成的,具体的细节再socket中没有体现出来

  2. 具体的四次挥手(断开连接方式):

    • server和client端对应的在代码中都有close方法

    • 每一端发起的close操作都是一次fin的断开请求,得到'断开确认ack'之后,就可以结束一端的数据发送

    • 如果两端都发起close,那么就是两次请求和两次回复,一共是四次操作

    • 可以结束两端的数据发送,表示链接断开了

8.2.3 应用场景

  • QQ和微信等上面的传输压缩文件,缓存下载的电影等等

    from socket import socket
    
    tcp_server = socket()
    tcp_server.bind(('127.0.0.1', 8000))
    tcp_server.listen()
    conn, _ = tcp_server.accept()
    msg = conn.recv(1024)
    print(msg.decode('utf-8'))
    conn.send(b'hello')
    conn.close()
    
    tcp_client = socket()
    tcp_client.connect(('127.0.0.1', 8000))
    tcp_client.send(b'hello world')
    msg = tcp_client.recv(1024)
    print(msg.decode('utf-8'))
    tcp_client.close()
    

8.2.5 tcp协议的粘包问题及解决

8.2.5.1 原因概述
  • 出现原因:dcp协议本身的工作原理导致,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

  • 传输数据过小,当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。即合包机制

  • 传输数据过大,当我们提交一段数据给TCP发送时,TCP选择进行拆包(不然无法发送,最大发送长度1500),然后逐个发送,即拆包机制

  • dcp协议本身的特性:无边界和流式传输,导致合包机制和拆包机制

  • 流式传输:传递过程中通过交换机,dcp协议本身无边界,但是在内部发送有拆解,拆解后逐步发送,导致接受有滞后,即接收方已经读取数据,还有数据在传输过程无法被读取,表象是接受到的大文件 会比发送方的文件略小。

  • 有固定的准确的接受长度时,不会出现粘包问题,如发送4字节,收4字节。

8.2.5.2 解决方法
  • 发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处理时可以根据长度来判断每条数据的开始和结束。方法就是通过struct方法

    • 即自定义协议

    • 首先发送报头,报头固定是4个字节

    • 报头内容是即将发送的报文的字节长度

    • 之后接收端按照字节长度接受报文

  • 第二种方法就是格式化数据:每条数据有固定的格式(开始符、结束符),这种方法简单易行,但选择开始符和结束符的时候一定要注意每条数据的内部一定不能出现开始符或结束符;(老师没讲)

8.3 udp协议

#server
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('192.168.12.46',8000))
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
msg = input('>>>')
sk.sendto(msg,addr)
sk.close()
#client
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
msg = input('>>>').encode('utf-8')
sk.sendto(msg,('192.168.12.46',8000))
msg = sk.recv(1024).decode('utf-8')
print(msg)
sk.close()

8.3.1 udp协议特点

  1. 面向数据报的,无连接,速度很快,类似于发短信,能实现一对一,多对一,一对多的高效通讯

  2. 由于没有回执,对于发送信息和接受信息的双方来说,可能存在丢失消息的情况

  3. 能够传递的长度有限,是根据数据传递设备的位置有关系

8.3.2 应用场景

  • 通信类的 如QQ 微信,发短信, 在线观看小视频等

8.4 osi七层模型

  • 总结:应表会传网数物, 即应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

  • 一般的五层协议:(5>>>>1)

    • 应用层 :https/http/ftp/smtp协议 所有的应用层协议是基于tcp或者是udp协议

    • 传输层:tcp协议和udp协议, 物理设备:端口 四层路由器,四层交换机

    • 网络层:ipv4/ipv6 协议, 物理设备: 路由器,三层交换机(交换机具有路由功能)ip通过dns解析获取 (dns 域名和ip相互映射的数据库)

    • 数据链路层: mac地址, arp协议 物理设备:网卡,交换机

    • 物理层

  • 你的电脑先在应用层打包一个 HTTP报文,然后在传输层在打包成 TCP报文,然后再根据 DNS 查到的 IP 在网络层打包成 IP数据报,然后在通过链路层打包成以太网数据帧,发送给你的交换机:

  • 你的交换机收到后,重新包装数据帧,再发送给你的路由器:

你的路由器利用 NAT(Network Address Translation),将你的主机IP(局域网IP)转换为外网IP,还会修改端口号,对外完全隐藏你的主机,再根据路由表选择一条合适的路径进行转发:

  • 在接下来的过程中,每个节点都只改变 MAC 地址,然后在网络中一路向着目的地发送

8.5 socket(套接字)

  • 通过python的socket模块完成socket的功能

  • 位置:工作在应用层和传输层之间的抽象层,可以帮我们完成所有信息的组织和拼接

  • 历史:最开始是同一个机器上的两个服务间的通信,现在是基于网路的多台机器之间的多个服务通信

    #服务端:
    import socket
    sk = socket.socket()
    sk.bind(('192.168.12.46',8000))
    sk.listen()
    connect,address = sk.accept()
    while True:
        content = input('请输入')
        connect.send(content.encode('utf-8)'))
        if content.upper() == 'N':
            break
        msg = connect.recv(1024)
        if (msg.decode('utf-8')).upper() == 'N':
            break
        print(msg.decode('utf-8'))
    connect.close()
    sk.close()
    
    
    #用户端
    import socket
    sk = socket.socket()
    sk.connect(('localhost',8000))
    while True:
        msg = sk.recv(1024)
        print(msg.decode('utf-8'))
        content = input('请输入')
        sk.send(content.encode('utf-8)'))
        if content.upper() =='N':
            break
    sk.close()
    

8.6 struct模块

  • 作用:根据输入的值,转换为一个长度为4的,每个都是16进制的数字,在解决粘包过程中,可以使用此模块先判断长度,再读取。

  • 注意:转成4长度的数字时,使用pack,pack有两个参数,第一个是指输入的类型,‘i'表示int,即整型,第二个是输入的信息。

  • unpack代表转回,输出的方式一定是(num,) 需要索引[0]来取值

    import struct
    ret = struct.pack('i',1000)   #i 对应 1000
    print(ret)                    #b'\xe8\x03\x00\x00'
    print(struck.unpack('i',ret)) #(1000,) 
    print(struck.unpack('i',ret)[0]) #1000
    

8.7 socketserver模块

  • 基本格式:见下

  • 作用:接受多个用户,并可以同时发送

'服务器'
class Myserver(socketserver.BaseRequestHangdler):
   def handle(self):
       msg = self.request.recv(1024).decode('utf-8')   #这里的self.request == conn
       self.request.send('1',encode('utf-8'))
server = socketserver.ThreadingTCPServer(('192.168.12.46',8000),Myserver)
server.server_forever()  #不用关闭,永远提供服务

'用户'
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
sk.send(b'hello,wusir')
msg = sk.recv(1024)
print(msg)
sk.close()

8.8 io模型

8.8.1 io定义

I: input O:output 输入输出都是相对内存来说

  • write send 输出 output

  • read recv 输入 input

8.8.2 阻塞io模型

  • 正常的socket模块中,服务器或者接收端遇到accep/recv都会等待另一方发送或接通。没有信息接收就会一直等待,即阻塞。

8.8.3 非阻塞io模型

  • 定义:服务器在接通后,遇到accept/recv都不在等待,没有就直接跳过,有的话就会接收

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(False)   #非阻塞
sk.listen()

conn_l = []
del_l = []
while True:
   try:
       conn,addr = sk.accept()   # 阻塞,直到有一个客户端来连我
       print(conn)
       conn_l.append(conn)  #将所有的连接加到一个列表,方便调用
   except BlockingIOError:
       for c in conn_l: #遍历列表
           try:
               msg = c.recv(1024).decode('utf-8')
               if not msg:
                   del_l.append(c) #当conn接受不到信息的时候,就把这个conn 剔除,不再遍历他,列表循环删除先添加到另一个列表,在删除
                   continue
               print('-->',[msg])
               c.send(msg.upper().encode('utf-8'))
           except BlockingIOError:pass
       for c in del_l:
           conn_l.remove(c) #删除
       del_l.clear() #清空列表,防止删除重复
sk.close()
# socket的非阻塞io模型 + io多路复用实现的
# 虽然非阻塞,提高了CPU的利用率,但是耗费CPU做了很多无用功(在没有接受消息的时候,就不停的在while循环)

8.8.4 io多路复用

  • io多路复用:操作系统提供的 网络对象监听的代理

  • io多路复用

    • select: 操作系统会帮助程序对需要监听的所有对象进行轮询,一旦有数据来,就会把对应的对象返回 。由于底层数据结构,select能够代理的网络对象是非常有限的 。随着监听对象增多,效率也会降低

    • poll 机制 轮询 对底层数据结构进行了优化,能够代理的网络对象没有限制

    • epoll 机制,采用的是回调机制 无论监听多少个对象 效率都是一样的

import select
import socket

sk = socket.socket()
sk.setblocking(False)
sk.bind(('127.0.0.1', 9000))
sk.listen()

while True:
   rlst = [sk]
   rl, wl, el = select.select(rlst, [], [])  #写 读 异常
   for obj in rl:
       if obj is sk:  #判断是conn还是sk对象 如果是conn就直接recv 如果是sk就accept
           conn, _ = obj.accept()
           rlst.append(conn)
       else:
           msg = obj.recv()
           if msg:
               print(msg.decode('utf-8'))
               obj.send(msg.upper().encode('utf-8'))
           else:
               rlst.remove(obj)
posted @ 2019-11-02 17:06  Kn19ht  阅读(106)  评论(0)    收藏  举报