关于网络(同步、异步、阻塞、非阻塞,select/poll/epoll,rpc/msgqueue,tcpip常见面试题)

1、先浅谈同步和异步:

同步和异步关注的是消息通信机制

所谓同步,就是在发出一个”请求或者调用“时,在没有得到结果之前,这个"请求或者调用"就不返回。但是一旦调用返回,那就是肯定得到返回值

所谓异步,"请求或者调用"发出之后,就直接返回了,不会有任何返回值,返回值由被调用者,通过状态、通知、回调函数等等方式来通知调用者


沿用网上众多通俗例子之一:

你打电话问书店老板有没有《分布式系统》这本书:
如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。

如果是异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调


Linux的三种典型的网络多IO复用方式,select、poll、epoll均属于同步, 注意这个同步,是针对于获取的数据而言他们都需要在读写事件就绪后自己负责进行读写
为什么经常说epoll是“异步”的呢?这只是因为对于epoll,数据是否就绪的这个事情如何知道,相比select和poll,同步的判断工作少,感觉起来像“异步”。
事实上它们都是同步方式的网络多IO复用方式,都需要调用通过各自API,同步陷入内核去发现数据是否就绪。
他们是同步的,且可以认为是非阻塞的。

2、先浅谈阻塞与非阻塞:
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞调用是指:调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回

非阻塞调用是指:不能立刻得到结果之前,该调用不会阻塞当前线程


沿用上面例子:

你打电话问书店老板有没有《分布式系统》这本书:

如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,

如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果


将一个socket文件描述符,调用recv

设置为阻塞时间为1秒、或5秒、或无限(-1):内核发现当前没有数据,等待1秒、5秒、一直等

设置为非阻塞,不管有没有都立即返回。


3、浅谈同步异步与阻塞非阻塞:

阻塞与非阻塞本身,与是否同步异步无关,这是两个事情。但确有影响

如上例:你的如何等待(阻塞与非阻塞),跟老板通过什么方式回答你结果(同步或异步),无关。但你是会等还是不等,怎么选择呢。

1、同步:一定要等到结果,势必会阻塞。不存在所谓“同步非阻塞”

2、异步:不需要立即获取到结果,完全没必要阻塞。不存在所谓"异步阻塞"


4、一个网上的最恰到好处的例子:

小张喜欢喝咖啡,同时养了好多狗;

出场:

1. 小张:相当于我们的客户端进程

2. 小狗大黑:阻塞处理的IO函数

3. 小狗大黄:非阻塞处理的IO函数

4. 小狗大白、大红:异步处理的IO函数


同步阻塞:小张派大黑去看咖啡煮好没,大黑等咖啡煮开了才回来;

伪代码:read(fd, recv, recv_len, -1);


同步非阻塞:小张派大黄去看咖啡煮好没,大黄看了一眼就回来了,过了一会,大黄再去看看咖啡煮好没;

伪代码:

while (1) {

res = read(fd, recv, recv_len, 0);

if (res) {

break;

}

sleep(1);

}


"异步"非阻塞:小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,大白就回来告诉小张,大红已经到厨房啦;

过了一会咖啡煮好了,大红喊大白,大白再告诉小张;

伪代码:select/poll/epoll的伪代码

此“异步”非文中1中的异步


"异步"阻塞:小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,一起在那等着;

过了一会咖啡煮好了,大红大白一起回到客厅告诉小张;

其他的情况



结论:

1、只有异步IO不会阻塞线程。但平时用的select、poll、epoll,还是直接的read/write,都是同步IO

Linux的网络应用基本上不涉及异步IO(AIO未关注),也就是说数据的最终读写都得是同步的。

2、问题根源是同步阻塞会拉住线程不放,同步非阻塞则显得很低效

5、select、poll、epoll:

select、poll、epoll,事实上更多解决的是很多个网络IO的统一管理问题,即“多路IO复用”的解决方案;

试想如有10个客户端会请求我们的服务器,并假设没有select、poll、epoll这些系统调用,那么必须自己设法形成一种机制,

