c++面试题 网络编程篇

守护进程编程规范

  • 1.首先要做的是调用umask,重设文件权限掩码。
    文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。
    由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。
    因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。
    设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。



  • 2.调用fork,然后将父进程退出(exit)
    这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。
    之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
    在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程是,就会自动由1号进程(init)收养它。这样,原先的子进程就会变成init进程的子进程。


  • 3.调用setsid以创建一个新会话
    由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。由于在调用了fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等。
    虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。

  • 4.当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配文件系统中。因为守护进程可能会在系统再引导之前就一直存在,所以如果守护进程的当前工作目录。在一个装配文件系统中,那么该文件系统就不可拆卸。这与装配文件系统的原意不符。

  • 5.闭不再需要的文件描述符。用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。

  • 6.些守护进程打开/dev/null使其具有文件描述符0、1和2
    因为守护进程并不与终端设备相关联,所以不能再终端设备上显示其输出,也无处从交互式用户那里接收输入。

主机字节序

不同的CPU有不同的字节序类型,这些字节序是指 整数 在内存中保存的顺序,这个叫做 主机序。
最常见的有两种:

  • 1.Little endian:将低序字节存储在起始地址
  • 2.Big endian:将高序字节存储在起始地址

LE little-endian(小端)

  • 最符合人的思维的字节序;
  • 地址低位存储值的低位;
  • 地址高位存储值的高位;
  • 怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说;
  • 低位值小,就应该放在内存地址小的地方,也即内存地址低位;
  • 反之,高位值就应该放在内存地址大的地方,也即内存地址高位;

BE big-endian(大端)

  • 最直观的字节序;
  • 地址低位存储值的高位;
  • 地址高位存储值的低位;
  • 为什么说直观,不要考虑对应关系;
  • 只需要把内存地址从左到右按照由低到高的顺序写出;
  • 把值按照通常的高位到低位的顺序写出;
  • 两者对照,一个字节一个字节的填充进去;

例子:在内存中双字 0x01020304(DWORD) 的存储方式

内存地址

4000 4001 4002 4003 
LE 04 03 02 01 
BE 01 02 03 04 

例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为

      big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12

x86系列CPU都是little-endian的字节序。

网络字节序

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节序采用big endian排序方式。

网络字节序和主机字节序的转换

htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序


网络与主机字节转换函数:htons()、ntohs()、htonl()、ntohl() (注意:s 就是short l是long h是host n是network)

Linux 进程间通讯的各种方式的比较

各种通信方式的比较和优缺点

  • 管道:速度慢,容量有限,只有父子进程能通讯
  • FIFO:任何进程间都能通讯,但速度慢
  • 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
  • 信号量:不能传递复杂消息,只能用来同步
  • 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

fork 函数的返回值

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

返回值有三种:

  • 负数:如果出错,则fork()返回-1,此时没有创建新的进程。最初的进程仍然运行。
  • :在子进程中,fork()返回0
  • 正数:在负进程中,fork()返回正的子进程的PID

wait 和waitpid

#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

waitpid则可以通过设置一个选项来设置为非阻塞,另外waitpid并不是等待第一个结束的进程而是等待参数中pid指定的进程。

waitpid的option常量:
WNOHANGwaitpid将不阻塞如果指定的pid并未结束
WUNTRACED如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

waitpid中pid的含义依据其具体值而变:

  • pid==-1 等待任何一个子进程,此时waitpid的作用与wait相同
  • pid >0 等待进程ID与pid值相同的子进程
  • pid==0 等待与调用者进程组ID相同的任意子进程
  • pid<-1 等待进程组ID与pid绝对值相等的任意子进程

waitpid提供了wait所没有的三个特性:

  • 1 waitpid使我们可以等待指定的进程
  • 2 waitpid提供了一个无阻塞的wait
  • 3 waitpid支持工作控制

XSI信号量函数API

信号量(信号灯)本质上是一个计数器,用于协调多个进程(包括但不限于父子进程)对共享数据对象的读/写。它不以传送数据为目的,主要是用来保护共享资源(信号量、消息队列、socket连接等),保证共享资源在一个时刻只有一个进程独享。

