网络编程和多线程面试相关

Posted yazhou-lai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程和多线程面试相关相关的知识,希望对你有一定的参考价值。

1.    简述 OSI 七层协议。
    OSI:开发系统互联模型,是国际化标准组织(ISO)制定的为开放式互联信息系统提供了一种功能框架。
    从低到高:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
    目的:是为异种计算机互连提供一个共同的基础和标准框架,并为保持相关标准的一致性和兼容性提供共同的参考
    物理层功能:1.为数据端设备提供数据通路,通路即是媒介(网线,光纤)
                2.传输数据
        中继器,集线器,双绞线
        
    数据链路层:物理寻址,将原始bit流进行传输
        PPP,ARP,RARP
        以太网二层交换机,网卡
        
    网络层:路由选择,数据报投递
            1.路由选择,
            2.流量控制,
            3.网络管理
        IP,ICMP,RIP,OSPF,BGP,
        路由器,三层交换机,防火墙
        
    传输层:端与端之间的数据收发与确认,端与端的接口
        TCP,UDP
        四层交换机路由器,防火墙
        
    会话层:会话层不参与具体的传输,提供访问验证和会话管理在内的建立和维护应用之间通信的机制。
    表示层:数据格式化,代码转换,数据加密
    应用层:应用层为操作系统或网络应用程序提供访问网络服务的接口
        HTTP,SNMP,FTP,DNS,TELNET,
        
2.    什么是C/S和B/S架构?
    C/S:客户机/服务器模式,CS一般面向相对固定的用户群,程序更加注重流程,它可以对权限进行多层次校验
        CS每一个客户端都必须安装和配置软件
        
    B/S:浏览器/服务器结构,就是只用安装维护一个服务器,而客户端采用浏览器(Browse)运行软件。
        分布性强,开发简单,维护方便。
    
    开发维护成本:cs高于bs
        因为采用cs结构时,对于不同的客户端要开发不同的程序,而且软件安装调试和升级都需要在所有客户机上进行。
        而bs,只需要将服务器上的软件版本升级,然后从新登录就可以了。
    安全性:cs高于bs
        cs适用于专人使用的系统,可以通过严格的管理派发软件
        bs使用人数多,不固定,安全性低
    客户端负载:cs高于bs
        cs客户端不仅负责和用户的交互,收集用户信息,而且还需要通过网络向服务器发出请求
        bs把事务处理逻辑部分交给了服务器,客户端只是负责显示。
    
3.    简述 三次握手、四次挥手的流程。
    三次握手:
        client:发送【SYN=1】        seq=X
        server: 发送【SYN=1,ACK=1】seq=Y  ,ack=X+1
        client: 发送【ACK=1】        seq=X+1,ack=Y+1
    为什么是三次握手,而不是两次?
        第一次握手,S可以确认收到C端的报文,而C呢,它什么都不能确定,所以需要进行第二次握手
        第二次握手:C可以确认收到S的报文,但是S呢,他怎么确认C端收到报文呢?所以需要第三次
        第三次:C发送报文给S端,S端收到,这样就相互确认连接成功
    四次挥手:
        client: 发送【FIN=1, ACK=1】seq=X    ,ack=Y
        server: 发送【ACK=1】        seq=K    ,ack=X+1
        server: 发送【FIN=1, ACK=1】seq=K    ,ack=X+1
        client: 发送【ACK=1】        seq=X+1 ,ack=K+1
        
4.    什么是arp协议?
    ARP协议:是地址解析协议,通过网络地址寻找MAC地址,在数据链路层数据报的封装需要源MAC和目的MAC地址
        它通过发送广播请求,去网络中寻找MAC地址
    sender MAC: e8:b1:fc:6d:12:34
    sender IP:192.168.11.254
    target MAC:00:00:00:00:00:00
    target IP:192.168.11.253
    
