网络相关:协议、网络编程、IO多路复用、并发网络库

《Python服务端工程师面试宝典-PegasusWang》学习笔记,第五章:TCP/UDP/HTTP概念、网络编程、IO多路复用、并发网络库
360阅读 · 2020-6-1 22:49发布

5.1 TCP和UDP

浏览器输入一个url中间经历的过程是什么?

  1. DNS查询
  2. TCP握手
  3. HTTP请求
  4. 反向代理Nginx
  5. uwsgi/gunicorn
  6. web app响应
  7. TCP挥手

TCP三次握手过程?

TCP四次挥手过程?

TCP和UDP的区别?

  • TCP是面向连接、可靠的、基于字节流。
  • UDP是无连接、不可靠、面向报文。

5.2 HTTP

HTTP请求的组成?

  • 状态行
  • 请求头
  • 空行
  • 请求主体

HTTP响应的组成?

  • 状态行
  • 响应头
  • 空行
  • 响应正文

HTTP常见状态码?

  • 1**信息。服务器收到请求,需要请求者继续执行操作。
  • 2**成功。操作被成功接受并处理。
  • 3**重定向。需要进一步操作完成请求。
  • 4**客户端错误。请求有语法错误或者无法完成请求。
  • 5**服务器错误。服务器在处理请求的过程中发生错误。

HTTP GET/POST的区别?

  • Restful语义上一个是获取,一个是创建。
  • GET是幂等的,POST非幂等。
  • GET请求参数放到url(明文),有长度限制。
  • POST请求参数放在请求体,更安全。

什么是幂等性?

  • 幂等方法是无论调用多少次都得到相同结果的HTTP方法。
  • 例如:a=4是幂等的,但是a+=4就是非幂等的。
  • 幂等的方法客户端可以安全地重发请求。
HTTP方法 是否幂等 是否安全(是否修改数据)
OPTIONS yes yes
GET yes yes
HEAD yes yes
PUT yes no
POST no no
DELETE yes no
PATCH no no

什么是HTTP长连接?

  • HTTP 1.1中实现了长连接。
  • 短连接:建立连接、传输数据、关闭连接(频繁建立和关闭会导致开销大)
  • 长连接:通过Connection:Keep-alive请求头。保持TCP连接不断开。
  • 长连接通过Content-Length(请求长度)或Transfer-Encoding:chunked(分段发送)来区分HTTP请求。

cookie和session的区别?

  • Session一般是服务器生成之后将sessionid交给客户端(通过url参数或者cookie)
  • Cookie是实现session的一种机制,通过HTTP cookie字段实现。
  • Session通过在服务器保存sessionid识别用户(例如服务端在redis中根据sessionid存储用户id,每次通过sessionid获取用户信息),cookie存储在客户端。

5.3 网络编程

TCP编程

  • 代码示例:

    # tcp_server.py
      import socket
      import time
    
      s = socket.socket()
      s.bind(('',8899))
      s.listen()
    
      while True:
          client,addr = s.accept()  # 返回conn和地址
          print(client)
          timestr = time.ctime(time.time()) + '\r\n'
          client.send(timestr.encode())  # 需要将send的参数encode字节
          client.close()
    
    # tcp_client.py
      import socket
      # 创建客户端socket
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 协议簇,类型是TCP/UDP
      s.connect(('127.0.0.1',8899))
      s.sendall(b'hello python')
      data = s.recv(1024)
      print(data.decode())
      s.close()
    

使用socket发送HTTP请求

  • 代码示例:

    import socket
      s = socket.socket()
      s.connect(('www.baidu.com',80))
    
      http = b"GET / HTTP/1.1\r\n Host: www.baidu.com\r\n\r\n"
      s.sendall(http)
      buf = s.recv(1024)
      print(buf)
      s.close()
    

5.4 IO多路复用

五种IO模型

  • Unix网络编程中提到了5种网络模型
  • Blocking IO(阻塞式IO)
  • Noblocking IO(非阻塞式IO)
  • IO multiplexing(IO多路复用)
  • Signal Driven IO(信号驱动IO)(不常用)
  • Asynchronous IO(异步IO)(不常用)

如何提升并发能力?

  • 多线程模型,创建新的线程处理请求。
  • 多进程模型,创建新的进程处理请求。
  • IO多路复用,实现单进程同时处理多个socket请求。

