网络编程
1.简述OSI
七层协议
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
2.简述TCP/IP
四层协议
- 应用层:
http
- 传输层:
Tcp
- 网络层:
IP
- 数据链路层 : 以太网
3.TCP
和UDP
的区别是什么?
TCP | UDP |
|
---|---|---|
可靠性 | 可靠 | 不可靠 |
连接性 | 面向连接 | 无连接 |
报文 | 面向字节流 | 面向报文 |
效率 | 传输效率低 | 传输效率高 |
双工 | 全双工 | 一对一、一对多 |
流量控制 | 有(滑动窗口) | 无 |
拥塞控制 | 有(慢开始、拥塞避免、快重传、快恢复) | 无 |
4. TCP连接建立的时候3次
seq是数据包本身的序列号 占4字节
ack
是确认号 占4字节
确认ACK
占一位,仅当ACK
=1,确认号字段才有效 ACK
=0,确认号无效
先来分析三次握手:
第一次握手 客户端发送SYN包(同步序列编号)(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认。
第二次握手 服务器收到SYN包,确认客户的SYN,发送确认包ACK
(ack=x+1
),同时自己也发送一个SYN包(Seq=y),即SYN+ ACK
包,此时服务器进入SYN_RECV
状态
第三次握手 客户端收到服务器的SYN+ ACK
包,向服务器发送确认包ACK(ACK=K+1)
,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据
5. TCP四次挥手关闭连接
FIN 连接释放报文,希望断开连接
由于TCP连接是全双工的,因此每个方向都必须单独关闭。这原则上是当一方完成它的数据发送任务后就发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到FIN后仍能发送数据。首先进行关闭的一方执行主动关闭,而另一方执行被动关闭。
1.客户端进程发出FIN(连接释放报文),并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN_WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2.服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1
,并且带上自己的序列号seq=v,此时服务端进入CLOSE-WAIT(关闭等待)状态。此时客户端向服务器的方向就释放了,就处于半关闭状态,即客户端没有数据要发送了,但服务器若发送数据,客户端仍要接受。这个状态持续一段时间。
3。服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=v+1
,服务器进入LAST-ACK(最后确认)状态,等待客户端的确认。
4.客户端收到服务器的释放报文后,发出确认包,ACK=1,ack=w+1
,而自己的序列号是seq=u+1,此时,客户端就进入了TIME_WAIT(时间等待)状态。此时TCP还没有释放,必须经过2**MSL
(最长报文段寿命)的时间后,客户端撤销相应的TCB
(传输控制块),才进入CLOSED状态
常见面试题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK
报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL
(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK
丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK
报文。在Client发送出最后的ACK
回复,但该ACK
可能丢失。Server如果没有收到ACK
,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK
。Client会在发送出ACK
之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL
的时间。如果在该时间内再次收到FIN,那么Client会重发ACK
并再次等待2MSL
。所谓的2MSL
是两倍的MSL
(Maximum Segment Lifetime)。MSL
指一个片段在网络中最大的存活时间,2MSL
就是一个发送和一个回复所需的最大时间。如果直到2MSL
,Client都没有再次收到FIN,那么Client推断ACK
已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
先来分析三次握手:
第一步客户端发送SYN,第二步服务端发送ACK说明客户端发送能力,服务端接收能力OK。
第三部客户端发送ACK说明客户端接受能力,服务端发送能力OK,连接则是可靠的。
如果是两次:
客户端知道自己的接受发送能力和服务端的接受发送能力都OK,而服务端只知道自己的接受能力和客户端的发送能力OK,其余的都不知道,连接不可靠
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
6、什么是socket?简述基于tcp
协议的套接字通信流程
socket(简称套接字)是进程间通信的一种方式,能实现不同主机间的进程间通信。
- 流程
- 通信双方实例化
tcpsocket
对象,服务端绑定ip
端口并执行listen()方法进入监听状态 - 客户端执行connect()方法请求连接,双方完成三次握手建立连接
- 服务端执行accept()方法获得连接对象,双方实现通信
- 通信结束执行close()方法关闭连接
- 通信双方实例化
7、为什么基于tcp
协议的通信比基于udp
协议的通信更可靠
TCP:面向连接,对方给了确认收到信息,才发下一个,如果没有收到确认信息就重发,数据不会丢失
UDP
: 无连接,一直发数据,不需要对方回应,可能会造成数据丢失。
8、大规模连接上来
12. 大规模连接上来,并发模型怎么设计?
多进程:
开启多个进程为客户端服务,同一时刻可为多个客户端提供服务,但是任务量大会因为创建进程的开销影响服务器性能。
多线程:
一个进程内开启多个线程,同一时刻只能为一个客户端服务,I/O等待的时间可以进行别的任务,不会浪费时间,不影响服务器性能,推荐使用。
协程:
协程的优势在于函数入口可以是上次停止的地方,显然对大规模连接没什么帮助。
所以这种情况推荐使用多线程来设计并发模式。
9、ARP
协议
地址解析协议(Address Resolution Protocol) :根据IP
地址获取MAC
地址的一个TCP/IP
协议
OSI模型把网络工作分为七层,IP地址在OSI模型的第三层,MAC地址在第二层,彼此不直接打交道。在通过[以太网](https://baike.baidu.com/item/以太网)发送IP数据包时,需要先封装第三层(32位IP地址)、第二层(48位MAC地址)的报头,但由于发送时只知道目标IP地址,不知道其MAC地址,又不能跨第二、三层,所以需要使用地址解析协议。使用地址解析协议,可根据网络层IP数据包包头中的IP地址信息解析出目标硬件地址(MAC地址)信息,以保证通信的顺利进行
10、心跳机制
- 在网络通信中,经常会出现客户端和服务端的非正常断开,需要检测链接状态。心跳机制就是定时发一个心跳包,让对方知道自己活着,以确保连接有效性的机制.
- 心跳包很小,或者只包含头的一个空包。
- 在TCP的机制里面,本身是存在有心跳机制的,默认是两小时的心跳频率。
- 总的来说,心跳包主要用于长连接的保活和断线处理。一般应用,判定时间在30-40比较不错,如果要求很高,那就在6-9秒。
11、长连接短连接
HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次分手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
HTTP首部的Connection: Keep-alive是HTTP1.0浏览器和服务器的实验性扩展,当前的HTTP1.1 RFC2616文档没有对它做说明,因为它所需要的功能已经默认开启,无须带着它,但是实践中可以发现,浏览器的报文请求都会带上它。如果HTTP1.1版本的HTTP请求报文不希望使用长连接,则要在HTTP请求报文首部加上Connection: close。《HTTP权威指南》提到,有部分古老的HTTP1.0 代理不理解Keep-alive,而导致长连接失效:客户端-->代理-->服务端,客户端带有Keep-alive,而代理不认识,于是将报文原封不动转给了服务端,服务端响应了Keep-alive,也被代理转发给了客户端,于是保持了“客户端-->代理”连接和“代理-->服务端”连接不关闭,但是,当客户端第发送第二次请求时,代理会认为当前连接不会有请求了,于是忽略了它,长连接失效。书上也介绍了解决方案:当发现HTTP版本为1.0时,就忽略Keep-alive,客户端就知道当前不该使用长连接。其实,在实际使用中不需要考虑这么多,很多时候代理是我们自己控制的,如Nginx代理,代理服务器有长连接处理逻辑,服务端无需做patch处理,常见的是客户端跟Nginx代理服务器使用HTTP1.1协议&长连接,而Nginx代理服务器跟后端服务器使用HTTP1.0协议&短连接。
在实际使用中,HTTP头部有了Keep-Alive这个值并不代表一定会使用长连接,客户端和服务器端都可以无视这个值,也就是不按标准来,譬如我自己写的HTTP客户端多线程去下载文件,就可以不遵循这个标准,并发的或者连续的多次GET请求,都分开在多个TCP通道中,每一条TCP通道,只有一次GET,GET完之后,立即有TCP关闭的四次握手,这样写代码更简单,这时候虽然HTTP头有Connection: Keep-alive,但不能说是长连接。正常情况下客户端浏览器、web服务端都有实现这个标准,因为它们的文件又小又多,保持长连接减少重新开TCP连接的开销很有价值。
以前使用libcurl做的上传/下载,就是短连接,抓包可以看到:1、每一条TCP通道只有一个POST;2、在数据传输完毕可以看到四次握手包。只要不调用curl_easy_cleanup,curl的handle就可能一直有效,可复用。这里说可能,因为连接是双方的,如果服务器那边关掉了,那么我客户端这边保留着也不能实现长连接。
如果是使用windows的WinHTTP库,则在POST/GET数据的时候,虽然我关闭了句柄,但这时候TCP连接并不会立即关闭,而是等一小会儿,这时候是WinHTTP库底层支持了跟Keep-alive所需要的功能:即便没有Keep-alive,WinHTTP库也可能会加上这种TCP通道复用的功能,而其它的网络库像libcurl则不会这么做。以前观察过WinHTTP库不会及时断开TCP连接。
12、IO多路复用之select、poll、epoll详解
select poll epoll 区别
-
单个进程打开的文件描述符个数(fd文件句柄)
select :32位1024 ,64位2048
poll:稍微比select好一些,没有限制,原因是基于链表来存储的
epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接
-
监听socket的方式(socket就是一个客户端到服务器端的连接)
select :轮询,一个一个的检查,看有没有活跃,每次调用时都会对连接进行线性遍历
poll:同上
epoll : 会将连接的socket注册到epoll里面,相当于socket的花名册,如果一个socket活跃了(来消息了),会回调一个函数,会通知epoll 赶紧过来处理。
-
内存空间拷贝方式,消息传递方式
select :需要将数据从内核态拷到用户态(内核态就是操作系统),这个过程比较耗时
poll:同上
epoll :内存态数据和用户态数据是共享的,共享一块内存来实现的。
一般来说epoll性能最好,但是在连接数较少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调