5.    TCP和UDP的区别?
    传输控制协议(TCP),HTTP(80),FTP(21),TElNET(23),SMTP(25)
    用户数据报协议(UDP),SNMP(161),DNS(53)
    TCP:1.是面向连接的,发送数据之前需要建立连接
        2.提供可靠的服务,无差错,不丢失,不重复,且按序到达(校验和,重传机制,序号标识,滑动窗口,确认应答)
        3.连接只能是点到点的一对一连接
        4.对系统资源要求多
        5.工作效率低
    UDP:1.无连接的,发送之前不需要建立连接,
        2.尽最大努力交付,不保证可靠交付
        3.一对一,一对多,多对一
        4.对系统资源要求少
        5.工作效率高
    
    UDP以其简单,传输快的优势,在越来越多的场景下使用:
        1.网速的提升给UDP的稳定性提供了网络保障,如果使用应用层重传,能够确保传输的可靠
        2.TCP为实现网络通信的可靠,使用了复杂的拥塞控制算法,建立了复杂的握手机制
    
    TCP:
    server:
        1.创建一个socket,用socket()函数;        sk = socket.socket()
        2.设置socket属性,用setsockopt() 可选    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)
        3.绑定ip和端口到socket上,用函数bind(); sk.bind((‘127.0.0.1‘,9999))
        4.开启监听,用listen()                    sk.listen(5)
        5.等待客户端连接,accept()                conn,addr = sk.accept()
        6.收发数据,recv(),send()                conn.recv(1024),conn.send(‘msg‘)
        7.关闭网络连接                            conn.close()
        8.关闭socket                            sk.close()
    client:
        1.创建一个socket,用socket()函数        sk = socket.socket()
        2.连接服务器,用函数connect();         sk.connect((‘127.0.0.1‘,9999))
        3.收发数据,recv(),send()                sk.recv(1024), sk.send(‘msg‘)
    ---------------------

    UDP:
    与之对应的UDP编程步骤要简单许多,分别如下:
      UDP编程的服务器端一般步骤是:
      1、创建一个socket,用函数socket();     sk = socket.socket(type=socket.SOCK_DGRAM)
      2、设置socket属性,用函数setsockopt();* sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)
      3、绑定ip和端口到socket上,用函数bind();sk.bind((‘127.0.0.1‘,9091))
      4、循环接收数据,用函数recvfrom();         msg,addr = sk.recvfrom(1024),,sk.sendto(msg,client_addr)
      5、关闭连接;                             sk.close()
    UDP编程的客户端一般步骤是:
      1、创建一个socket,用函数socket();     sk = socket.socket(type=socket.SOCK_DGRAM)
      5、发送数据,用函数sendto();             msg,addr = sk.recvfrom(1024),,sk.sendto(msg,(‘127.0.0.1‘,9091))
      6、关闭网络连接;                        sk.close()
    ---------------------

6.    什么是局域网和广域网?
    通俗而言:局域网就是内网,广域网即是外网,局域网一般是私有地址互联,有公有地址出口
              广域网一般都是公有地址互联,局域网通过公有地址连接上广域网
              
    LAN:某以区域内多态计算机互联的计算机组,某一区域:一个办公室,一栋建筑,同一个公司,一所学校
    WAN:是一种跨地域的计算机网络的集合,通常跨时,省,国家。广域网包含大大小小的子网,子网可是是局域网
        也可以是小型广域网
        
7.    为何基于tcp协议的通信比基于udp协议的通信更可靠?
    1.因为TCP是面向连接的,在通信前需要建立连接,而UDP是无连接的,通信前不需要连接连接
    2.TCP有确认重传机制,数据的发送序列号seq保证数据有序,流量控制,拥塞控制等保证数据的可靠
    3.而UDP 发送完数据后不会确认数据是否交付
    
8.    什么是socket?简述基于tcp协议的套接字通信流程。
    实际上socket是对TCP/IP协议的封装,它的出现使得程序员更方便的使用TCP/IP协议栈而已。
    socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)
    
    server:
        1.创建一个socket,用socket()函数;        sk = socket.socket()
        2.设置socket属性,用setsockopt() 可选    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)
        3.绑定ip和端口到socket上,用函数bind(); sk.bind((‘127.0.0.1‘,9999))
        4.开启监听,用listen()                    sk.listen(5)
        5.等待客户端连接,accept()                conn,addr = sk.accept()
        6.收发数据,recv(),send()                conn.recv(1024),conn.send(‘‘)
        7.关闭网络连接                            conn.close()
        8.关闭socket                            sk.close()
    client:
        1.创建一个socket,用socket()函数        sk = socket.socket()
        2.连接服务器,用函数connect();         sk.connect((‘127.0.0.1‘,9999))
        3.收发数据,recv(),send()                sk.recv(1024), sk.send(‘‘)