什么是IO多路复用?

  • 操作系统提供的同时监听多个socket的机制。
  • 为了实现高并发需要一种机制并发处理多个socket。
  • Linux常见的是select/poll/epoll。
  • 可以使用单线程单进程处理多个socket。

Python如何实现IO多路复用?

  • Python的IO多路复用基于操作系统实现(select/poll/epoll)
  • Python2 select模块
  • Python3 selectors模块

IO多路复用相关知识点

  • 建议了解阻塞式IO的请求流程、IO多路复用执行流程。
  • IO多路复用简述:执行select调用时,内核会等待数据,一但返回了socket可读的时候,recvfrom会直接拿到数据。select可以同时处理多个socket,有一个就绪应用程序代码就可以处理它。
  • IO多路复用常用格式:
    while True:
          events = sel.select()
          for key, mask in events:
              callback = key.data
              callback(key.fileobj, mask)
    
  • 建议了解select/poll/epoll的区别。他们最主要的区别是应用程序索引就绪文件描述符的时间复杂度,epoll是O(1),另外2个是O(n)。
  • selectors模块:
    事件类型EVENT_READ,EVENT_WRITE
      DefaultSelector:自动根据平台选取合适的IO模型
          register(fileobj, events, data=None)
          unregister(fileobj)
          modify(fileobj, events, data=None)
          select(timeout=None): return[(key, events)]
          close()
    
  • 建议查看python官方文档中selectors模块的介绍和示例代码。

5.5 Python并发网络库

常用的并发网络库有哪些?

  • Tornado开源并发网络库,同时也是一个web微框架。提供基于回调基于协程的并发逻辑的编写方式。
  • Gevent绿色线程(greenlet)实现并发,猴子补丁修改内置socket(例如将阻塞socket修改为非阻塞socket)。
  • Asyncio Python3内置的并发网络库,基于原生协程。

Tornado框架

  • Tornado适用于微服务,实现Restful接口。
  • 底层基于Linux多路复用。
  • 可以通过协程或者回调实现异步编程。
  • 生态不完善,相应的异步框架比如ORM不完善。
  • 示例代码:

    import tornado.ioloop
      import tornado.web
      from tornado.httpclient import AsyncHTTPClient
      import platform
    
      if platform.system() == "Windows":
          import asyncio
    
          asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
      class APIHandler(tornado.web.RequestHandler):
          async def get(self):
              url = 'http://httpbin.org/get'
              http_client = AsyncHTTPClient()
              resp = await http_client.fetch(url)
              print(resp.body)
              return resp.body
    
      def make_app():
          return tornado.web.Application([
              (r"/api",APIHandler),
          ])
    
      if __name__ == "__main__":
          app = make_app()
          app.listen(8998)
          tornado.ioloop.IOLoop.current().start()
    

Gevent

  • 基于轻量级绿色线程(greenlet)实现并发。
  • 需要注意monkey patch,gevent修改内置的socket改为非阻塞。
  • 用于配合gunicorn和gevent部署作为wsgi server。
  • 示例代码:

    """实现并发爬虫"""
      import gevent.monkey
      gevent.monkey.patch_all()
    
      import gevent
      import requests
    
      def fetch(i):
          url = 'http://httpbin.org/get'
          resp = requests.get(url)
          print(len(resp.text),i)
    
      def asynchronous():
          threads = []
          for i in range(1,10):
              threads.append(gevent.spawn(fetch,i))
          gevent.joinall(threads)
    
      print('Asynchronous:')
      asynchronous()
    

Asyncio

  • Python3引入到内置库,协程+事件循环。
  • 生态不够完善,没有大规模生产环境检验。
  • 目前应用不够广泛,基于Aiohttp可以实现一些小的服务。
  • 示例代码:

    """基于aiohttp并发请求"""
      import asyncio
      from aiohttp import ClientSession
    
      async def fetch(url, session):
          async with session.get(url) as response:
              return await response.read()
    
      async def run(r=10):
          url = "http://httpbin.org/get"
          tasks = []
    
          async with ClientSession() as session:
              for i in range(r):
                  task = asyncio.ensure_future(fetch(url, session))
                  tasks.append(task)
              responses = await asyncio.gather(*tasks)
              for index,resp_body in enumerate(responses):
                  print(len(resp_body))
    
      loop = asyncio.get_event_loop()
      future = asyncio.ensure_future(run())
      loop.run_until_complete(future)