网络编程

Posted lijianqing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程相关的知识,希望对你有一定的参考价值。

  • 客户端/服务端架构
    1. 我们了解的设计到两个程序之间通讯的应用大致分为两种
      1. 需要安装桌面应用: qq, 微信, 等
      2. 不需要安装就可以使用, 用浏览器可以直接访问的web应用
    2. C/S架构: Client(客户端)与Server(服务端), 这种结构是从用户层面(也就是物理层面)类划分的, 这里的client一般反制客户端应用程序exe, 程序需要先安装, 然后才能运行在用户的电脑上, 对用户的电脑操作环境依赖较大
    3. B/S架构: Browser(浏览器)与Server(服务端), 这种架构也是从用户层面来划分, Browser, 其实也是一种Client(客户端), 只是这个客户端不需要大家去安装什么应用程序, 只需要在浏览器上通过正确的http请求服务器端相关的网页资源, 即可进行使用
  • 什么是网络
    1. 早期网络情况  
      1. 早期: 联机 技术分享图片
      2. 以太网: 局域网与交换机 技术分享图片
      3. 相关协议:
        1. 广播: 主机之间"一对所有"的通讯模型, 网络对其中每一台主机发出的信号进行无条件复制并转发, 所有主机都可以接受到所有信息,(不论你是否需要), 由于其不用路径选择, 所以其网络成本可以很低,有线电视网就是典型的广播行网络, 我们的电视机实际上是接受到所有频道的信号, 但只将一个频道的信号还原成画面.在数据网络中也允许广播的存在, 但其被限制在二层交换机的局域网范围内, 进制广播数据穿过路由器, 防止广播数据影响大面积的主机
        2. ip地址与ip协议: 规定网络地址的协议叫 IP协议, 它定义的地址称之为IP地址, 广泛采用的ipv4, 它规定网络地址由32位2进制表示, 范围是0.0.0.0~255.255.255.255, 一个ip地址同城协程四段十进制数: 例如:127.0.0.1这是本机的ip地址
        3. mac地址: head中包含的源和目录地址由来, Ethernet规定加入internet的设备必须具备网卡, 发送端和接收端的地址便是指网卡的地址, 即mac地址, mac地址: 每块网卡出厂时都被烧制上一个世界唯一的mac地址, 成都为48位2进制, 通常由12位16进制数表示(前六位是厂商编码, 后六位是流水线号)
        4. arp协议: 地址解析协议, (Adderss Resolution Protocol), 是根据IP地址获取物理地址的一个TCP/IP协议, 主机发送信息时, 将包含目标ip地址的ARP请求广播到网络上的所有主机, 并接受返回消息,以此确定目标的物理地址, 收到返回消息后将该ip地址和物理地址存入本机ARP缓存中并保留一定时间, 下次请求时直接查询ARP缓存以节约资源  
        1.  

    2. 广域网和路由器: 技术分享图片 
      1.  路由器(Router):是连接internet中各局域网, 广域网的设备, 它会根据信道的情况自动选择和设定路由, 以最佳路径, 安前后顺序发送信号,  
         路由器是互联网络的枢纽,"交通警察"。目前路由器已经广泛应用于各行各业,各种不同档次的产品已成为实现各种骨干网内部连接、骨干网间互联和骨干网与互联网互联互通业务的主力军。路由和交换机之间的主要区别就是交换机发生在OSI参考模型第二层(数据链路层),而路由发生在第三层,即网络层。这一区别决定了路由和交换机在移动信息的过程中需使用不同的控制信息,所以说两者实现各自功能的方式是不同的。
          路由器(Router)又称网关设备(Gateway)是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网。当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成。因此,路由器具有判断网络地址和选择IP路径的功能,它能在多网络互联环境中,建立灵活的连接,可用完全不同的数据分组和介质访问方法连接各种子网,路由器只接受源站或其他路由器的信息,属网络层的一种互联设备。  
      2. 局域网(Local Area Network, LAN):是指在某一区域内由多台计算机互联成的计算机组, 一般是方圆几千米内, 局域网可以实现文件管理, 应用软件共享, 打印机共享, 工作组内的日程安排,  电子邮件和传真通信服务等功能, 局域网是封闭性的, 可以由办公室内的两台计算机组成, 也可以由一个公司内的上千台计算机组成
      3. 子网掩码: 就是表示子网络特征的一个参数, 形式上等同于ip地址, 也是一个32位二进制数字, 它的网络部分全部为1, 主机部分全部为0, 比如: ip地址172.16.10.1, 如果一直网络部分是前27位, 主机部分是后8位, 那么子网络掩码就是11111111,11111111,11111111,00000000 写成十进制就是255.255.255.0, 知道了"子网掩码"就可以判断,任意两个IP地址是否处在同一个子网络, 方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1, 运算结果为1, 否则为0), 然后比较结果是否相同, 如果是的话, 就表明他们在同一个子网络中, 否则就不是
    3. tcp协议和udp协议
      1. 端口: 一台拥有IP地址的主机可以提供许多服务, 比如web, ftp, smtp等, 这些服务完全可以通过1个IP地址来实现, 那么, 主机是怎么区分不同的网络服务呢, 显然不能只靠IP地址, 因为IP地址与网络服务的关系是一对多的关系, 实际上是通过"ip地址+端口号"来区分不同的服务的
      2. tcp协议: 当应用程序希望通过tcp与另一个应用程序通信时, 它会发送一个通信请求, 这个请求必须被送到一个确切的地址, 在双方‘握手‘之后, tcp将在这两个应用程序之间建立一个全双工 https://baike.baidu.com/item/%E5%8F%8C%E5%B7%A5 (full-duplex)的通信 , 这个全双工的通信将占用两个计算机之间的通信线路, 知道它被一方或双方关闭为止技术分享图片 
        技术分享图片三次握手
      3. 技术分享图片四次握手
      4. udp协议: 当应用程序希望通过dup与一个应用程序通信时, 传输数据之前源端和终端不建立建立, 当它想传送时就简单地去抓取来自应用程序的数据, 并尽可能的把它扔到网络上, qq就是采用的udp协议, 不管你在或不在, 对方想给你发消息随时都可以
      5. 二者对比
        技术分享图片tcp与udp对比

          

    4. 互联网协议与osi模型 技术分享图片 
      1.  
        技术分享图片
        传输层-->四层交换机, 四层的路由器
        网络层-->路由器, 三层交换机
        数据链路层-->网桥, 以太网交换机, 网卡
        物理层-->中继器, 集线器, 双绞线
        每层运行常见物理设备

         

      2. 技术分享图片
        传输层-->tcp与udp协议
        网络层-->ip协议
        数据链路层-->arp协议
        每层运行常见的协议
         
  • 套接字(socket)使用
    1. socket简介:技术分享图片
    2. socket发展史
      1. 套接字起源于29世纪70年代啊加利福尼亚大学伯克利分校版本的unix, 即人们所说的BSD Unix, 因此有时人们也把套接字称为‘伯克利套接字‘或‘BSD套接字‘, 一开始, 套接字被设计用在同一台主机上多个应用程序之间的通讯, 这也被称进程间通讯或IPC, 套接字有两种分别是基于文件型的基于网络型的
      2. 文件类型: AF_UNIX, unix一切皆文件, 基于文件的套接字调用的就是低层的文件系统来采取数据, 两个套接字进行运行在同一机器, 可以通过同一个文件系统间接完成通信
      3. 网络类型: AF_INET(常用)
    3. socket使用基于tcp
      1. 技术分享图片
        import socket
        
        
        def tcp_srv():
            # 1. 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后从新建立的socket
            # 需要用到两个参数
            # AF_INET: 含义同udp一致
            # SOCK_STREAM: 表明是使用的tcp进行通信
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 2. 绑定端口和地址
            # 此地址信息是一个元祖类型内容,元祖分两部分,第一部分为字符串,代表ip,第二部分为端口,是一个整数,推荐大于10000
            addr = ("127.0.0.1", 8998)
            sock.bind(addr)
            # 3. 监听接入的访问socket
            sock.listen()
        
            # 4. 接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路
            # accept返回的元祖第一个元素赋值给skt,第二个赋值给addr
            skt,addr = sock.accept()
            # 5. 接受对方的发送内容,利用接收到的socket接收内容
            # 500代表接收使用的buffersize
            #msg = skt.receive(500)
            msg = skt.recv(500)
            # 接受到的是bytes格式内容
            # 想得到str格式的,需要进行解码
            msg = msg.decode()
        
            rst = "Received msg: {0} from {1}".format(msg, addr)
            print(rst)
            # 6. 如果有必要,给对方发送反馈信息
            skt.send(rst.encode())
        
            # 7. 关闭链接通路
            skt.close()
        
        
        if __name__ == "__main__":
            print("Starting tcp server.......")
            tcp_srv()
            print("Ending tcp server.......")
        基于tcp协议的socket_server
      2. 技术分享图片
        import socket
        
        def tcp_clt():
            # 1. 建立通信socket
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 2. 链接对方,请求跟对方建立通路
            addr = ("127.0.0.1", 8998)
            sock.connect(addr)
            # 3. 发送内容到对方服务器
            msg = "Hello World!"
            sock.send(msg.encode())
            # 4. 接受对方的反馈
            rst = sock.recv(500)
            print(rst.decode())
            # 5. 关闭链接通路
            sock.close()
        
        
        if __name__ == "__main__":
            tcp_clt()
        基于tcp协议的socket_client
    4. socket使用基于udp  

      1. 技术分享图片
        import socket
        
        # 模拟服务器的函数
        def serverFunc():
            # 1. 建立socket
            # socket.AF_INET: 使用ipv4协议族
            # socket.SOCK_DGRAM: 使用UDP通信
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        
            # 2. 绑定ip和port
            # 127.0.0.1: 这个ip地址代表的本机
            # 7852: 随便指定的端口号
            # 地址是一个tuple类型, (ip, port)
            addr = ("127.0.0.1", 7852)
            sock.bind(addr)
        
            # 3. 接受对方的消息
            # 等待方式为死等
            # recvfrom接受返回值是一个元组, 前一项表示数据, 后一项表示地址
            # 参数的含义是缓冲区大小
            # rst = server_sock.recvfrom(500)等效于下面
            data, addr = sock.recvfrom(500)
        
            print("服务器接受的数据: ", data)
            print(type(data))
            # 发送过来的数据是bytes格式, 必须通过解码才能得到str格式内容
            # decode默认参数是UTF-8
            text = data.decode("utf-8")
            print(text)
            print(type(text))
        
            # 4. 给客户端返回消息
            rsp = "我不饿!"
        
            # 发送的数据需要编码成bytes格式
            # 默认编码是utf-8
            data = rsp.encode("utf-8")
            sock.sendto(data, addr)
        
        
        if __name__ == __main__:
            print("Starting server..........")
            serverFunc()
            print("Ending server.............")
        基于udp协议的socket_server
      2. 技术分享图片
        import socket
        
        # 模拟客户端
        def clientFunc():
            # 1. 创建一个client
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        
            # 2. 发送内容到指定服务器
            text = "Hello World"
        
            # 发送数据必须是bytes格式
            data = text.encode("utf-8")
        
            # 发送
            sock.sendto(data, ("127.0.0.1", 7852))
        
            data, addr = sock.recvfrom(500)
        
            text = data.decode("utf-8")
        
            print(text)
        
        if __name__ == __main__:
            clientFunc()
        基于udo协议的socket_client
    5. socket参数的详解:socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
      技术分享图片
      family: 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。(AF_UNIX 域实际上是使用本地 socket 文件来通信)
      type: 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
      SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 
      SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
      proto:协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
      fileno: 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。
      与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。
      这可能有助于使用socket.close()关闭一个独立的插座。
      参数说明
  • 黏包
    1. 黏包成因:   
  • socket的更多方法介绍
    1. 技术分享图片
      服务端套接字函数
      s.bind()    绑定(主机,端口号)到套接字
      s.listen()  开始TCP监听
      s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来
      
      客户端套接字函数
      s.connect()     主动初始化TCP服务器连接
      s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
      
      公共用途的套接字函数
      s.recv()            接收TCP数据
      s.send()            发送TCP数据
      s.sendall()         发送TCP数据
      s.recvfrom()        接收UDP数据
      s.sendto()          发送UDP数据
      s.getpeername()     连接到当前套接字的远端的地址
      s.getsockname()     当前套接字的地址
      s.getsockopt()      返回指定套接字的参数
      s.setsockopt()      设置指定套接字的参数
      s.close()           关闭套接字
      
      面向锁的套接字方法
      s.setblocking()     设置套接字的阻塞与非阻塞模式
      s.settimeout()      设置阻塞套接字操作的超时时间
      s.gettimeout()      得到阻塞套接字操作的超时时间
      
      面向文件的套接字的函数
      s.fileno()          套接字的文件描述符
      s.makefile()        创建一个与该套接字相关的文件
      更多方法
    2. 技术分享图片
      官方文档对socket模块下的socket.send()和socket.sendall()解释如下:
      
      socket.send(string[, flags])
      Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.
      
      send()的返回值是发送的字节数量,这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
      
      –
      
      socket.sendall(string[, flags])
      Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.
      
      尝试发送string的所有数据,成功则返回None,失败则抛出异常。
      
      故,下面两段代码是等价的:
      
      #sock.sendall(‘Hello world
      ‘)
      
      #buffer = ‘Hello world
      ‘
      #while buffer:
      #    bytes = sock.send(buffer)
      #    buffer = buffer[bytes:]
      send和sendall方法
        
  • 验证客户端链接的合法性
  • socketserver
    • 技术分享图片
      import socketserver
      class Myserver(socketserver.BaseRequestHandler):
          def handle(self):
              self.data = self.request.recv(1024).strip()
              print("{} wrote:".format(self.client_address[0]))
              print(self.data)
              self.request.sendall(self.data.upper())
      
      if __name__ == "__main__":
          HOST, PORT = "127.0.0.1", 9999
      
          # 设置allow_reuse_address允许服务器重用地址
          socketserver.TCPServer.allow_reuse_address = True
          # 创建一个server, 将服务地址绑定到127.0.0.1:9999
          server = socketserver.TCPServer((HOST, PORT),Myserver)
          # 让server永远运行下去,除非强制停止程序
          server.serve_forever()
      server端
    • 技术分享图片
      import socket
      
      HOST, PORT = "127.0.0.1", 9999
      data = "hello"
      
      # 创建一个socket链接,SOCK_STREAM代表使用TCP协议
      with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
          sock.connect((HOST, PORT))          # 链接到客户端
          sock.sendall(bytes(data + "
      ", "utf-8")) # 向服务端发送数据
          received = str(sock.recv(1024), "utf-8")# 从服务端接收数据
      
      print("Sent:     {}".format(data))
      print("Received: {}".format(received))
      client端

以上是关于网络编程的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

使用 Pygments 检测代码片段的编程语言

面向面试编程代码片段之GC

如何在 Django Summernote 中显示编程片段的代码块?