9.    什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?
    TCP黏包是指发送方发送的若干数据到接收方接收是黏成一包,从接收缓冲区看,后一包的头紧接着前一包的尾
    造成黏包的成因:
        1.发送端需要等缓冲区满才发送出去,造成黏包
            发送方引起的黏包是由于tcp协议本身造成的,tcp为了提高传输效率,发送发往往要收集足够多的数据
            后才发送一包数据,若连续几次发送的数据都很少,通常tcp会根据优化算法把这些包合成一包发送,
            这样接收方就收到了黏包数据。
            
        2.接收方没有及时接收缓冲区的包,造成多个包接收
            接收方引起的黏包是由于接收方进程没有及时接收数据,从而导致的黏包。接收方先把数据放在系统的接收
            缓冲区,用户进程从该缓冲区取数据,若下一个包到达时前一个包尚未被用户进程取走,则下一个包放到前一个包的尾部
            而用户进程根据预先设定的缓冲区大小从系统缓冲区取数据,这样导致了黏包的产生
    
    TCP无保护消息边界的解决:
        1.发送固定长度的消息
        2.把消息的尺寸和消息一块发送,
        3.使用特殊的标记来区分消息的间隔
        
10.    IO多路复用的作用?
    阻塞:数据没来,啥都不做,直到数据来了,才进行下一步操作
    非阻塞忙轮询:数据没来,进程就不断的去检测数据,直到数据来了
    # IO多路复用的本质是用select、poll、epoll(系统底层提供的)来监听socket对象内部是否有变化
    # select 是在Win和Linux中都支持额,相当于系统内部维护了一个for循环,缺点是监听个数有上限(1024),效率不高
    # poll的监听个数没有限制,但仍然用循环,效率不高。
    # epoll的机制是socket对象变化,主动告诉epoll。而不是轮询,相当于有个回调函数,效率比前两者高
    # nginx就是用epoll。只要IO操作都支持,除开文件操作
    阻塞:
        一个线程只能处理一个套接字的IO事件,如果想同时处理多个?可以用非阻塞忙轮询的方式
        但这样做很不好,因为如果所有的流都没有IO事件,就白白浪费cpu的时间片。所以,我们为了解决这个问题
        我们引进一个代理(一开始是select,后来是poll),这个代理可以同时观察多个流的IO事件,如果没有
        事件,代理就阻塞,线程就不会去挨个轮询了
        但是依旧有个问题,我们从select哪里仅仅知道,有IO事件发送了,却不知道是那几个流(一个?多个?),
        我们只能无差别的轮询所有的流,找到能读取的数据,对他们进行操作。所以select具有O(n)的无差别轮询复杂度。
        
        epoll可以理解为event poll,不同于忙轮询和无差别轮序,epoll会把那个流发生了怎样的IO事件通知给我们。所以说
        epoll实际上是事件驱动,此时的复杂度降到O(1)
        
        可以看到,select和epoll的最大区别是:select只告诉你有一定数量的流有IO事件了,具体哪个流,还需要你一个一个去轮序
        ,而epoll会把发生的事件告诉你,通过发生的事件,就自然定位到哪个流了。
    IO多路复用:
        输入操作:
            1.等待数据准备好,数据从网络到达,并将其复制到内核缓冲区
            2.将数据从内核缓存区复制到进程缓冲区
        阻塞IO模型:
            默认情况下所有的socket都是阻塞的,
            当调用recv()函数时,系统首先检查是否有准备好的数据,如果数据没有准备好,那么系统就处于等待状态,当数据准备好后,
            将数据从系统缓冲区复制到用户空间,然后函数返回
        非阻塞IO模型:
            我们把一个socket接口设置为非阻塞就是告诉内核,当所请求的IO操作无法完成时,不要将进程睡眠,而是返回一个错误。
            进程反复调用recvfrom等待返回成功的指示(轮询),会不断的测试数据是否准备好,没有准备好,继续测试,直到数据准备好为止。
            在测试的过程中会占用大量的CPU时间。
        IO复用模型:阻塞于select调用,等待数据报套接口变为可读。当select返回套接口可读这一条件时,我们调用recvfrom
                    把所读数据报拷贝到应用进程缓冲区。
        信号驱动IO:首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。
                    该系统调用立即返回,我们的进程继续工作,也就是说它没有被阻塞,当数据报准备好读取时,内核就为该进程产生一个SIGIO信号
                    这种模型的优势在于等待数据报到达期间,进程不被阻塞
        异步IO模型:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到我们自己的缓冲区)完成后通知我们
                    这种模型与信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时启动一个I/O操作,
                    而异步I/O模型是由内核通知我们I/O操作何时完成。
    
    同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成。
    异步I/O操作(asynchronous I/O operation)不导致请求进程阻塞。
    
    我们的前4种模型——阻塞I/O模型、非阻塞I/O模型、I/O复用模型和信号驱动I/O模型都是同步I/O模型,
    只有异步I/O模型与POSIX定义的异步I/O相匹配。

