20 网络编程

OSI七层协议:

七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

五层划分为:应用层、传输层、网络层、数据链路层、物理层。
传输层:TCP协议和UDP协议   

UDP(User Data Protocol)用户数据报协议, 是⼀个⽆连接的简单的⾯向数据报的传输层协议。 UDP不提供可靠性, 它只是把应⽤程序传给IP层的数据报发送出去, 但是并不能保证它们能到达⽬的地。 由于UDP在传输数据报前不⽤在客户和服务器之间建⽴⼀个连接, 且没有超时重发等机制, 故⽽传输速度很快。常见的有:语音通话、视频通话、实时游戏画面 等。

TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接,然后再进行收发数据。常见有:网站、手机APP数据获取等。

 

# 1. 简述 二层交换机 & 路由器 & 三层交换机 的作用。
"""
二层交换机:构建局域网并实现局域网内数据的转发。
路由器:实现跨局域网进行通信。
三层交换机:具备二层交换机和路由的功能。

2.简述常见词:IP、子网掩码、DHCP、公网IP、端口、域名的作用。
IP,本质上是一个32位的二进制,通过 . 等分为了48位二进制。
子网掩码,用于指定IP的网络地址和主机地址。
DHCP,网络设备中的一个服务,用于给接入当前网络的电脑自动设置 IP、子网掩码、网关。
公网IP,一般企业拉专线时会给固定的公网IP,只有具备公网IP才可以被互联网上的其他电脑访问。
端口,IP用于表示某台电脑,端口则表示此电脑上的具体程序。0-65535
域名,与IP构造对应关系,方便用户记忆。

 

 

软件架构:C/S : client                    server => 服务端
B/S : browser:浏览器           server => 服务端

本质:B/S架构也是C/S架构       

C/S架构,是Client和Server的简称。开发这种架构的程序意味着你即需要开发客户端也需要开发服务端。

          例如:你电脑的上QQ、百度网盘、钉钉、QQ音乐 等安装在电脑上的软件。服务端:互联网公司会开发一个程序放在他们的服务器上,用于给客户端提供数据支持。
          客户端:大家在电脑安装的相关程序,内部会连接服务端进行收发数据并提供 交互和展示的功能。

B/S架构,是Browser和Server的简称。开发这种架构的程序意味着你开发服务端即可,客户端用用户电脑上的浏览器来代替。例如:淘宝、京东等网站。
            服务端:互联网公司开发一个网站,放在他们的服务器上。
            客户端:不需要开发,用现成的浏览器即可。

简而言之,B/S架构就是开发网站;C/S架构就是开发安装在电脑的软件。

TCP和UDP协议
  TCP协议的特点:
    1. 数据可靠传输
    2. 速度相对于UDP协议较慢
 UDP协议的特点:
    1. 数据不可靠
    2. 速度相对于TCP协议较快
    
 TCP协议的三次握手和四次挥手

 

  • 最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。

 

  1. TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
  2. TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
  3. TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
  4. TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
  5. 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

 

  • 数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。
  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗ *∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

 

 

 

 

 

 

 

 

 

本机的IP地址:127.0.0.1
socket抽象层:就是一个个的对外访问的接口



 

 

socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

我们经常把Socket翻译为套接字,Socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用

套接字分类

  • 基于文件类型的套接字家族:AF_UNIX
  • 基于网络类型的套接字家族:AF_INE

套接字工作流程

 

 

 

套接字函数

一、服务端套接字函数

  • s.bind() 绑定(主机,端口号)到套接字
  • s.listen() 开始TCP监听
  • s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

二、客户端套接字函数

  • s.connect() 主动初始化TCP服务器连接
  • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

三、公共用途的套接字函数

  • s.recv() 接收TCP数据
  • s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
  • s.sendall() 发送完整的TCP数据(本质就是循环调用
  • send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
  • s.recvfrom() 接收UDP数据
  • s.sendto() 发送UDP数据
  • s.getpeername() 连接到当前套接字的远端的地址
  • s.getsockname() 当前套接字的地址
  • s.getsockopt() 返回指定套接字的参数
  • s.setsockopt() 设置指定套接字的参数
  • s.close() 关闭套接字

四、面向锁的套接字方法

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

五、面向文件的套接字的函数

  • s.fileno() 套接字的文件描述符
  • s.makefile() 创建一个与该套接字相关的文件
  • 六、基于tcp的套接字
  • 服务端
    
    import socket
    
    # 1. 实例化socket对象
    
    # 参数不传递默认代表是TCP协议
    server = socket.socket(type=socket.SOCK_STREAM)
    
    # 2. 绑定
    server.bind(('127.0.0.1', 8001))
    
    # 3. 监听, 括号中是半连接池
    server.listen(5)
    
    # 4. 接收客户端发来的信息
    print("正在等待客户端发来的消息:")
    
    # sock:当次链接对象,addr:客户端的地址, ip+port
    sock, addr = server.accept()
    
    # 5. 拿到数据, 一次最多接收1024个字节的数据
    data = sock.recv(1024)
    print(data)
    
    # 6. 发送客户端的数据
    sock.send(data.upper())
    
    # 7. 断开链接
    sock.close()
    
    # 8.
    server.close()
    客户端
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8001))
    
    # 向服务端发送数据
    client.send(b'hello baby')
    
    # 接收服务端发来的数据
    data = client.recv(1024)
    print(data)
    client.close()

    实现通信循环

  • 服务端
    import socket
    
    # 1. 实例化socket对象
    
    # 参数不传递默认代表是TCP协议
    server = socket.socket(type=socket.SOCK_STREAM)
    
    # 2. 绑定
    server.bind(('127.0.0.1', 8002))
    
    # 3. 监听, 括号中是半连接池
    server.listen(5)
    
    # 4. 接收客户端发来的信息
    print("正在等待客户端发来的消息:")
    
    while True:
    # sock:当次链接对象,addr:客户端的地址, ip+port
        sock, addr = server.accept()
    
        while True:
            # 5. 拿到数据, 一次最多接收1024个字节的数据
            try:
                data = sock.recv(1024)
                print(data)
    
                # 6. 发送客户端的数据
                sock.send(data.upper())
            except Exception as e:
                print(e)
                break
    
    
        # 7. 断开链接
        sock.close()
    
    # 8.
    server.close()
    
    
    客服端
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8002))
    
    while True:
    # 向服务端发送数据
        input_data = input('请输入数据:').strip()
    
        # client.send(b'hello baby')
        client.send(input_data.encode('utf-8'))
    
        # 接收服务端发来的数据
        data = client.recv(1024)
        print(data)
    client.close()

     

  • 七、基于udp的套接字
  • 服务端
    import socket
    
    # SOCK_STREAM : tcp协议的服务端
    # SOCK_DGRAM :udp协议的服务端
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 数据报协议-》UDP
    server.bind(('127.0.0.1', 8080))
    
    while True:
        data, client_addr = server.recvfrom(1024)
        print('===>', data, client_addr)
        server.sendto(data.upper(), client_addr)
    
    server.close()
    
    客户端
    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 数据报协议-》UDP
    
    while True:
        msg = input('>>: ').strip()  # msg=''
        client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
        data, server_addr = client.recvfrom(1024)
        print(data)
    
    client.close()

     

  • 粘包

    两台电脑在进行收发数据时,其实不是直接将数据传输给对方。

    • 对于发送者,执行 sendall/send 发送消息时,是将数据先发送至自己网卡的 写缓冲区 ,再由缓冲区将数据发送给到对方网卡的读缓冲区。

    • 对于接受者,执行 recv 接收消息时,是从自己网卡的读缓冲区获取数据。

    所以,如果发送者连续快速的发送了2条信息,接收者在读取时会认为这是1条信息,即:2个数据包粘在了一起。例如:

  • # socket客户端(发送者)
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8001))
    
    client.sendall('alex正在吃'.encode('utf-8'))
    client.sendall(''.encode('utf-8'))
    
    client.close()
    
    
    # socket服务端(接收者)
    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 8001))
    sock.listen(5)
    conn, addr = sock.accept()
    
    client_data = conn.recv(1024)
    print(client_data.decode('utf-8'))
    
    conn.close()
    sock.close()

     

  • 如何解决粘包的问题?
  • 每次发送的消息时,都将消息划分为 头部(固定字节长度) 和 数据 两部分。例如:头部,用4个字节表示后面数据的长度。

    • 发送数据,先发送数据的长度,再发送数据(或拼接起来再发送)。

    • 接收数据,先读4个字节就可以知道自己这个数据包中的数据长度,再根据长度读取到数据。

    对于头部需要一个数字并固定为4个字节,这个功能可以借助python的struct包来实现:

  • import struct
    
    # ########### 数值转换为固定4个字节,四个字节的范围 -2147483648 <= number <= 2147483647  ###########
    v1 = struct.pack('i', 199)
    print(v1)  # b'\xc7\x00\x00\x00'
    
    for item in v1:
        print(item, bin(item))
    
    # ########### 4个字节转换为数字 ###########
    v2 = struct.unpack('i', v1) # v1= b'\xc7\x00\x00\x00'
    print(v2) # (199,)

     

posted @ 2021-08-31 22:53  甜甜de微笑  阅读(34)  评论(0编辑  收藏  举报