信号量是一个特殊的变量,只允许进程对它进行等待信号和发送信号操作。最简单的信号量是取值0和1的二元信号量,这是信号量最常见的形式。

通用信号量(可以取多个正整数值)和信号量集方面的知识比较复杂,应用场景也比较少。

下面侧重介绍二元信号量。

相关函数
Linux中提供了一组函数用于操作信号量,程序中需要包含以下头文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

1、semget函数
semget函数用来获取或创建信号量,它的原型如下:

int semget(key_t key, int nsems, int semflg);
  • 1)参数key是信号量的键值,typedef unsigned int key_t,是信号量在系统中的编号,不同信号量的编号不能相同,这一点由程序员保证。key用十六进制表示比较好。

  • 2)参数nsems是创建信号量集中信号量的个数,该参数只在创建信号量集时有效,这里固定填1。

  • 3)参数sem_flags是一组标志,如果希望信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。如果没有设置IPC_CREAT标志并且信号量不存在,就会返错误(errno的值为2,No such file or directory)。

  • 4)如果semget函数成功,返回信号量集的标识;失败返回-1,错误原因存于error中。

PS:
可以使用ftok函数将文件名转变成一个key_t

 key_t ftok(const char *pathname, int proj_id);

其中参数fname是指定的文件名,这个文件必须是存在的而且可以访问的。id是子序号,它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返回-1

示例

1)获取键值为0x5000的信号量,如果该信号量不存在,就创建它,代码如下:

int semid=semget(0x5000,1,0640|IPC_CREAT);

2)获取键值为0x5000的信号量,如果该信号量不存在,返回-1,errno的值被设置为2,代码如下:

int semid= semget(0x5000,1,0640);

2、semctl函数
该函数用来控制信号量(常用于设置信号量的初始值和销毁信号量),它的原型如下:

int semctl(int semid, int sem_num, int command, ...);
  • 1)参数semid是由semget函数返回的信号量标识。
  • 2)参数sem_num是信号量集数组上的下标,表示某一个信号量,填0。
  • 3)参数cmd是对信号量操作的命令种类,常用的有以下两个:

IPC_RMID:销毁信号量,不需要第四个参数;
SETVAL:初始化信号量的值(信号量成功创建后,需要设置初始值),这个值由第四个参数决定。第四参数是一个自定义的共同体,如下:

// 用于信号灯操作的共同体。
  union semun
  {
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
  };
  • 4)如果semctl函数调用失败返回-1;如果成功,返回值比较复杂,暂时不关心它。

示例:

1)销毁信号量。

semctl(semid,0,IPC_RMID);

2)初始化信号量的值为1,信号量可用。

union semun sem_union;
  sem_union.val = 1;
  semctl(semid,0,SETVAL,sem_union);

3、semop函数
该函数有两个功能:1)等待信号量的值变为1,如果等待成功,立即把信号量的值置为0,这个过程也称之为等待锁;2)把信号量的值置为1,这个过程也称之为释放锁。

int semop(int semid, struct sembuf *sops, unsigned nsops);

1)参数semid是由semget函数返回的信号量标识。
2)参数nsops是操作信号量的个数,即sops结构变量的个数,设置它的为1(只对一个信号量的操作)。
3)参数sops是一个结构体,如下:

struct sembuf
{
  short sem_num;   // 信号量集的个数,单个信号量设置为0。
  short sem_op;    // 信号量在本次操作中需要改变的数据:-1-等待操作;1-发送操作。
  short sem_flg;   // 把此标志设置为SEM_UNDO,操作系统将跟踪这个信号量。
                   // 如果当前进程退出时没有释放信号量,操作系统将释放信号量,避免资源被死锁。
};

示例:

1)等待信号量的值变为1,如果等待成功,立即把信号量的值置为0;

struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = -1;
  sem_b.sem_flg = SEM_UNDO;
  semop(sem_id, &sem_b, 1);

2)把信号量的值置为1。

struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = 1;
  sem_b.sem_flg = SEM_UNDO;
  semop(sem_id, &sem_b, 1);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章