11.    什么是防火墙以及作用?
    防火墙:它是内网和外部网络之间的网络安全系统。保护内部免受非法用户的侵入,主要根据规则,验证,包过滤
            应用网关进行安全防护。
    防火墙分为软件防火墙和硬件防火墙
    防火墙分为:
        1.网络防火墙:基于源地址,目的地址,源端口和目的端口,以及应用层协议做入站和出站规则
        2.应用层防火墙:可以基于应用做相关规则
    
12.    select、poll、epoll 模型的区别?
    1.select  时间复杂度O(n)
        它仅仅知道,有IO事件发生了,却不知道是哪几个流,所以我们只能进行无差别的轮询,找到后对其操作。它有最大连接数的限制
        
    2.poll      时间复杂度O(n)
        poll本质上和select没有区别,他讲用户传入的数据copy到内核空间,然后查询每个fd对应的状态,但她没有最大连接数
        的限制,
    3.epoll   时间复杂度O(1)
        不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。没有最大连接数。不支持win
    
    select,poll,epoll都是IO多路复用的机制。IO多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(
    读或者写),就能够通知程序进行相应的操作。
    
13.    简述 进程、线程、协程的区别 以及应用场景?
    程序是一组静态的资源:
    进程:是一个程序在一个数据集中的一次动态执行的过程。可以简单理解为正在执行的程序,每一个应用运行起来都有自己的进程
          进程是系统资源分配的最小单位,进程占用的资源有:地址空间,全局变量,文件描述符,各种硬件等等资源。
    线程:线程的出现是为了降低上下文切换的消耗,提高系统的并发性,线程也叫轻量级的进程,它是cpu执行的基本单位,一个进程可以
          包含多个线程。
    协程:是一种用户态的轻量级线程,协程的调度完全由用户控制
          协程的执行效率非常高
          是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁
    
    多线程、协程用于IO密集型,如socket,爬虫,web,抢占cpu资源
    多进程用于计算密集型,如金融分析,利用多核优势
    
14.    GIL锁是什么鬼?
    GIL:全局解释器锁
    
    python代码的执行需要又python解释器来控制。python在设计之初就考虑到在主循环中,同时只有一个
    线程在执行。虽然python解释器可以运行多个线程,但是在任一时刻只有一个线程在解释器中运行
    
    GIL并不是python的特性,而是在实现Python解释器(CPython)时引入的概念(现在难以移除),像Jpython就没有GIL。
    毫无疑问,GIL的存在会多多线程的效率有不小的影响,甚至就等于python是个单线程程序了
    每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
    线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,
    
    线程锁限制了对cpu的使用,但是不影响web类或爬虫类代码的效率
    
