校招实习面试经验-后台开发

C++

  1. strcpy和strncpy的区别
    答:strcpy(char *dest,char *src),strncpy(char *dest,char *src,int n);
    strncpy可以控制字符串复制的长度,从而避免溢出情况,例如src数组的最后一位并不是\0的时候
  2. 快速排序
    答:快速排序是每次选取一个数(一般取当前数组的第一个元素)作为中间值,(之后全部默认从小到大排序)将小于该值的放左边,大于该值的放右边,然后从中间值处分为两部分开始进行递归。
    一般方法的算法复杂度为:O(nlog(n))到O(n*n);
    改进方法:
    a.随机调戏选中间值
    b.当数据比较大时,当划分到的自区间比较小时改用其它排序方法(归并排序,插入排序)
    c.多线程进行
  3. 基数排序(桶排序)
    简单理解:先从个位开始,然后将排序结果按照十位进行排序。
  4. 一个包含很多字符串的文本文件,计算出每个字符串出现的次数?
    在网上看很多人都使用了STL的MAP,但我觉得如果只是计算每个字符串出现的次数,那么使用字典树应该更好,无论是在存储空间还是更新和查找上效率都更胜一筹。
  5. STL Map的底层实现
    这篇博客写的挺好的,可以参考:http://blog.csdn.net/mxway/article/details/29216199
  6. 为什么将构造函数不写成虚函数,而析构函数要写成虚函数?
    (1.1)每个虚函数都对应一个虚表(Vtable),而虚表是存储在对象的内存空间里的,但是构造函数在对象生成之前就要调用,此时还没有空间,Vtable还不存在。
    (1.2)虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义。而且虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
    (1.3)构造函数的调用是完全忽略之后的继承者的。
    (2.1)在实现多态时,用基类指针去操作派生类,为了防止在析构时只析构基类而没有析构派生类。写成虚函数就可以保证基类和派生类都被析构。
  7. static关键字的用法
    主要有三种用户(1)局部静态变量(2)外部静态变量/函数(3)静态数据成员/成员函数.
    在内存方面,static分配在静态存储区, 在程序整个运行期间都不释放。static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次。局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符。
    如果作为静态数据成员/成员函数,那么在整个内存中只有一个副本,即任何实例对其操作后其它实例的再调用时内容也会变化。
  8. 智能指针
    智能指针也是模版,即shared_ptr类。需要初始化,否则就是一个空指针。通常比较安全的内存分配和使用方法是调用一个名为make_shared的标准库函数:
    //指向一个值为42的int的shared_ptr
    shared_ptr p3=make_shared(42);
    //指向一个值为”999999999”的string
    shared_ptr p4=make_shared(10,’9’);
    //也可以采用new返回的指针来初始化智能指针
    shared_ptr p2(new int(42));//正确
    shared_ptr p1=new int(42);//错误
    //返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针
    shared_ptr clone(int p){
    return new int(p); //错误:隐式转换为shared_ptr
    }
    shared_ptr clone(int p){
    return shared_ptr(new int(p)); //正确
    }
    当指向一个对象的最后一个shared_ptr被销毁时,系统会调用析构函数来完成销毁工作。其中shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。而当一个函数返回一个shared_ptr,指向一个类型的动态分配的对象,而这个存储shared_ptr的是一个局部变量或成员变量,那么在对象被销毁时所指向的动态分配的对象也会被销毁,但如果还有其它shared_ptr也指向这块内存,这块内存就不会被关联销毁。
    程序使用动态内存主要出于三种原因:
    (1)程序不知道自己需要使用多少对象.
    (2)程序不知道所需对象的准确类型.
    (3)程序需要在多个对象间共享数据.
    不要混合使用普通指针和智能指针:shared_ptr可以协调对象的析构,但这仅限于其自身的拷贝之间,所以不推荐使用new而应使用make_shared,从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr上
    虽然不能传递给函数process一个内置指针,但可以给其传递一个临时的shard_ptr,但这样很容易导致错误。
    process(shared_ptr(x)); //合法,但是内存会被释放!
    在这个表达式执行结束后,这个临时变量就被销毁了,同时这个临时变量会递减引用数,此时引用计数就变为0,内存会被释放。

  9. 如果父类中仅有方法,子类有一个 int 变量,这时候 sizeof 是多大?
    答:答案是4,因为只有子类的int变量占据大小。但如果存在虚函数(无论子类还是基类中),那么大小是8,因为虚函数表(Vtable)是存在于内存中的. 如果只是sizeof(父类)则为1,这是为了内存对其,因为每个类都要给分配空间,而如果再加入一个int,则sizeof(父类)则变为了4,是int的大小,类中的函数不计算内存,但是虚函数表需要占用内存,大小为指针大小。

  10. 指针的大小是多少?
    答:在32位系统中是4字节,在64位系统中是8字节。因为指针指示的是一个内存地址,所以与操作系统有关。但这个也不是绝对正确的,因为64位系统兼容32位,对应的32程序的指针也是32位的,此时使用sizeof()得到的便是4(即32位),例如编写win32程序时,指针就是32位

  11. #define 和 inline 函数的区别是什么?
    #define只是预编译器上符号表的简单替换,不能进行参数有效性检测及使用C++类的成员访问控制。inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。inline代码放入预编译器符号表中,高效;它是个真正的函数,调用时有严格的参数检测;它也可作为类的成员函数。

  12. const static 在哪里初始化
    答:(1)普通的变量,直接在定义时初始化。(2)类成员变量,只能是int类型在类内初始化。其它需要在类外初始化。
    //类内定义
    const static double a;
    const static int b=2;
    //类外初始化
    const double a=1.1;
  13. 什么情况下基类的析构函数没有被调用?
    答:父类指针指向子类,且子类的析构函数不是虚函数的时候。
  14. 如何实现 vector?优化 O(n) 的复制?
    答:在STL中,除了vector以外,其它类型的复制采用interator复制,而vector进行了单独处理。由于vector的实现是数组,在数组空间饱和以后系统会重新分配一块内存,然后将之前的数组整体复制到新的内存中,因此复杂度为O(1),而不再是O(n)。
  15. Dubug模式和Release模式的区别?
    答:Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
  16. malloc()函数分配的内存空间连续吗?
    (1)在Windows系统下,使用malloc分配的内存空间在逻辑地址空间上是连续的,但是转换到物理内存空间上有可能是不连续的,因为有可能相邻的两个字节是在不同的物理分页上。
    (2)在Linux系统下,使用malloc分配的空间在逻辑地址和实际物理地址上都是连续的,因为linux系统的虚拟地址和物理地址是一一对应的。
    频繁的使用maclloc()函数容易造成大量碎片。
  17. 32位程序在64位系统上能运行吗?64位程序在32位系统上能运行吗?为什么?
    答:这里写图片描述
  18. printf和cout的区别?
    答:printf是函数调用,cout是对象方法调用。关于缓冲区的问题,printf和cout都有缓冲区,但是在vs和linux下却有所不同,在vs中,输出语句执行以后输出的内容在控制台中就会展示出来,但是在linux下只有\n或程序执行结束等刷新缓冲区时才会在终端输出。
  19. 虚函数和纯虚函数的区别?
    虚函数定义:virtual void fun();
    纯虚函数定义: virtual void fun()=0;
    区别:虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数!纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数!
  20. 静态static
    (1)静态成员变量:这个变量只能被定义一次,而且和同类的所有实例共享。静态成员变量的一个作用就是统计该类实例化的数目。其需要设置为public属性,且只能在体外(包括类题,函数体等)初始化:type ClassName::variable=0;(例如int Base::num=0;)
    (2)静态成员函数: a.出现在类体外的函数定义不能指定关键字static。b.静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数。c.非静态成员函数可以任意地访问静态成员函数和静态数据成员。d.静态成员函数不能访问非静态成员函数和非静态数据成员。e.由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长。f.调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,当同一类的所有对象使用一个量时,对于这个共用的量,可以用静态数据成员变量,这个变量对于同一类的所有的对象都取相同的值。
  21. 地址相加
    地址相加的时候是加的类型长度,比如定义unsigned char p1;计算:p1=(unsigned char )0x801000;则得到的是0x801005,因为一个char型只占1个字节,如果定义unsigned long p2;则计算p2=(unsigned long )0x810000;得到的就是0x810014,因为一个long占4个字节(在32为机器下),因此+5就是加了4*5=20个字节,换算成16进制就是0x14。
  22. 浅拷贝和深拷贝
    浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误。最能体现前拷贝和深拷贝的就是“=”的重载。重载”=”的时候重载函数里要实现内存的分配。
    (1)没有重载=之前
    A a,b;
    a=b; //这里是赋值操作
    A a;
    A b=a; //这里是浅拷贝
    (2)重载=之后
    A a,b;
    a=b; //这里是深拷贝操作
    A a;
    A b=a; //这里还是浅拷贝
  23. 函数返回数组
    函数内new或malloc得到的内存是存在与内存的栈区中,等到函数调用结束后,栈中的内存会被释放掉,因为如果将函数内的指针返回的话,会出现内存错误。因为此时栈空间已经不存在了。
  24. const指针
    (1)指针
    我们先来看4个定义语句
    [1]const int* a=1;
    [2]int const *a=1;
    [3]int * const a=1;
    [4]const int* const a=1;
    const在定义时的作用主要分为两类,即在*左边和在*右边.如果在*左边则表示指针所指向的对象为常量,即不能修改指针指向对象的内容,即*a=3;这样的操作是错误的,上述[1],[2]则是这种情况。如果const在*的右侧,则表示指针本身是一个常量,即指针指向的地址不能修改,例如a++操作是错误的,上述[3]则为这种情况。上述[4]则表示指针和内容均为常量。
    (2)函数返回值
    函数返回值使用const修饰时和普通的一样,只是为了保证返回的左值不能被修改。
    (3)类的const成员
    作为类的成员时,必须在构造函数的初始化列表中进行初始化,不能在构造函数中进行。
    class Base{
    public:
     static int A;
     Base(int a):A(a){};//正确
     Base(int a){ A=a;};//错误
    }
    (4)类的const函数
    将类的成员函数定义为const表示在函数中没有值的修改,写法实在函数最后加上const修饰,这样可以极大的提升代码的健壮性.如果其中加入了修改内容,则会在编译时报错。
    void fun() const{};
  25. 纯虚函数的作用?虚继承的作用?

