网络编程(四)

2020-06-25  本文已影响0人  焰火青春

1. 输入 URL 中间经历的过程

回答这个问题应该从以下几个方面入手:

DNS 查询(解析 IP) --> TCP 握手 --> HTTP 请求 --> 反向代理 Nginx --> uwsgi/gunicom(将请求转发到 web 框架层) --> Web APP 响应 --> TCP 挥手

三次握手和四次挥手

用 wireshark 找包工具可以查看

2. TCP 和 UDP 区别

3. HTTP

HTTP 请求

HTTP 请求由:状态行、请求头、消息主体组成,可以使用 httpie 模块查看:

pip install httpie
http baidu.com                            # 发送 get 请求
http -f POST baidu.com hello=world -v       # 发送 post 请求

[图片上传失败...(image-277c36-1593095791043)]

HTTP 响应组成

HTTP 响应组成:状态行、响应头、响应正文

3.1 HTTP 常见状态码

3.2 GET 和 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
no no

3.3 什么是 HTTP 长连接

长连接可以保证 tcp 连接一段时间内不断开,那么它是如何区分不同的请求的呢?

它是通过请求的长度(Content-Length )和传输编码(Transfer-Encoding)来区分

image

3.4 cookie 和 session 的区别

HTTP 是无状态的,即对于服务器来说,每一次请求都认为是一次新的请求(新人),那么如何识别用户呢?

解决:在服务端给用户生成一个标识,然后每次让客户端带过去给后端

4. TCP / UDP 和 socket 编程

image

客户端:

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

sk.sendall(b'Hello World!')
data = sk.recv(1024)
print(data.decode('utf-8'))
sk.close()

服务端:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    print(data.decode('utf-8'))
    conn.sendall('你好!'.encode('utf-8'))
    conn.close()
sk.close()

socket 发送 HTTP 请求

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 80))

http = b'GET /HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n'
s.sendall(http)
buf = s.recv(1024)
print(buf)
s.close()

5. 五种IO 模型

Unix 网络编程中提到了 五种网络模型:

5.1 如何提高并发能力

常见的提升并发能力的方式:

5.2 IO 多路复用

即操作系统提供同时监听多个 socket 的机制,Linux 常见的是:select/poll/epoll,可以用单进程处理多个 socket。

while True:
    envents = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

select/poll/epoll 的区别

image

Python 如何实现 IO 多路复用

具体可参考官网:https://docs.python.org/3/library/selectors.html

5.3 Python 并发网络库

Tornado 框架

Tornado 使用与微服务,实现 RESTful 接口:

import tornado.ioloop
import tornado.web
from tornado.httpclient import AsyncHTTPClient

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(8888)
    tornado.ioloop.IOLoop.current().start()

Gevent

Gevent 是一个高性能的并发网络库:

推荐学习书籍:《Gevent 程序员指南》

import genvent.monkey
genvent.monkey.patch_all()

import gevent
import requests

def fetch():
    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.spwan(fetch, i))
    gevent.joinall(threads)
    
print('Asynchronous:')
asynchronous()

Asyncio 模块

Asyncio 基于协程实现的内置并发网络库:

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(10):
            task = asyncio.ensure_future(fetch(url, session))
            task.append(task)
        responses = await asyncio.gather(*tasks)
        for res_body in responses:
            print(len(resp_body))
            
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(run())
loop.run_until_comlete(future)

6. 总结

思考

编写一个异步爬虫类,使用 gevent 或 asyncio

7. 面试题

1、TCP/IP分别在模型的哪一层

TCP 在传输层(运输层),IP 在网络层(互联网层)。

2、socket长连接是什么意思

TCP/IP
TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层。
在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
在传输层中有TCP协议与UDP协议。;
在应用层有:通过TCP协议来通信的应用层协议包括FTP、HTTP、TELNET、SMTP等 ;
通过UDP协议来通信的应用层协议包括DNS、TFTP等;
短连接
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。

长连接
连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

http的长连接
HTTP也可以建立长连接的,使用Connection:keep-alive,HTTP 1.1默认进行持久连接。HTTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持(貌似最新的 http1.0 可以显示的指定 keep-alive),但还是无状态的,或者说是不可以信任的。

什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

socket解读,http和socket之长连接和短连接区别!

**3、select、poll 和 epoll 的区别

select,poll,epoll都是IO多路复用的机制。同时监听多个 socket 对象,当 socket 对象有变化时(有数据)就通知用户进程

- `select`:单进程支持高并发,跨平台,最大支持 1024 个文件描述符;缺点:多次从内核到应用,应用到内核的数组拷贝
- `poll`:与 `select` 原理相同,由打开文件的上限决定,请求和返回分离,避免每次毒药重设数组,突破 1024 的限制,不能跨平台
- `epoll`:不管是 `poll` 还是 `select` 都需要遍历数组轮询,突破 1024 限制,不能跨平台。无须遍历整个文件描述符,只需遍历被内核 IO 时间异步唤醒,而加入 `ready` 队列的文件描述符。

4、TCP UDP区别;三次握手四次挥手讲一下