15.    Python中如何使用线程池和进程池?
    进程池:创建销毁进程都需要消耗时间,即便开启了很多进程,操作系统也不会让其同时执行。所以引出进程池的概念
            来提高效率。定义一个池子里面放上固定数量的进程,有需求来了就拿一个进程来处理,
            等到处理完毕,进程不关闭,而是将进程再放回进程池中继续等待任务。
    进程池和Semaphore的区别:
        信号量:n个任务开启n个进程,同一时间只有固定数量的进程在运行
        进程池:n个任务开启固定数量的进程,同一个时刻只有固定数据的进程在运行
    进程池的使用:
        1.导入进程池 from multiprocessing import Pool
        2.定义执行函数f1(i)
        3.实例化进程池对象 p = Pool(5)  # 一般设置为cpu+1
        4.利用循环调用执行函数创建进程:ret = p.apply_async(func=f1,args=(i,)) #异步调用,主进程和所有的子进程异步了
    
    
    线程池:对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初始只有一个链接a,
            a中还有b,c等线程数量暴涨。在之后的运行中,线程数量还会不停的增加,完全无法控制。
            所以,对于任务数量不断增加的程序,固定线程数量的线程池是必要的。
            
    线程池的使用:threadpool(比较老),concurrent.futures(了高度封装的异步调用接口)
        1.导入concurrent    from concurrent.futures import ThreadPoolExecutor
        2.定义执行函数f1(i)
        3.实例化线程池对象 thread_pool = ThreadPoolExecutor(5)
        4.利用循环调用执行函数创建进程:ret = thread_pool.submit(func,i)  # 相当于apply_async
        
16.    threading.local的作用?
    import threading
    val = threading.local()
    def task(arg):
        val.x1 = arg
        # threading.local对象.xxx = arg
        # 为每一个线程创建一个独立的空间,每个空间都有一个唯一标识,就是线程id
        # 本质上threading.local是一个大字典
        #{
        #    线程id_1:{‘x1‘:1},
        #    线程id_1:{‘x1‘:2},
        #}
        print(threading.get_ident())  # 线程的唯一标识
        print(val.x1)
    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()
    为每个线程创建一个独立的空间,使得线程对自己的空间中的数据进行操作(数据隔离)。
    为每一个线程创建一个独立的空间,每个空间都有一个唯一标识,就是线程id
    
17.    进程之间如何进行通信?
    Queue:实现多个进程之间通信
        from multiprocessing import Queue,Process
        def wahaha(q):
            print(q.get())
            q.put(2)
        if __name__ == "__main__":
            q = Queue()
            p = Process(target=wahaha,args=(q,))
            p.start()
            p.put(1)
            time.sleep(0.5)
            print(q.get())
            
    Pipe:实现两个进程之间通信
        from multiprocessing import Process,Pipe
        def f(parent_conn,child_conn):
            parent_conn.close()
            while True:
                try:
                    print(child_conn.recv())  # 子进程取数据
                except EOFRrror:
                    child_conn.close()  
                    break
        if __name__ == "__main__":
            parent_conn,child_conn = Pipe()
            p = Process(target=f,args=(parent_conn,child_conn))
            p.start()
            child_conn.close()
            parent_conn.send(‘hello‘)  # 主进程往管道内推送数据
            parent_conn.send(‘hello‘)
            parent_conn.send(‘hello‘)
            parent_conn.close()
            p.join()
            
18.    什么是并发和并行?
    并行:同一时刻,多个程序在同时执行,
        微观上:就是在一个精确的时间片刻,有不同的程序在运行,这就要求必须有多个处理器。
    并发:在资源有限的情况下,两个交替轮流的使用资源,
        宏观上:在一个时间段上可以看做是同时运行,微观上是交替运行
        
