KID_XiaoYuan

导航

面试总结

LINUX 系统编程:

1:CS模型下建立TCP/IP的步骤:

S:进程A:创建socket->准备地址->绑定->监听(设置队列长度)->等待连接->通信->关闭socket
C:进程B:创建socket->准备地址->连接->通信->关闭socket

服务器端:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <stdbool.h>
 5 #include <unistd.h>
 6 #include <sys/wait.h>
 7 #include <sys/types.h>
 8 #include <sys/socket.h>
 9 #include <arpa/inet.h>
10 #include <netinet/in.h>
11 
12 typedef struct sockaddr* saddrp;
13 
14 int main(int argc, char const *argv[])
15 {
16     int sockfd = socket(AF_INET,SOCK_STREAM,0);
17     if (0 > sockfd)
18     {
19         perror("socket");
20         return -1;
21     }
22 
23     struct sockaddr_in addr = {};
24     addr.sin_family = AF_INET;
25     addr.sin_port = htons(atoi(argv[1]));//通过命令行设置端口与ip
26     addr.sin_addr.s_addr = inet_addr(argv[2]);
27 
28     int ret = bind(sockfd,(saddrp)&addr,sizeof(addr));
29     if (0 > ret)
30     {
31         perror("bind");
32         return -1;
33     }
34 
35     listen(sockfd,1024);//设置监听数量
36 
37     struct sockaddr_in client_addr = {};
38     socklen_t addr_len = sizeof(client_addr);
39     bool quit_flag = false;//进程结束标志
40 
41     while(1)
42     {
43         int client_fd = accept(sockfd,(saddrp)&client_addr,&addr_len);
44         if(0 == fork())
45         {
46             while(1)
47             {
48                 char buf[255] = {};
49                 int val = recv(client_fd,buf,sizeof(buf),0);
50                 if(0 == strcmp(buf,"q"))
51                 {
52                     quit_flag = true;
53                     break;
54                 }
55                 sprintf(buf,"Server ID:%d:%s",getpid(),"I will reply u soon");
56                 send(client_fd,buf,strlen(buf)+1,0);
57             }
58             close(client_fd);
59             if(quit_flag == true) exit(0);
60         }
61     } 
62     close(sockfd);
63     return 0;
64 }

客户端:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <stdbool.h>
 5 #include <unistd.h>
 6 #include <sys/types.h>
 7 #include <sys/socket.h>
 8 #include <arpa/inet.h>
 9 #include <netinet/in.h>
10 
11 typedef struct sockaddr* saddrp;
12 
13 int main(int argc, char const *argv[])
14 {
15     int sockfd = socket(AF_INET,SOCK_STREAM,0);
16     if (0 > sockfd)
17     {
18         perror("socket");
19         return -1;
20     }
21 
22     struct sockaddr_in addr = {};
23     addr.sin_family = AF_INET;
24     addr.sin_port = htons(atoi(argv[1]));
25     addr.sin_addr.s_addr = inet_addr(argv[2]);
26 
27     int ret =connect(sockfd,(saddrp)&addr,sizeof(addr));
28     if (0 > ret)
29     {
30         perror("connect");
31         return -1;
32     }
33 
34     while(1)
35     {
36         char buf[255] = {};
37         printf(">");
38         gets(buf);
39         send(sockfd,buf,strlen(buf)+1,0);
40         if(0 == strcmp(buf,"q")) break;
41         ret = recv(sockfd,buf,sizeof(buf),0);
42         if (0 > ret)
43         {
44             perror("read");
45             return -1;
46         }
47         printf("Recv:%d Bytes.\nShow:%s\n",ret,buf);
48         if(0 == strcmp(buf,"q")) break;
49     }
50     close(sockfd);
51     return 0;
52 }

函数说明:

socket()

打开一个网络端通讯端口,成功返回socket的标识符,失败返回-1.