网络

  1. 各个设备的工作在网络的哪一层?
    网卡工作在物理层
    中继器工作在物理层,用来复原网络中的信号并重新发送到其他网段上
    集线器工作在物理层,用于连接各物理设备
    网桥工作在数据链路层的介质访问控制(MAC)子层上,用于在多个使用同一种通信协议的网段中传送数据包的设备
    交换机也是在数据链路层,作用类似网桥
    路由器工作在网络层
  2. HTTP 和 TCP 有什么关系?
    答:HTTP是应用层协议,TCP是传输层协议
  3. TCP 的三次握手过程和四次挥手过程是怎样的?
    建立连接(三次握手)
    第一次握手: 客户端————————————SYN(x)———————————–>服务器端
           进入SYN_SEND状态
    第二次握手: 客户端<—————————–SYN(y)_ACK(x+1)—————————-服务器端
                                        进入SYN_RECV状态
    第三次握手:客户端———————————–ACK(y+1)———————————>服务器端
          进入Established状态
    终止连接(四次挥手)
    第一次挥手:客户端—————————————FIN————————————->服务器端
    调用close,告诉客户端数据发送完毕
    第二次挥手:客户端<————————————-ACK————————————–服务器端
                                        服务器端确认收到
    第三次挥手:客户端<————————————-FIN————————————–服务器端
                                       结束进程并告诉客户端
    第四次挥手:客户端————————————–ACK————————————>服务器端
         客户端确认收到
    说明:(1)第一次挥手的FIN可以在最后一次数据传输时一起发送。(2)第二次挥手和第三次挥手在没有其它内容时可以一起发送。(3)任何一方的进程中止时也都将发送一个FIN给对方。
    被动断开连接的一方在收到ACK并回复FIN(即last_ack)后就要进入2MSL等待状态,确认主动发收到后(即ACK回复)再断开。从而确保双方都断开了。
  4. TCP 和 UDP 的区别?
    答:(1)TCP需要建立连接,而UDP不需要建立连接。(2)TCP传输可靠,UDP传输不可靠。(3)TCP适用于大量数据的传输,UDP适合少量数据的传输。(4)TCP慢,UDP快。
  5. TCP 包为什么需要 Seq?
    答:Seq:sequence number,双方用来验证数据包的顺序码,为保证传输的可靠性。
  6. TCP包有没有IP地址?
    答:TCP包是没有IP地址的,那是IP层的事情,但是有源端口和目的端口。
  7. HTTP的概念
    HTTP是超文本传输协议,是一个客户端与服务器端之间请求和应答的标准。其不仅能够保证快速的传输文本文档,还能控制传输文本中的哪一部分以及哪部分内容首先显示。HTTP使用的是TCP协议。
    请求和相应消息的组成:(1)一个起始行(2)一个或多个头域(3)一个指示头域结束的空行(4)可选的消息体。
    其中头域包括:(1)通用头(2)请求头(3)响应头(4)实体头
    一次HTTP操作的4个组成部分:(1)客户与服务器建立连接,通常点击链接即可。(2)建立连接后,客户机发送请求给服务器,请求的格式为:统一资源标识符URL,协议版本号,请求修饰符,客户机信息,可能的内容.。(3)服务器接受请求后给予相应的响应信息,为一个状态行:协议版本号,一个成功或错误的代码,服务期信息,实体信息,可能的内容。(4)客户端接收服务器返回的信息通过浏览器进行显示,并与服务器断开连接。
    HTTP(端口80)通常承载与TCP协议之上,但现在也有承载于TSL或SSL协议之上的,即我们所说的HTTPS(端口443)。由于HTTP永远是客户端发起请求,那么服务器端就无法主动向客户发起请求。
  8. HTTP 1.0 和 HTTP 1.1 有什么差别?
    HTTP1.0不允许长连接,在每次传输完成后系统会自动断开连接,而HTTP1.1允许长连接。
    其中存在的问题:由于HTTP1.0不允许长连接,因此每一次请求都需要建立连接,当一个Web页面上有很多img等文件时,由于HTTP1.0的限制,每次只能传输一个文件,那么将Web页面加载完就需要很多次连接,因此造成了巨大的耗时。
    解决方式:因此推出HTTP1.1来支持持久连接,在一次TCP连接上可以传输多个HTTP请求和响应,减少连接建立和关闭时的消耗的延时。同时HTTP1.1不需要等待上一次请求返回结果就能够继续发送下一次请求,但服务器端必须按照接收到请求的顺序来返回响应结果,以保证客户端能够区分出每次请求的响应结果。
    HTTP1.1不仅继承了HTTP1.0的优点,还进行了功能的扩充。例如对请求头和响应头的扩充,HTTP1.0不支持HOST,而HTTP1.1增加了HOST头之后就可以在同一个ip地址上部署多个系统。除此之外,HTTP1.1还提供了身份认证、状态管理、和Cache缓存等相关请求头和响应头。
  9. 为什么HTTP1.0是无连接,无状态?
    答:无连接是说每次连接只处理一次请求,请求结束后即断开连接。无状态是指协议对事务处理没有记忆能力,因此每个事务之间是相互独立的,如果要后续处理事务,则需要客户端进行重传。
  10. TCP的拥塞控制是怎样的?
    参考:http://blog.csdn.net/kinger0/article/details/48206999
  11. 如何判断服务器文件是否已修改?知道浏览器缓存的文件与服务器文件不一致?在 HTTP 中哪个字段表示?
    参考:http://www.cnblogs.com/tzyy/p/4908165.html
  12. ARP协议?
    ARP(Address Resolution Protocol)是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,然后接受返回的消息(正常只有ip地址符合的主机才会响应),接收到消息以后将IP地址和物理地址存储在自己的ARP表中,方便下次使用。
    工作过程
    第1步:根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存
       中检查主机B的匹配MAC地址。
    第2步:如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广播到本地网络上的
       所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是
       否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
    第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
    第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
    第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期
       的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。
    ARP欺骗
    由于ARP请求是在网络上进行广播的,且主机收到应答报文时不会检测报文的真实性,因此可以随意发送报文来篡改MAC地址表。导致通信重定向,从而获取他人的想要发送的信息。
    防御措施
    (1)应将网络信任建立在IP+MAC的基础上
    (2)设置静态IP+MAC表
    (3)设置防火墙
  13. 局域网中的主机如何与外部通信?
    采用NET技术,将内网IP与唯一端口绑定进行转发。
  14. 一个PPP帧的数据部分(用十六进制写出)是7D 5E FE 27 7D 5D 7D 5D 65 7D 5E.试问真正的数据是什么?
    答:由于PPP帧的标志字段为7E,因此,为了区别标志字段和信息字段,将信息字段中出现的每一个0x7E转变成(0x7D,0x5E),0x7D转变成(0x7D,0x5D).
    所以7D 5E FE 27 7D 5D 7D 5D 65 7D 5E中实际上是:7E FE 27 7D 7D 65 7E.
  15. 长URL -> 短UR
    用MD5将长URL压缩,然后把2个URL的映射存储在数据库中。
    MD5流程:http://blog.csdn.net/forgotaboutgirl/article/details/7258109
  16. TCP和UPD是否存在粘包?
    答:UDP不存在数据“粘包”的情况,要么接收不到数据,要么接收到一个完整的包。而TCP出现粘包的原因是Nagle算法造成的,其为了改良网络传输效 率所产生的。
    解决粘包的方式:给数据加上包头,这样数据就变成了包头和包体两部分,如果为了过滤非法包,则还要加上“包尾”。通常也会在所发送的内容前加上发送内容的长度,所有对方会先收到4Byte,解析获得接下来需要接收的长度,再进行收包。
  17. RTP
    RTP提供的各种服务包括有效负载识别,序列编号,时间戳和投递监听。RTP能够序列化包,当这些包在收端不是按顺序到达的时。序列号也能被用来识别包丢失。时间戳被用于媒体有效的播放。到达的数据一直被RTCP监听,以通知RTP层来校正其编码和传输的参数。例如,如果RTCP层检测到包丢失,它会通知RTP层减缓发送速率。