1、三次握手

image image

2、四次挥手

image
1、第一次
client 发送一个 FIN ,用来结束连接。client 进程发出连接释放报文,并停止发送数据。释放报文首部:`FIN=1`,序列号 `seq=i`。
此时 client 进入 `FIN_WAIT_1` (终止等待1)状态。

2、第二次
server 收到这个 FIN 后,返回一个 `ACK`(确认),确认序号:`ack=i+1`。同时携带自己的序列号 `seq=j`。
此时, server 进入 `CLOSED_WAIT`(关闭等待)状态。
并通知高层的应用进程,此时处于半关闭状态,client 没有数据发送了,但 server 若发送数据,client 依然会接收,这种状态还会持续一段时间。

3、第三次
server 将最后的数据发送完毕后,发送一个 `FIN`(结束),确认序号:`ack=i+1`,同时携带序号 `seq=w`,准备 关闭 client 的连接,等待 client 的最后确认。
此时,server 进入 `LAST_ACK`(最后确认)状态。

4、第四次
client 发送 `ACK` 确认,并将确认序号+1:`ack=w+1`,而自己序列号 `seq=i+1`。
此时,client 进入 `TIME_WAIT`(时间等待)状态。

> **Note:**此时 client 并没有释放,必须等待 2MSL(最长报文段寿命)使君子后,当 server 撤销相应 TCB 后,从进入 `CLOSED` 状态。server 只要收到了 client 发出的确认,立即进入`CLOSED`状态。同样,撤销TCB后,就结束了这次的TCP连接


**为什么会是四次挥手?**

三次挥手时没有数据传输,而四次挥手时涉及到有数据传输。client 发出关闭请求,表示已经数据传输完毕,但是 server 有可能数据还未传输完毕,这时就需要已 server 端数据是否传输完毕为标准,因此需要四次。

当高并发时,现实情况往往是 server 先断开 client 连接,因为多保存 client 一次连接,就会多占用一些资源。因此在短时间内再次向 server 发起连接,会提示 serve time_wait。


**客户端突然挂掉了怎么办?**

 正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。解决办法是在服务器端设置保活计时器,每当服务器收到

客户端的消息,就将计时器复位。超时时间通常设置为2小时。若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,

还没有响应就认为客户端出了故障,因而终止该连接。

其他答案:

三次握手用于建立连接,四次挥手用于断开连接

**三次握手**

- 首先客户端向服务器发送 `SYN` 报文,请求建立新的连接

- 服务端接收到来自客户端的请求后,结束 `LISTEN` 阶段,回复客户端 `ACK` ,表示确认客户端的报文 `seq` 序号有效,服务端能正常接收客户端发送的数据,并同意创建新的连接
- 客户端接收到 服务端返回的 `ACK` 后,明确了从客户端到服务端的数据传输是正常的,结束 `SYN+SENT` 阶段,也向服务端回复 `ACK` 确认号,并分配资源,这样 `TCP` 连接就建立了

**四次挥手**

- 客户端发送 FIN 报文(终止连接),服务端接收到后,若还有数据未发送完毕,则不会关闭 `socket`,可以继续发送数据。服务端发送 `ACK` 报文(告诉客户端你的请求已收到,但是我还没准备好,请继续等我的消息)
- 此时客户端进入 `FIN_WAIT` 状态,继续等待服务端的 `FIN` 报文;当服务端接收完数据后,则向客户端发送 `FIN` 报文,告诉客户端已经接收完数据,准备好关闭连接
- 客户端接收到 `FIN` 报文后,就可以关闭连接了,但是它不相信网络,怕服务端不知道要关闭,所以又给服务端发送一个 `ACK` 的确认报文,进入了 `TIME_WAIT` 状态,若服务端没有收到 `ACK` 则可以重传
- 服务端接收到 `ACK` 后,就断开连接,客户端等待 `2MSL` 后依然没有收到回复,则证明服务端已关闭连接,那么客户端也断开连接。

**为什么是四次挥手**

关闭连接时,服务端收到 `FIN` 报文时,很可能并不会立即关闭 `socket`,所以只能先回复一个 `ACK` 报文,告诉客户端,你发送的 `FIN` 报文我已经收到,只有等服务端所有的报文都发送完后,才发送 `FIN` 报文,因此不能一起发送,故而需要四次挥手。

因为三次连接的双方的资源,四次的话是要双方都能够安全地确认,都不传输数据了,才释放资源。

**为什么是三次握手**

因为通信是双向的,要保证服务端、客户端的输入输出都是对的,且能够正常传输数据

参考文章:`https://baijiahao.baidu.com/s?id=1654225744653405133&wfr=spider&for=pc`

5、TIME_WAIT过多是因为什么

6、http一次连接的全过程:你来说下从用户发起request——到用户接收到response

客户端发起 http 请求,服务器接收请求,解析请求(请求方法、请求体。。。),服务器根据解析出来的结果,返回相应的值,再由浏览器解析出来,最终呈现给用户查看。

使用 TCP 传输服务:

7、http连接方式。get和post的区别,你还了解其他的方式么?