int socket(int domain, int type, int protocol);  

  1. family: 协议族(family),指定了socket的通信地址类型,通常用AF_INET,代表使用ipv4地址。
  2. type: socket类型,通常有两个值可选:TCP/IP 使用 SOCKET_STREAM,指面向流的协议;SOCKET_DGRAM是面向数据包的协议,如UDP。
  3. protocol: 指定协议,通常设置为0, 会根据type类型选择协议。

bind()函数

将socket绑定到对应端口,进行监听,成功返回0,失败返回-1.

int bind(int sockfd, struct sockaddr_in *myaddr, int addrlen);

  1. sockfd: socket描述符,它是通过socket()函数得到的。
  2. myaddr是一个包含本机ip地址和端口等信息的sockaddr指针。
  3. struct sockaddr_in结构类型是用来保存socket信息的:
      struct sockaddr_in {
      sa_family_t sin_family;                 //AF_INET
      in_port_t sin_port;                         //绑定端口号
      struct in_addr sin_addr;               //ip地址
      unsigned char sin_zero[8];         //为了与sockaddr大小一致增加的补位,sockaddr_in是前者的改进结构
      };
  4. addrlen是myaddr的长度

 

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(SERVERPORT);
myaddr.sin_addr.s_addr = inet_addr(INADDR_ANY);//表示可以接收来自任意ip的请求
bzero(&(myaddr.sin_zero), 8);

 

connect()函数

由客户端发起,与服务端建立连接,成功返回0,失败返回-1.

int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);

  1. sock_fd是socket描述符,由socket()创建。
  2. serv_addr表示服务器的地址信息。
  3. addrlen表示serv_addr长度

listen()函数

服务端需要对多个客户端进行通讯服务,listen()函数实现对socket描述符的监听。成功返回0,失败返回-1.

int listen(int sock_fd, int backlog);

  1. sock_fd表示要监听的socket描述符
  2. backlog表示监听请求队列的最大值,超出这个返回连接请求就会被忽略。

accept()函数

TCP服务端有listen()监听到一个连接请求后交给accept()接受该请求,这样连接就建立成功,之后就可以进行IO操作了。

成功返回新的socket描述符,失败返回-1.

int accept(int sock_fd, struct sockaddr_in* remote_addr, int addrlen);

  1. sock_fd是socket()创建的描述符
  2. remote_addr是一个结果参数,它用来接收客户端的地址信息。
  3. addrlen是remote_addr的大小。

send()函数

通过accept()得到socket描述符,并调用该标识符向该标识符指代的socket发送信息。成功返回实际发送的数据长度,失败返回-1.

int send(int sock_fd, const void *msg, int len, int flag)

  1. sock_fd: 要写入并传输数据的socket。
  2. msg:写入的数据的指针。
  3. len:写入数据的大小。
  4. flag:默认0, 用法略。

recv()函数

接受socket传输过来的数据。成功返回实际接收数据的长度,失败返回-1.

int recv(int sock_fd, void *buf, int len, int flag);

  1. sock_fd:接受数据的socket描述符。
  2. buf:是接受数据的缓冲区。
  3. len:是缓冲区的长度

就IO函数来说,不只是上面说的,总共有:

  1. read() / write()
  2. send() / recv()
  3. readv() / writev()
  4. recvmsg() / sendmsg()
  5. recvfrom() / sendto()

close()函数

顾名思义,关掉相应的socket描述符。

2:进程的资源有哪些:

PCB的主要资源:

标识相关:pid,ppid等等
文件相关:进程需要记录打开的文件信息,于是需要文件描述符表
内存相关:内存指针,指向进程的虚拟地址空间(用户空间)信息
优先级相关:进程相对于其他进程的调度优先级
上下文信息相关:CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
状态相关:进程当前的状态,说明该进程处于什么状态
信号相关:进程的信号处理函数,以及记录当前进程是否还有待处理的信号
I/O相关:记录进程与各种I/O设备之间的交互

进程和线程:

进程是系统进行资源分配和调度的一个独立单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源,一个线程可以创建和撤销另一个线程;     同一个进程中的多个线程之间可以并发执行。

堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

 

栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

现在操作系统的线程实现模型:

用户态的执行负责进程内部线程在,即我们同时实现内核态和用户态线程管理。每个内核态线程可以服务一个或者更多个用户态线程。

线程从用户态切换到内核态:

什么情况下会造成线程从用户态到内核态的切换呢?

首先,如果在程序运行过程中发生中断或者异常,系统将自动切换到内核态来运行中断或异常处理机制。

此外,程序进行系统调用也会从用户态切换到内核态。

 

 

 

 

 

线程共享资源

 

线程独享资源

地址空间

程序计数器

全局变量

寄存器

打开的文件

子进程

状态字

闹铃

 

信号及信号服务程序

 

记账信息

 

 

 

 

 

3:进程和程序的主要区别

3)进程与程序的主要区别:

 (1)程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;

 (2)程序是静态的观念,进程是动态的观念;

 (3)进程具有并发性,而程序没有;

 (4)进程是竞争计算机资源的基本单位,程序不是。

 (5)进程和程序不是一一对应的: 一个程序可对应多个进程即多个进程可执行同一程序; 一个进程可以执行一个或几个程序

 

 

4:进程间通信:

管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

 

5:共享内存

每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

 

Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制

(1)Proc A 到共享内存       (2)共享内存到 Proc B

因为直接在内存上操作,所以共享内存的速度也就提高了。

内存的分块:

1、栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。

2、堆区(heap) — 在内存开辟另一块存储区域。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上

3、全局区(静态区)(static)—编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。而C++则没有这个区别 - 程序结束后由系统释放

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码。

共享内存的接口函数以及指令

1.查看系统中的共享存储段

ipcs -m
 

2.删除系统中的共享存储段

ipcrm -m [shmid]

3.shmget ( ):创建共享内存

int shmget(key_t key, size_t size, int shmflg);
4:shmat ( ):挂接共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg);
[参数shmid]:共享存储段的标识符。

[参数*shmaddr]:shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用)。

[参数shmflg]:若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。