19.    进程锁和线程锁的作用?
    同步控制:锁,信号量,事件
    数据共享:队列,管道
    
    进程锁:当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。加锁可以保证多个进程修改同一块数据时,
    同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
    from multiprocessing import Lock,Process
    def work(n,lock):
        lock.acquire()
        pass
        lock.release()
    if __name__ == "__main__":
        lock = Lock()
        for i in range(3):
            p = Process(target=work,args=(i,lock))
            p.start()
    
    线程锁:资源总是有限的,程序运行如果对同一个对象进行操作,则有可能造成资源的争用,甚至导致死锁
            也可能导致读写混乱
    有超过一个资源需要锁的时候,就用递归锁:Rlock(一个线程中对一个锁多次acquire,不会产生阻塞)
    单个资源需要锁的时候,就可以直接用互斥锁:Lock(一个线程中对一个锁多次acquire,会产生阻塞)
    from threading import Thread,Lock,Rlock
    
    if __name__ == "__main__":
        fork_lock = noodle_lock =Rlock() #这样就变为了一串钥匙了,只要一个人抢到钥匙即可,不会产生死锁
        
20.    解释什么是异步非阻塞?
    同步与异步:
        简单来说:同步就是一个任务的完成需要依赖另一个任务的时,只有等待依赖的任务完成,才能进行后续操作
                  异步就是不需要等待被依赖的任务完成,
        是针对于应用程序与内核的交互而言。同步过程中进程触发IO操作,需要等待IO操作完成,或者轮询的去,
        查看IO操作是否完成,在这个过程中程序只能阻塞在这里;异步过程中进程触发IO操作后,直接返回,做自己的事情,
        IO交给内核来处理,处理完成后内核通知进程IO完成。
    阻塞与非阻塞:
        应用程序请求IO操作时,如果数据未准备好,请求立即返回就是非阻塞,不立即返回就是阻塞。
    同步阻塞:老王烧水,站在那里,每隔一段时间去看水是否烧开,
    同步非阻塞:老王烧水,跑去上网,每个一段时间过来看一下是否烧开
    异步阻塞:老王烧水,用响水壶,站在那里,但是不会看水是否烧开,而是等待水开了,水壶通知他
    异步非阻塞:老王烧水,用响水壶,跑去上网,等水开了,水壶通知他
    
    同步就是烧开水,需要自己去轮询,异步就是水开了,水壶通知你
    阻塞就是说在烧水的过程中,你不可以去干其他事情;非阻塞就是在烧水的过程中你,可以去干其他事情
    
21.    路由器和交换机的区别?
    路由器一般是做:路由功能,根据IP地址寻址,可以把不同的网段连接起来
    交换机一般是做:数据报文交换,根据MAC地址寻址
    
22.    什么是域名解析?
    域名解析:就是DNS,根据域名去解析出主机的IP地址,然后用IP地址进行通信
    
    当应用过程需要将一个主机的域名映射为ip地址的时候,就调用域名解析函数,域名解析函数将待转换的域名放在dns请求中,
    以UDP的报文发送给本地的域名服务器,本地域名服务器查到后,将对应的ip地址放在应答报文中返回;若本地域名服务器不能jiexi
    ,则此域名服务器就成为客户端,向根域名服务器发送请求解析。
    
23.    如何修改本地hosts文件?
    在windows中:
        C:WindowsSystem32driversetchosts
        IP地址         域名(hostname)
        127.0.0.9  www.cnblog.com
    在linux中:
        /etc/hosts
        IP地址          hostname               aliases(可选)
        127.0.0.1   localhost.localdomain        localhost
        
24.    生产者消费者模型应用场景及优势?
    生产者和消费者模型是通过一个容器来解决生产者和消费者的关系,生产者和消费者之间不直接进行通信,而是利用阻塞队列来进行通信,
    生产者生产了数据后直接丢给阻塞队列,消费者从阻塞队列中获取,进行处理。
    实际中,生产者和消费者模型主要解决生产者和消费者的速率不一致问题,达到平衡生产者与消费者de处理能力,
    而阻塞队列相当于缓冲区。
    
    应用场景:用户提交订单,订单进入到阻塞队列中,由专门的线程从阻塞队列中获取订单并处理。
    
    针对原先轮询的方式处理队列中的订单,无疑生产者和消费者更加节约cpu资源
    支持并发,生产者和消费者是两个对立的并发体。
    
25.    什么是cdn?
    CDN:内容分发网络
    