8、restful你知道么

网站即软件,软件和网络是两个不同的领域,很少有交集。软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

也可以成为表现层状态转化。

9、状态码你知道多少,比如200/403/404/504等等

10、http、https 区别

C/S 架构即客户端/服务端架构,B/S 架构(浏览器与服务端)也是 C/S 架构的一种。

11、OSI 七层

socket 是应用层与传输层(TCP/UDP协议)通讯的中间软件抽象层。它封装了 TCP/UDP协议,并提供一组接口,供开发软件使用。

[图片上传失败...(image-23045c-1593095791043)]

12、手写一个简单的 socket

客户端:

import socket


sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

while True:
    msg = input('>>>').strip()
    sk.send(msg.encode('utf-8'))

    data = sk.recv(1024)
    print(data.decode('utf-8'))

sk.close()

服务端:

import socket


sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)


while True:
    conn, addr = sk.accept()

    while True:
        try:
            data = conn.recv(1024)
            print(data.decode('utf-8'))

            msg = input('>>>').strip()
            conn.send(msg.encode('utf-8'))
        except Exception as e:
            break
    conn.close()

sk.close()

13、介绍下协程,为何比线程还快

进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态。

因为线程会等待,遇到 IO 会阻塞,而协程遇到 IO 阻塞可以去处理别的请求,等 IO 阻塞过去了,可以调用回调函数执行别的操作

14. 异步 IO 和 IO 多路复用

1、异步 IO

2、IO 多路复用

通过一种机制,可以监视多个文件描述符,一旦某个描述符就绪(一般为读或者写就绪),能够通知程序进行相应的读写操作。

Python 中有一个 select 模块,提供了:select、poll、epoll 三个方法,分别调用系统的 select、poll、epoll,从而实现 IO 多路复用。

IO 多路复用用于提升效率,单个进程可以同时监听多个网络连接 IO,可以避免阻塞在 IO 上。原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个 socket 的状态后轮询结果。

IO 模型

15、为何基于 tcp 协议的通信比基于 udp 协议的通信更可靠?

16、什么是 GIL 锁?

全局解释锁,在同一时间内,Python 解释器只能运行一个线程的代码,大大影响了 Python 多线程的性能,而由于历史原因,现在几乎无法消除。

之所以会影响线程性能,是当线程获得一个全局锁的时候,那么该线程的代码才能运行,而全局锁只有一个,所以使用多线程时,同一时刻也只有一个线程在运行,因此始终只能发挥出单核的性能,在如今的多核流行的时代,多线程显得很鸡肋。

17、TCP 和 UDP 的区别?

18、OSI 七层协议,TCP/IP 分别输入哪一层?

socket 是应用层与传输层通讯的中间软件抽象层,它封装了 tcp/udp 协议,并提供一组接口,供开发软件使用。

19、 手写一个 socket

客户端

import socket

sk = socket.socket()
sk.connect(('192.168.131.131', 9999))

while True:
    msg = input('>>>').strip()
    sk.send(msg.encode('utf-8'))
    
    data = sk.recv(1024)
    print(data.decode('utf-8'))
    
sk.close()

服务端

import socket

sk = socket.socket()
sk.bind(('192.168.131.130', 9999))
sk.listen(5)

while True:
    conn, addr = sk.accept()
    while True:
        try:
            data = conn.recv(1024)
            print(data.decode('utf-8'))
            
            msg = input('>>>').strip()
            conn.send(msg.encode('utf-8'))
        except Exception as e:
            break
      conn.close()
    
sk.close()

20、如何提高并发能力?

IO 多路复用

即操作系统提供同时监听多个 socket 的机制,常见的有:select/poll/epoll,可以单进程处理多个 socket,阻塞 IOIO 多路复用区别:

常用并发编程库

Tornado、Gevent、Asyncio

21、进程、线程、协程的区别?

程序运行时,会在内存空间里形成一个独立的内存提,这个内存体有独立的地址空间,有自己的堆,上级挂靠单位是操作系统。操作系统会以进程为单位,分配系统资源。

1、进程

是系统进行资源分配和调度的一个独立单位,拥有独立的内存空间,不同进程间通过进程间通信来通信。由于比较重量,占据独立的内存空间,所以上下文进程间的切换(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对稳定安全。

2、线程

进程的一个实体,CPU 调度和分派的基本单位,不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是可以与同一线程的其他线程共享进程所拥有的全部资源。

线程间通信主要通过共享内存,上下文切换很快,资源开销很小,但相比进程不够稳定易丢失数据。

3、协程

一种用户态的轻量级线程,调度完全由用户自己控制,拥有自己的寄存器、上下文和栈。在调度切换时将寄存器上下文和栈保存到其他地方,切换回来时,恢复先前保存的寄存器、上下文和栈,直接操作栈则基本没有内存切换的开销

因为只有一个线程,不存在同时写变量冲突,可以不加锁的访问全局变量,所以上下文的切换非常快。

总结

并发和并行

22、分布式的理解

上一篇下一篇

猜你喜欢

热点阅读