[返回值]:成功返回共享存储段的指针(虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1。

5.shmdt ( ):去关联共享内存

当一个进程不需要共享内存的时候,就需要去关联。该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。

int shmdt(const void *shmaddr);
[参数*shmaddr]:连接以后返回的地址。

[返回值]:成功返回0,并将shmid_ds结构体中的 shm_nattch计数器减1;出错返回-1。

6.shmctl ( ):销毁共享内存

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
[参数shmid]:共享存储段标识符。

[参数cmd]:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。

[参数*buf]:设置为NULL即可。

[返回值]:成功返回0,失败返回-1。

总结:

(1)优点:我们可以看到使用共享内存进行进程之间的通信是非常方便的,而且函数的接口也比较简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。

(2)缺点:共享内存没有提供同步机制,这使得我们在使用共享内存进行进程之间的通信时,往往需要借助其他手段来保证进程之间的同步工作。

 计算机网络

OSI七层网络模型:(从下往上)

物理层:接口特性 传输模式

数据链路层:成帧 差错控制 (CRC ARQ) 流量控制(停止等待协议 GBN SR ) 传输管理(FDM WDM TDM CDM ALOHA CSMA CSMACA CSMACD)

网络层:成数据报 路由选择 流量控制 拥塞控制 差错控制 网际互连(IP IPX ICMP ARP IGMP OSPF RARP)

传输层:报文段 可靠传输 流量控制 差错控制 复用分用(TCP UDP)

会话层:建立连接 有序传输(同步) 建立管理 通信恢复

表示层:数据格式翻译 数据加密解密 数据压缩恢复

应用层:用户网络 FTP SMTP HTTP

TCP家族:TCP IP ICMP IGMP ARP RARP UDP DNS FTP HTTP

TCP保证可靠传输:1校验 2序号:保证有序提交 建立在字节流上 把缓冲区每个TCP报文段的首部字节序号 3确认 累计确认 4:超时重传

IPV4报头内容: 版本 首部长度 总长度 标识 标志 片偏移 TTL 协议 首部检验和 源地址 目的地址

C++

多态

同一个事物在不同场景下的多种形态

多态分类:静态多态:函数重载 泛型编程 动态多态:虚函数

静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错.

 

 如果基类指针指向了一个派生类对象,虽然使用了teacher的成员变量,但是却没有使用他的成员函数,基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数,为了解决这个问题,才引入了虚函数的概念。使用了虚函数以后,基类指针指向的基类对象就是使用基类的成员,派生类就是派生类 成员,这中现象多态。

目的是通过基类指针对所有派生类进行访问,特别是成员函数,如果没有多态,只能访问成员变量。

动态多态: 显然这和静态多态是一组反义词,它是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。

 virtual使得函数称为虚函数,实现指向派生类的基类指针访问派生类成员函数。

派生类对象的替换原则:凡是基类对象出现的场合都可以用公有派生类对象进行取代。

派生类对象替换基类对象:凡是基类对象出现的场合都可以用公有派生类对象取代。

三种替换形式:1:派生类对象给基类对象赋值 2:派生类对象可以初始化为基类对象的引用 3:可以令基类对象的指针指向派生类对象,派生类的地址传递给基类的指针。

虚函数:virtual 函数返回类型 函数名(参数表)

{

函数体

}

 

虚函数的用途:实现运行时候的多态性,通过指向派生类的基类指针,方位派生类中同名覆盖的成员函数。应通过指针或者引用来调用虚函数,而不要以对象名调用虚函数。在派生类中重定义的基类虚函数扔为虚函数,同事可以省略virtual关键字。不能定义虚构造函数,但是可以定义虚的析构函数。

纯虚函数:virtual 返回类型 函数名(参数表)=0.至少包含一个纯虚函数的类称为抽象类。抽象类不能实例化,只能作为基类被继承,但是可以定义抽象类的指针或引用。

浅拷贝:只复制直接成员变量。深拷贝:同时复制资源如指针指向的空间。

浅拷贝运算符:=,系统默认的拷贝构造函数实现的都是浅拷贝。

深拷贝的实现:1:自定义一个成员函数2:重载拷贝构造函数。3:重载赋值运算符。

虚函数表:多态的调用原理。

虚表是一个函数指针数组,按照顺序存放虚函数的每个地址,该数组最后一个元素的值为0。

对于多继承的派生类对象,其不但继承了基类对象,同时也继承了基类的虚函数表指针。

派生类继承多个基类,派生类对象模型其实就相当于接力的对象模型继承下来了,只不过派生类独有的虚函数指针将会按照顺序放在第一个虚表的最后一个位置。最后再添加派生类自己的成员。

如果在继承的时候子类父类都有虚函数,那么会重新建立一个虚表,不包含父类表的内容,而在普通的继承中却是在父类表的基础上建立一个虚表。如果虚继承中子类父类都有各自的虚函数,在子类里面就会有两个虚函数表指针,一个指向父类的虚表,一个指向子类的虚表,而普通的继承只有一个指向子类虚表的指针。

 

vector 容器是在堆上分配空间,是动态空间,有保留内存,如果不够就再申请内存。

list每次操作都会配置空间,但是list是一个环状双向链表,因此效率低。

常见的网络编程模型:

1:同步阻塞迭代模型:

bind->listen ->accpet->read ->write;

2:多进程并发模型:

bind->listen->accept->fork()->switch(0:子进程操作1:父进程close)

3:多线程并发模型:

bind->listen->accept->pthread_create:特点:稳定性较差,可能会出现因为临界资源的访问问题造成的程序系统性能过低。

4:IO多路复用-slelct/poll

bind->listen->select

可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程),这样可以大大节省系统资源。

posted on 2020-03-01 10:48  KID_XiaoYuan  阅读(161)  评论(0编辑  收藏  举报