浙江宇视科技一面

浙江宇视科技一面

这题超纲了 柳筋

前情提要

本人从2022-11月末开始在各大招聘网站上面投递简历,即今为止收到了3次面试,前两次面试不想评价,首先就是面试的岗位不是我想去的岗位(甚至和我投递的完全不搭边…),其次就是问的问题一点技术含量也么没有,全程和我唠嗑(有没有可能,我是说可能,或许我投了传销的岗位?),直到昨天下午,在图书馆好好学习的我突然被一阵电话铃声吓了一跳,原本也不在意,以为又是来催我买车的( 因为我驾照已经有一年了(狗头 ),等我看到来电地址是浙江杭州的时候,并且号码还很正常,我才意识到确实有人来找我,在脑子里想了很久我是不是欠了某个杭州朋友的钱的时候,电话已经挂了…
后来多次拨打过去,才接通并且告知我笔试通过,准备一面…此时的心情怎么说呢?有点激动又有点害怕。冷静下来后意识到
“如今面试就在眼前,我必须考虑这会不会是我此生仅有的机会,我相信中国能有现在的地位,老一辈功不可没,重铸华夏人荣光,我辈义不容辞”

面试开始

自我介绍

没什么好说的,可以去看看我的置顶文章捏

重点介绍一下哪些与岗位相关的项目

说真的,我不知道自己面的是什么岗位了,于是就把WebServer,简易RPC框架,简易抖音APP都简单介绍了一下

介绍一下RPC框架

RPC框架实现的是一个函数的远程调用,将函数部署在一个服务器上面,通过另外一个服务器调用该函数

答案

答案:RPC(Remote Procedure Call)即远程过程调用,不同于本地调用,RPC是指调用远端机器的函数或方法,且不需要关心底层的调用细节,如网络协议和传输协议等,对于调用者来说,和调用本地方法没有什么区别。

实现原理是怎样的

实现原理就是:先用protobuf把需要的参数进行序列化,然后通过网络库将数据传输给被调用方,然后被调用方再用protobuf将参数(反)序列化后传入函数里面,获得结果后再用相同的方法传递回去,被调用方会先将函数注册在zookeeper上,然后就可以进行监听事件的发生,如果有请求事件的就会进行处理

主要有以下几个步骤:

1、建立通信

首先要解决通讯的问题:即A机器想要调用B机器,首先得建立起通信连接。主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有相关的数据都在这个连接里面进行传输交换。

通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。

2、服务寻址

解决寻址的问题:即A机器上的应用A要调用B机器上的应用B,那么此时对于A来说如何告知底层的RPC框架所要调用的服务具体在哪里呢?

通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。比如基于Web服务协议栈的RPC,就需要提供一个endpoint URI,或者是从UDDI服务上进行查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。

3、网络传输

3.1、序列化

当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。

3.2、反序列化

当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用, 通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。

4、服务调用

B机器进行本地调用(通过代理Proxy)之后得到了返回值,此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,最后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。

通常,经过以上四个步骤之后,一次完整的RPC调用算是完成了,另外可能因为网络抖动等原因需要重试等。

这个调用跟消息收发有什么区别吗?

(其实这里不太懂想要问什么)
这个调用就和浏览器请求服务器资源和回应,都是通过网络协议进行的

那我可不可以理解为这个远程调用就是网络消息的收发

可以吧(心虚…因为从开学到现在一直在看那个抖音项目,说实话RPC这方面有一些都忘了,再加上当时很紧张)

走的是什么协议

TCP协议
面试官:TCP协议?
(心虚)应该还有HTTP协议,因为我是用的muduo库的(阿西吧!)

答案

应该只用到了TCP协议,用来与服务器建立连接

RPC和HTTP的联系与区别

联系:都是远程通信的网络协议,任何网络协议都包括语法、语义、时序三个特征。

区别:

HTTP协议是应用层协议,更多地用于前后端通信或移动端、PC端与后端通信,其他的常用应用层协议还包括SSH协议、ftp协议。

RPC是远程过程调用,更多地用于分布式架构中各个服务模块之间的通信,其中的知识涉及socket通信、序列化和反序列化、动态代理、反射调用。如下程序,服务端要发布socket服务+线程池io处理多个客户端连接+反射调用,客户端要动态代理实例化对象,参与网络传输的Bean要实现Serializable接口变为可序列化+IO流序列化/反序列化。

HTTP和TCP有什么区别

HTTP是应用层的协议,TCP是传输层的;
HTTP协议是一个报文协议,传输的内容包含了请求以及回应,TCP协议是数据流的协议,里面有目的IP地址和源IP地址(我这回答的简直就是依托答辩,回答的时候还口吃了…)

HTTP协议与TCP协议的区别

1、TCP对应与传输层、而HTTP对应于应用层,所以HTTP协议是建立在TCP协议之上的;

2、HTTP底层是利用TCP协议传输的,所以支持http也就一定支持TCP;

3、TCP是网络传输协议, HTTP是超文本传输协议;

    TCP是底层协议,定义的是数据传输和连接方式的规范。    HTTP是应用层协议,定义的是传输数据的内容的规范。

4、HTTP是无状态的短链接,而TCP是有状态的长连接;

TCP对应于传输层,HTTP对应于应用层,从本质上来说,二者没有可比性。

消息封装的时候先封装哪个协议呢

先封装HTTP协议(面试官不说话,我以为我说错了,立马改口成TCP协议了)
答案:先封装HTTP协议,封装消息是从应用层开始,到物理层结束,所以应该先封装应用层的HTTP协议,整个流程如下

消息发出去需要封装几层

先经过TCP,然后IP,然后HTTP,最后经过数据链路层封装成帧,应该是4层…(今晚听录音的时候真想抽死自己)
答案:四层,如上图所示

项目调试过程遇到了什么问题

(努力思考做项目的时候遇到的问题,然后乱七八糟回答了一堆有的没的)

消息通信一般定位的方法有哪些

(第一次没听清,等第二次听清的时候我才发现不是我没听清而是我根本不知道怎么回答,随便说了一下也不知道是什么东西的玩意)

这个项目是用C++写的是吧,那讲一下C++里面数组和链表的区别

数组在内存中是连续存放的,链表在链表中是随机存放的
数组的插入、删除的时间复杂度为O(n),获取元素的时间复杂度为O(1)
链表的插入、删除的时间复杂度为O(1),获取元素的时间复杂度为O(n)
补充:
数组:(1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素;(6)数组从栈上分配内存,使用方便,但是自由度小
链表:(1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存;(4)链表从堆上分配内存,自由度大,但是要注意内存泄漏

为什么数组的查询时间复杂度是O(1)的呢

因为数组可以通过数组下标来访问数组的元素
补充:因为数组在内存中是连续的,所以在知道数组首地址的情况就就能知道数组中所有元素的地址,所以要可以不需要从头开始遍历去查找,所以为O(1)

如果数组里面的数据是乱序的,也就是说数组的下标和数组元素之间不存在数学关系的话,那么他的查找效率是怎么样的

还是0(1)吧,数组的下标和元素好像本来就没有什么数学关系

讲一下指针函数和函数指针的区别

(寄!)
指针函数是指向函数地址的一个指针…函数指针的话不太记得了…

函数指针和指针函数的区别

指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
声明格式为:*类型标识符 函数名(参数表)

函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
声明格式:类型说明符 (*函数名) (参数)

以上都是对普通函数指针的介绍,中在C++中因为类函数的关系,函数指针又会有一些不同的区别
类成员函数的指针(非静态)
指向类成员函数的指针与普通函数指针的区别在于,前者需要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类类型。

这是因为非静态的成员函数必须被绑定到一个类的对象或者指针上,才能得到被调用对象的this指针,然后才能调用指针所指的成员函数(所有类的对象都有自己数据成员的拷贝,但是成员函数都是共用的,为了区分是谁调用了成员函数,就必须有this指针,this指针是隐式的添加到函数参数列表里去的)
类成员函数指针定义:typedef 返回值 (类名::*指针类型名)(参数列表);
类成员函数指针赋值:指针类型名 指针名 = &类名::成员函数名;
类成员函数指针的调用:
(类对象.*指针名)(参数列表);
(类指针->*指针名)(参数列表);

指向类的静态函数的指针
类的静态成员函数和普通函数的函数指针的区别在于,他们是不依赖于具体对象的,所有实例化的对象都共享同一个静态成员,所以静态成员也没有this指针的概念。所以,指向类的静态成员函数的指针就是普通的函数指针。

讲一下类和结构体有什么区别

在结构体里面所有的数据都是public的,在类里面还存在protect和private
类存在构造函数和析构函数
类可以通过继承的方法实现多态
类里面还会封装对类里面的数据的操作的函数,在结构体里面没办法这样做

类和结构体的区别
  1. 结构体是一种值类型,而类是引用类型。值类型用于存储数据的值,引用类型用于存储对实际数据的引用。

    那么结构体就是当成值来使用的,类则通过引用来对实际数据操作。

  2. 结构体使用栈存储(Stack Allocation),而类使用堆存储(Heap Allocation)

栈的空间相对较小.但是存储在栈中的数据访问效率相对较高.

堆的空间相对较大.但是存储在堆中的数据的访问效率相对较低.

  1. 类是反映现实事物的一种抽象,而结构体的作用只是一种包含了具体不同类别数据的一种包装,结构体不具备类的继承多态特性

  2. 结构体赋值是 直接赋值的值. 而对象的指针 赋值的是对象的地址

  3. Struct变量使用完之后就自动解除内存分配,Class实例有垃圾回收机制来保证内存的回收处理。

  4. 结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制

首先,关于隐式构造函数.我们知道,在1个类中如果我们没有为类写任意的构造函数,那么C++编译器在编译的时候会自动的为这个类生成1个无参数的构造函数.我们将这个构造函数称之为隐式构造函数 但是一旦我们为这个类写了任意的1个构造函数的时候,这个隐式的构造函数就不会自动生成了.在结构体中,就不是这样了,在结构体中隐式的构造函数无论如何都存在。所以程序员不能手动的为结构添加1个无参数的构造函数。

  1. 结构体中声明的字段无法赋予初值,类可以
结构体可以包含一个类吗

应该可以吧

多态是什么意思

(啊啊啊我知道多态大概是个什么意思,但是我就是没办法用口语表达出来)额…像是…额…

多态

一个接口多种方法,即用同一个接口,但是效果不同
多态又分为静态多态和动态多态。
静态多态是指在编译期就把函数链接起来,此时即可确定调用哪个函数或模板,静态多态是由模板和重载实现的,重载又分为函数重载和运算符重载
动态多态是指在程序运行时才能确定函数和实现的链接,此时才能确定调用哪个函数,父类指针或者引用能够指向子类对象,调用子类的函数,所以在编译时是无法确定调用哪个函数
动态多态存在的三个必要条件:
1. 继承
2. 重写
3. 父类引用指向子类对象

全局变量和局部变量可以用相同的变量名吗

可以
答案:局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。

如果使用这个变量的时候是使用哪里的?

如果在函数里面的话,会先使用局部变量

什么情况下使用全局变量呢?

(不会)在定义局部变量的函数外就是使用全局变量
答案:想要在函数体内使用全局变量而不是同名的局部变量的话,需要使用”::”(作用域解析符)+全局变量名

讲一下C++和GO的区别

(没去探究过,只知道GO很简便)GO里面有很多库可以使用,我通过GORM框架和GIN框架实现视频的上传和聊天记录的上传,并且保存在数据库里面

讲一下进程和线程的区别是什么

进程是资源调度(资源分布才对…)的基本单位,线程是CPU执行的基本单位
线程是比线程更轻量级的一个东西(?),一个线程可以包括很多个进程(不是我在说什么啊?这都能说错啊?)
线程之间是共享一些数据的,它们通信不需要通过内核,进程通信需要通过内核

进程与线程的区别与联系

  1. 拥有资源:进程时资源分配的基本单位,而线程时CPU分配和调度的基本单位。
    进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  2. 调度:线程时实现独立调度的基本单位。在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
  3. 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
    通信:程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC (Inter-Process Communication)。


进程间通信有哪些方法?哪个效率最高

方法有管道、信号、消息队列、信号量、共享内存、内存映射;共享内存的效率应该是比较高的

共享内存具体怎么操作的呢?

一个进程会先将要共享的文件通过页表映射在物理内存上面,其他进程就可以通过访问物理内存对文件进行修改从而实现进程间通信(奇奇怪怪的)

共享内存的实现

  1. 使用shmget()函数创建一个新的共享内存段或者区的一个已有的共享内存段的标识符(就是说有其他进程创建的共享内存段),这个调用将返回后续调用需要用到的共享内存标识符
  2. 使用shmat()函数来附上共享内存段,即使该段称为调用进程的虚拟内存的一部分
  3. 此时在程序中就可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()函数的返回值的addr值(一个指向进程的虚拟地址空间中该共享内存段的起点的指针)
  4. shmdt()函数用来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存
  5. 调用shmctl()来删除共享内存段。只有当所有附加内存段的进程都与之分离之后内存段可会销毁,只有一个进程需要执行这一步


Linux里的死锁一般是怎么造成的?

一个线程拿了锁没有被释放
山羊过独木桥一样,两个线程因为需要对方的锁才能将自己的锁解开,从而出现死锁
一个线程持有多把锁,有可能会出现死锁

总结:依托答辩!!!!今晚听录音写博客的时候都想抽死我自己了,我是什么垃圾啊(哭),下次一定要比这次更好,但谁知道有没有下次呢。

果然不出我所料,下午就收到感谢信了捏
鼠鼠我啊,就是这样的捏~不说了,健身去了

  • 标题: 浙江宇视科技一面
  • 作者: 这题超纲了
  • 创建于: 2023-02-24 19:50:20
  • 更新于: 2023-06-23 14:31:30
  • 链接: https://qx-gg.github.io/2023/02/24/blog5/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
推荐阅读
进程间通信的各种方法以及优缺点 进程间通信的各种方法以及优缺点 计算机网络模型及各层的作用和主要协议 计算机网络模型及各层的作用和主要协议 应用层相关协议 应用层相关协议
 评论
此页目录
浙江宇视科技一面