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

本文主要是介绍关于网络(同步、异步、阻塞、非阻塞,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接口,短连接

这篇关于关于网络(同步、异步、阻塞、非阻塞,select/poll/epoll,rpc/msgqueue,tcpip常见面试题)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/974653

相关文章

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Java 中实现异步的多种方式

《Java中实现异步的多种方式》文章介绍了Java中实现异步处理的几种常见方式,每种方式都有其特点和适用场景,通过选择合适的异步处理方式,可以提高程序的性能和可维护性,感兴趣的朋友一起看看吧... 目录1. 线程池(ExecutorService)2. CompletableFuture3. ForkJoi

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制