操作系统

  1. 用于提高cache命中的算法
    (1)FIFO(First In First Out)即最先进入到cache的先被替换出
    (2)LFU(Least Frequently Used)把最近使用频次最低的优先替换出cache
    (3)LRU(Least Recently Used)
  2. 32位操作系统和64位操作系统的内存空间是多大?
    答:32位不是4GB的内存(当然,我们平时的实际中也会发现只有3.XGB的内存)。我们平时编程使用的内存地址是逻辑地址,并不是内存实际的物理地址。且操作系统在寻址的时候不仅要寻找内存地址,而且要为BIOS ROM,cache,I/O等分配地址,因此操作系统可以读到的物理地址就没有4G了。
    这里写图片描述
  3. RAID(Redundant Arrays of Independent Disks,磁盘阵列)
    RAID是通过一些技术将一个或多个磁盘组合成一个较大的磁盘空间设备,并且还具有一定的数据保护功能的磁盘阵列。
    (1)RAID0 数据分条技术
    整个逻辑盘的数据是被分条(stripped)分布在多个物理磁盘上,可以并行读/写,提供最快的速度,但没有冗余能力。要求至少两个磁盘。RAID 0首先考虑的是磁盘的速度和容量,忽略了安全,只要其中一个磁盘出了问题,那么整个阵列的数据都会不保了, 没有提供冗余或错误修复能力,但实现成本是最低的。
    (2)RAID1 镜像
    将磁盘镜像复制到另一台或多态服务器,数据安全得到了保证,但成本高,磁盘利用率低。
    (3)RAID (0+1)/10
    为了达到既高速又安全,出现了RAID 10(或者叫RAID 0+1),可以把RAID 10简单地理解成由多个磁盘组成的RAID 0阵列再进行镜像。
    (4)RAID3:带奇偶校验码的并行传送
    只能查错不能纠错,访问数据时一次处理一个带区(这样可以提高读取和写入速度)。校验码在写入数据时产生并保存在另一个磁盘上。需要实现时用户必须要有三个以上的驱动器,写入速率与读出速率都很高,因为校验位比较少,因此计算时间相对而言比较少。用软件实现RAID控制将是十分困难的,控制器的实现也不是很容易。它主要用于图形(包括动画)等要求吞吐率比较高的场合。不同于RAID 2,RAID 3使用单块磁盘存放奇偶校验信息。如果一块磁盘失效,奇偶盘及其他数据盘可以重新产生数据。 如果奇偶盘失效,则不影响数据使用。RAID 3对于大量的连续数据可提供很好的传输率,但对于随机数据,奇偶盘会成为写操作的瓶颈。
    (5)RAID5:分布式奇偶校验的独立磁盘结构
    RAID 5不单独指定的奇偶盘,而是在所有磁盘上交叉地存取数据及奇偶校验信息。在RAID 5上,读/写指针可同时对阵列设备进行操作,提供了更高的数据流量。RAID 5更适合于小数据块和随机读写的数据。RAID 3与RAID 5相比,最主要的区别在于RAID 3每进行一次数据传输就需涉及到所有的阵列盘;而对于RAID 5来说,大部分数据传输只对一块磁盘操作,并可进行并行操作。在RAID 5中有“写损失”,即每一次写操作将产生四个实际的读/写操作,其中两次读旧的数据及奇偶信息,两次写新的数据及奇偶信息。
    这里写图片描述
  4. netstat tcpdump ipcs ipcrm的意义?
    netstat:显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。Netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。
    tcpdump:将网络中传送的数据包完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。
    ipcs:显示当前Linux系统中的共享内存段、信号量集、消息队列等的使用情况。
    ipcrm:删除一个或更多的消息队列、信号量集或者共享内存标识。
  5. 查看所有知名服务器端口号
    cat /etc/services | grep XXX(| grep 表示进行检索,XXX表示需要检索的内容)
  6. IO操作
    IO有两种操作,同步IO和异步IO。同步IO:必须等到iO操作完成后,控制权才返回给用户进程。异步IO:无需等待IO操作完成,就将控制权返回给用户进程。当IO发生时,涉及2个对象,一个是调用IO的进程,另一是系统内核。
  7. 4种网络IO模型
    (1)阻塞IO模型
    IO操作彻底完成之后才会回到用户空间。当应用进程调用了recvfrom这个系统调用后,系统内核就开始了IO的第一个阶段:准备数据。这时数据还没来,整个进程则会被阻塞起来。等到数据到来以后,才会将数据从系统内核拷贝到用户内存中,然后从内核返回结果,用户进程这时才解除阻塞,所以阻塞IO模型的特点就是在等待数据拷贝数据阶段都被阻塞了。大部分Socket接口都是阻塞型的。
    为什么一个socket可以accept多次?
    答:当请求连接到来时,将该请求连接加入请求队列。调用accept()接口正是从socket fd的请求队列抽取第一个连接信息,创建一个与fd同类的新的socket返回句柄,即后续read()和recv()的输入参数。而如果没有请求则进入阻塞。
    如果大量访问时应如何处理?
    大多数人想到的可能是多线程或线程池,虽然线程池对相比多线程可以减少资源开销,但面对成百上千的请求时仍无法满足要求,因此可以尝试采用非阻塞模型。
    (2)非阻塞IO模型
    非阻塞IO模型就是一直请求,如果有数据则进行数据复制,否则返回错误并进行下一次请求。但这个方法并不被推荐,因为会消耗大量的cpu资源。实际的linux系统中,采用了select()多路复用模式。
    (3)多路IO复用模型
    即事件驱动IO,就是有个函数(如select)会不断的轮询所负责的所有socket,当某个socket到达时,就通知用户进程。
    select的优势:可以同时处理多个连接,如果连接数不是很高的话,select/epoll的web server不一定比多线程阻塞式IO更好。它的优势并不是对单个连接能够处理的更快,而是能够处理更多的连接。且相比于其它模型,select()的事件驱动模型只是单线程执行,占用资源少,不消耗套多CPU资源,同时能够为多个客户端提供服务。
    **select的问题:**select()并不是“事件驱动”最好的选择,当探测的句柄较大时,select()将消耗大量的时间去轮询各个句柄,相比之下,epoll等接口效果更好。同时,模型将事件探测和事件响应夹杂在一起,一旦时间响应执行过于庞大,则对整个模型是灾难性的。
    解决办法: libevent库、libev库等都可以避免上述问题,或采用异步响应的IO操作(linux在2.6以后引入了aio_read和aio_write.
    (4)异步IO模型
    当发起read()操作之后,就立刻去执行其它事情,等待内核响应read()后再立即返回执行read()操作。
  8. select(),poll(), epoll()
    select是用来实现多路复用的。
    epoll()是select和poll的增强版本。相比于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放在内核的一个事件表里,这样用户空间和内核空间之间的数据拷贝只需要一次。
    select,poll和epoll本质上都是同步IO。select的移植性好,但可以监控的文件少,poll可以监控的文件数目远远大于select。epoll的IO效率不随FD数目增加而线性下降,因为epoll是根据每个fd上的callback函数实现的,只有活跃的socket才会主动去调用callback()
    epoll的具体流程:(1)socket绑定并监听(2)添加监听描述符事件(3)epoll_wait(),会返回ret(需要处理的事件数目),events(4)如果有事件调用handle_events()去处理,否则继续epoll+wait().
    主要的三个处理:(1)handle_accpet(),结束后原本的listen事件不删除(2)do_read(),如果读完则modify_event()为下一步事件,否则删除(3)do_write(),如果读完则modify_event()为下一步事件,否则删除。
  9. epoll的使用
    创建epoll: int epoll_create(int size);
    epoll的事件注册函数: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    **struct epoll_event结构:**struct epoll_event{ __uint32_t events; epoll_data_t data; }
    等待事件的产生: int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  10. 多线程是什么?
    答:一个线程有多个进程,且只有栈中最顶端的(一般操作系统是大端内存,即栈内存中最低位)才能被调用,因此,一个多线程的进程在内存中有多个栈,多个栈之间空白内存隔开以保证栈的增长。每个线程调用自己栈中的函数,并与其它线程共享内存中的text,heap和global data区域。
  11. 线程的使用
    创建线程: int pthread_create(pthread_t thread, const pthread_attr_t *attr, void (start_routine) (void ), void *arg);
    等待线程结束: int pthread_join(pthread_t thread,void **retval);
    线程退出: (1)等待线程函数结束(2)void pthread_exit(void *retval);
    获取线程ID: (1)pthread_create的返回值(2)int pthread_self(void);
    线程的状态: (1)分离状态:线程结束后资源将被系统回收,不可以由其它线程杀死并回收资源 (2)结合状态,可以由其它线程杀死并回收资源。
    线程的3种调度策略:(1)先入先出策略(SCHED_FIFO)(2)循环策略(SCHED_RR)(3)自定义策略(SCHED_OTHER)
    将已经创建的线程分离: pthread_detach(int tid);线程分离以后在主线程里调用pthread_join(…)时函数会出错,即返回不再是0。如果主线程先结束了,那么进程就结束了,其它子线程也就结束了。
    每日持续更新中……
    条件变量: 适用于多个线程等待某个条件的发生,如果一个锁被释放了,则唤醒其它等待的线程。创建pthread_cond_t类型的信号量,用pthread_cond_wait(pthread_cond_t *qready,pthread_mutex_t *qlock)来等待,用pthread_cond_signal(pthread_cond_t *qready)来唤醒。在pthread_cond_wait的时候线程会先解锁再等待最后等到条件变量的时候再加锁继续执行。
    出租车问题: 在条件变量的使用时,可能发生一个车先到了接车点,发现没有乘客(pthread_cond_signal函数无作用),则等待,而乘客到达之后没有接收到条件变量,因此无法发现车。解决办法:出租车到了之后则一直while进行pthread_cond_signal的触发。
    读写锁: 包括强读者和强写者。(1)强读者同步:总是给读者更高的优先权,只要写者当前没有进行写操作,读者就可以获得访问权限。(2)强写者同步:总是将优先权交给写者,而读者只能等待正在等待或正在执行的写者结束以后才能执行。
  12. 进程的基本概念
    进程由3部分组成:代码段数据段堆栈段。多个进程可以使用同一个代码段。数据段存放全局变量、常量和静态变量。堆栈段用于函数调用,存放函数参访,内部的局部变量,还包括了进程控制块(Process Control Block, PCB),这是进程存在的唯一标识,不需要额外的空间,系统通过PCB对进程进行管理和调度。
    程序转换为进程的步骤: (1)内核将程序读入内存,为程序分配内存控件.(2)内核为该进程分配进程标识符(PID)和其它所需资源.(3)内核为进程保存PID及响应的状态信息,把进程放到运行队列中等待执行,程序转化为进程后就可以被操作系统的调度程序执行了。
    进程的创建方式:(1)由操作系统创建(2)由父进程创建.
  13. 进程创建
    fork():在父进程中返回的是子进程的进程id,即Pid。在子进程中返回的0。创建失败返回-1。
    在linux系统中,调用fork()函数时系统内核并不立即复制父进程的数据段和堆栈段,而是当子进程修改这些数据内容时才发生复制操作,内核才会给子进程分配进程空间。
  14. 进程间通信
    (1)管道pipe():管道就是一个文件,在linux系统中是实际可见的,但管道是半工的,即一个进程只能对一个管道读或者写。如果要让两个进程之间双向通信,一个解决办法就是每个客户在向服务器发送信息前都建立自己的读入管道,或让服务器得到数据后再建立管道。使用客户的进程号(pid)作为管道名是一种常用的方法,客户可以先把自己的进程号告诉服务器,然后到那个以自己进程号命名的管道中读取回复。
    (2)消息队列:一个进程创建一个消息队列,然后一直读取,设置msgflg为0,则可以在消息队列为空或满时呈阻塞态,否则立即返回-1.
    (3)共享内存: shmget()得到一片内存和shmid,通过shmid进行内存共享。是进程间最快的通讯方式,但是无法解决同步问题,即什么时候可以取,什么时候不可以取。
    (4)信号量:一种用于多线程同步,即之前提到的条件变量,在一个就是SYSTEM V信号量,用于进程间通信。
  15. 僵尸进程和守护进程
    孤儿进程: 一个父进程推出后,它的一个或多个子进程还在运行,那么这些进程将变为孤儿进程,孤儿进程被init进程管理,并由init对它们进程状态收集。
    僵尸进程: 一个使用fork创建的子进程,如果子进程退出,而父进程没有调用wait或waitpid获取子进程状态,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。但等父进程结束之后,就会退出,再查看状态,僵尸进程就不见了,因为父进程退出以后,僵尸进程交给了init处理,init会调用wait系统调用来清除各个僵尸子进程。
    守护进程: 守护进程生存期比较长,例如打印,作业规划等。linux下普通进程都依附于一个终端,当中断被关闭时进程将会关闭,但守护进程能够突破这种限制。方法:(1)在父进程中创建子进程,父进程退出,子进程让init收养(2)这里需要明白每个或多个进程都有进程组,组长的进程号就是进程组的ID,且该进程组的ID不会因为组长的退出而改变。那么用setid函数创建一个新的会话,并让该进程单人会话组的组长,这里调用setid有3个作用a.让进程摆脱会话的控制b.让进程摆脱原进程组的控制c.让进程摆脱原控制端的控制.那么所有的内容都会重新复制一份。(3)改变当前目录为根目录(4)重设文件权限掩码(5)关闭文件描述符.
  16. CGI(Common Gateway Interface) 通用网关接口
    CGI是Web服务器提供信息服务的标准接口,通过CGI接口Web服务器能够获取用户提交的信息。例如在tomcat下编写test,然后调用http://127.0.0.1:8080/cgi/test 就可以返回test程序中cout的内容。
  17. 内存管理机制
    在windows系统中采用段页式管理,因为虚拟地址和物理地址是不对应的。在linux系统中,虽然也采用了段页式管理,但虚拟地址和物理地址是一一对应的。
  18. 系统的栈和堆
    (1)栈是由系统分配释放,程序结束之后由系统回收,存在与内存的栈区域,存放函数的参数、局部变量等值。其操作方式类似于数据结构中的栈,存放于一级缓存,他们通常是调用时存放于存储空间中,调用结束后即释放。
    (2)堆是由用户分配释放,若程序员分配和释放,如果程序员没有释放,等到程序结束以后系统也会对其释放。分配方式类似于链表,存放在二级缓存中。
    在Windows中,堆栈的大小是静态的,如果Windows系统通过复杂的机制对其进行保护,但是Linux系统的堆栈是动态分配的,即堆栈满了的时候会继续增长,但是没有机制去处理堆栈端对非堆栈段的覆盖,毕竟是开源系统,这个工作需要程序员去控制。
    !!!!!!!看看下边的例子!!!!!!!!!
    这里写图片描述
    输出内容如下:
    0036FB20
    10 20
    0036FB20
    按照常理,在函数中创建的int a[100]应该在函数执行结束以后释放掉,我们不应该得到的b指针应该指向一片空白区域,而且输出b[0]和b[1]的时候应该乱码,但是却没有,这是因为系统执行的内容较少,只是把这一片内存解保护了,但并没有去格式化而已,必经格式化非常占用系统资源,下次用到的时候只需要直接覆盖即可(这也就是我们平时说的在内有初始化的内存中得到的值是不可预见的),但在编译过程中系统已经给出了警告,说函数不能返回其局部变量的地址!
    这里写图片描述

其它

  1. rebuding
  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值