至少能不断的检查10个socket文件fd,是否处于可读、可写的状态。而select、poll、epoll就是这个机制的实现。


select的方式:

1、应用程序阻塞的调用select,将服务器监听的端口、全部已创建tcp连接的客户端的端口,即fd_set大集合,通过select系统调用,复制给内核;

2、内核遍历这些端口,找到在协议栈里是活跃的fd们。方式为,置位,细节见fd_set数据结构;

3、内核回复给应用程序fd_set大集合,select遍历fd_set大集合,处理可能发生了的listen、recv事件

缺点:

1、fd_set大集合的传递:用户态和内核态的复制,慢

2、内核需要遍历全部fd_set大集合的每一个fd,用这种方式判断哪些fd是就绪的,慢

3、因为上面1和2都慢,再加上阻塞的select调用,导致应用程序线程也慢,慢


poll的方式:

除以下,与select基本相同。

1、解除了select的最大连接数仅为1024的限制;

2、增加了水平触发机制,即内核回复了某fd的就绪事件,如应用程序未处理,下次还会报。这相当于epoll的边沿处理


epoll的方式:

1、epoll_create:为应用程序,mmap映射一个内存区域,在该内核创建一个"节点":并在该"节点":

1.1、创红黑树用于存储接下来epoll_ctl系统调用传来的socket fd;

1.2、建立一个双向链表,用于存储准备就绪的事件

1.3、返回给应用程序一个fd,作为这个"节点"标识

2、epoll_ctl:2.1、把需要listen(也包括后面需要接收的)的socket fd,放到epoll文件系统里file对象对应的红黑树上

2.2、内核中断处理程序会注册一个回调函数,当这个句柄的中断到了,就把它放到准备就绪该"节点"的双向链表里

3、epoll_wait:阻塞的系统调用,和select、poll一样的,自己设置阻塞超时时间。

3.1、观察"节点"的双向链表里有没有数据。有数据就返回,没有数据就sleep;

3.2、超时时间到,发现还没数据,返回。

3.3、超时时间到之前,发现有数据,返回有数据的socket fd们。然后依旧是同步的accept、read这些socket fd。并不是"自动"accept、read的

优点(核心把握1和2):

1、不再每次复制和接收回传全部的fd_set大集合,改为:1.1、需要加入一个新的待listen、recv的socket fd时,通过epoll_ctl通知下内核,从此以后不必再次通知;

1.2、如果epoll_wait发现有数据,仅返回有数据的socket fd,而不是全部fd_set从内核再传回来再慢慢判断

2、内核也不再遍历全部的fd并挨个判断是否就绪,而是依靠回调函数,在socket fd在内核协议栈就绪时,调用回调函数将socket fd事件放入双向链表。这个是epoll总被认为是“异步”的最大原因

3、epoll加入了边沿触发,即就绪的socket fd被返回给过应用程序后,不论应用程序是否处理,不会再多通知

4、mmap,即内存映射一个内存区域,用户态到内核态的数据复制速度大幅加快


6、总结 那么nginx、libevent之类,他们其实也都是使用epoll,为什么说它们是异步的呢,异步机制体现在哪里呢?

答:它们是在更高层次,体现事件的异步处理性。简单说,由epoll引领read、write的这一套数据收发机制,相当于,对于事件级别的,异步。或者说,nginx、libevent的网络事件的定义及处理,是异步,底层的数据收发,依然是同步。真正的底层数据的异步,像Linux应该是AIO之类的东西。

7、rpc与消息队列:

现实互联网业务开发中,随着各种牛逼东西的诞生,似乎越来越不太多关注epoll、select、poll、accept、read、write了,更多的似乎是“更大的架构”的开发,哈哈哈, 但原理一定要懂的,必须要透彻,否则,libevent、libev、nginx、rpc、kafka。。。。。就会遇到坑再懂了。