26.    LVS是什么及作用?
    Linux virtual sever:linux虚拟服务器
    LVS主要用于多服务器的负载均衡。它工作在网络层,可以实现高性能,高可用的服务器集群技术。
    可把许多低性能的服务器组合在一起形成一个超级服务器。它易用,配置非常简单,且有多种负载均衡的方法。
    它稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果。另外可扩展性也非常好
    
27.    Nginx是什么及作用?
    Nginx是一个开源,支持高并发的web服务和代理服务器软件。
    
    支持高并发,能支持几万并发连接
    资源消耗少,在3万并发连接下开启10个nginx线程,消耗内存不到200M
    可以做http反向代理和负载均衡
    支持异步IO事件模型epoll
    nginx不但是一个优秀的web服务软件,还可以作为反向代理,负载均衡,以及缓存服务使用。
    
28.    keepalived是什么及作用?
    keepalived是一个linux下轻量级的高可用性解决方案。用来监控集群系统中各个服务节点的状态。
    它根据TCP/IP模型中第三,四,五层交换 机制检测每一个节点的状态,如果出现异常,keepalived检测到了,
    将出现故障的服务器节点从集群中剔除。
    
29.    haproxy是什么以及作用?
30.    什么是负载均衡?
    负载均衡:就是由多台服务器以对称的方式组成一个服务器的集合,每台服务器都是具有等价的地位,都可以
        单独的对外提供服务。在高并发的情况下,请求到来,将外部发送来的请求经过某种负载分管技术,分配到集群中的某一台服务器
        
    优点:高可靠性,高可用性
    
31.    什么是rpc及应用场景?
32.    简述 asynio模块的作用和应用场景。
33.    简述 gevent模块的作用和应用场景。
    python中提供协程的模块有greenlet和gevent,
    greenlet和gevent最大的区别在于greenlet需要你自己来处理线程切换,你需要自己指定现在执行哪个greenlet再执行哪个greenlet
    gevent是基于greenlet的,有了gevent,协程的使用变得非常简单,你根本无需像greenlet一样显示的切换,每当一个协程阻塞时,程序会自动调用。
    
    # spawn发布协程
    # join 开启并等待任务结束
    
    
    gevent 本身不认识其他模块中的IO操作,利用monkey就可以了
    如下例子:使用gevent自身的IO操作,是ok的,
    但是如果不用gevent.sleep(),而用time.sleep(),打印结果就12,34,56,78 变成同步的了
    import gevent
    def test1():
        print(12)
        gevent.sleep(0)
        print(34)
    def test2():
        print(56)
        gevent.sleep(0)
        print(78)
    gevent.joinall([
        gevent.spawn(test1), # 发布协程
        gevent.spawn(test2),
    ])
    # 12
    # 56
    # 34
    # 78
    
    from gevent import monkey;monkey.patch_all()
    import gevent
    import time
    def eat():
        print(‘eating1‘)
        time.sleep(1)
        print(‘eating2‘)

    def play():
        print(‘playing1‘)
        time.sleep(1)
        print(‘playing2‘)
    gevent.joinall([
            gevent.spawn(eat),
            gevent.spawn(play),
        ])
        # eating1
        # playing1
        # eating2
        # playing2
34.    twisted框架的使用和应用?


    
35.生产者消费者模型?
    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过消息队列(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给消息队列,消费者不找生产者要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个消息队列就是用来给生产者和消费者解耦的。------------->这里又有一个问题,什么叫做解耦?

    解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产
    生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区
    ,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调
    用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据
    很慢,生产者就会白白糟蹋大好时光。缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体
    现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度
    慢下来,消费者再慢慢处理掉。






































































































































































































































































































































































































































































































































以上是关于网络编程和多线程面试相关的主要内容,如果未能解决你的问题,请参考以下文章

JUC并发编程线程池及相关面试题 详解

IOS面试题(多线程) --- 锁

Kafka相关面试题

Java面试系列之并发编程专题-Java线程池灵魂拷问

Java面试系列之并发编程专题-Java线程池灵魂拷问

并发编程面试 ReentrantLock 相关