网络编程

1.简述OSI七层协议
  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层
2.简述TCP/IP四层协议
  • 应用层: http
  • 传输层:Tcp
  • 网络层: IP
  • 数据链路层 : 以太网
3.TCPUDP的区别是什么?
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的通知机制需要很多函数回调

posted @ 2019-12-28 10:05  千亿  阅读(87)  评论(0编辑  收藏  举报