1希望同步得到结果的场合、使用方式模拟本地调用,RPC最合适,RPC不要做所谓异步的事情;

2、如果是一个生产者消费者的业务场景,那就不适合RPC了更适合消息队列,因为这样会限制生产者的生产的速度;

按理说rpc和消息队列没有直接关联,但实际互联网业务开发中,分别代表着同步和异步的业务处理方式,所以在这里放一起描述。

rpc更多是一种基于业务间通讯方式的演变的业务思路。消息队列在互联网业务中的功能如同是一级级的大坝,最大作用就是缓冲,具体到业务,就演变成了:按业务解耦、削波峰、均分流量等等的功能。

8、tcpip:

针对互联网偏业务、工程架构,略掉估计不会考或者平时用的太少的知识(7层协议、4层协议基本知识应该已会,不会自己看,如果有公司问什么太细节的东西说明进去也没什么意思),只看如下方面,往往和日常工作有点关系:

8.1、握手与挥手:

握手:1、客户端发送一个带SYN标志的TCP报文到服务器;

   2、服务器端回应客户端,同时带ACK标志和SYN标志。表示对刚才客户端SYN报文的回应;同时询问客户端是否准备好进行数据通讯

   3、客户再次回应服务段一个ACK报文

挥手:由于TCP连接是全双工的,因此每个方向都必须单独进行关闭,也就是,当一方完成它的数据发送任务后,就能发送一个FIN来终止这个方向的连接

   1、客户端发送一个FIN,用来关闭客户到服务器的数据传送

   2、服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1,至此客户端无法再发送

   3、服务器关闭客户端的连接,发送一个FIN给客户端

   4、客户段发回ACK报文确认,并将确认序号设置为收到序号加1,至此服务端也无法再发送

8.2、socket的各个状态:

核心是time_wait和close_wait,有时出问题通过查看是否这两个状态特别多进而差代码是否有问题


1、CLOSED:初始状态

2、LISTEN:服务端监听时状态

3、SYN_RCVD:任何一端接收到SYN

4、SYN_SENT:任何一端发送SYN之后

5、ESTABLISHED:tcp连接创建成功

6、FIN_WAIT_1:当前是ESTABLISHED时,某端主动关闭连接(close),向对方发送FIN之后

7、FIN_WAIT_2:在6的基础上,收到了对端的ACK后

8、TIME_WAIT:在7的基础上,等待2MSL时间的状态。正常情况下下一个状态是回到CLOSED

等待2MSL时间是因为,在6的基础上,某端发送的ACK,对端未收到,此时对端还会重发FIN;

这个2MSL时间就是包容这种未收到的情况,直到让它收到为止。一般为几分钟。

对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接(非常不推荐服务端主动关闭,应该是客户端主动关闭),

将导致服务器端存在大量的处于TIME_WAIT状态的socket,

甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,挂了。

解决方法为:1、服务端避免主动发起关闭;2、设置操作系统,缩短这个时间。

另外,要开启端口复用,即,如果端口忙,但TCP状态位于TIME_WAIT ,那么可以重用端口,否则重用时返回“正在被使用”

9、CLOSING:基本不用管。这个状态是双方同时发送FIN时的情况极罕见。

10、CLOSE_WAIT:在收到对方的FIN且发送ACK后,等待本方应用程序也调用close时的状态,本方应用程序未调用close之前,本方处于此状态

这就是为什么挥手要4次,因为本端可能还得继续发送呢,tcp是双向的,所以得等着本方应用程序也close,不能说对方不发了,本方也跟着结束了

服务端应用程序不要忘记执行close(),否则无法由CLOSE_WAIT到LAST_ACK,导致服务端里边大量CLOSE_WAIT

11、LAST_ACK:在10的基础上,本方应用程序调用close了,等待对方的ACK,期间处于此状态


9、长短连接和连接池

总得连接着的东西用长连接,比如数据库;如果预知是哪些客户端,服务端改用连接池

像一下一下的,典型如网站里普通的各种业务http接口,短连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值