1、API是一些函数,这些函数是由linux系统提供支持的,由应用层程序来使用。
2、基于linux去做应用编程,其实就是通过调用linux系统API来实现应用需要完成的任务。
3、我们要使用linux系统来读写文件,手段就是学习linux系统的API中和文件IO有关的几个(open、close、write、read、lseek)。
4、打开文件时,文件的读写权限:
O_RDONLY以只读方式打开
O_WRONLY以只写方式打开
O_RDWR以可读可写的方式打开
打开存在并且由内容的文件时:
O_APPEND属性打开文件时,新内容会接续到原来内容后面
O_TRUNC属性打开文件时,新内容会替换原来的内容(原来的内容丢失)
O_CREAT | O_EXCL搭配使用可以当我们创建一个文件时,如果有这个文件则报错。
open()函数的最后一个参数mode(一般前两位没用,666user,group,others可读可写不可执行):
00700 user 可读可写可执行
00400 user 可读
00200 user 可写
00100 user 可执行
00070 group 可读可写可执行
00040 group 可读
00020 group 可写
00010 group 可执行
00007 others 可读可写可执行
00004 others 可读
00002 others 可写
00001 others 可执行
注:可读为4,可写为2,可执行为1
5、我们打开一个文件默认就是阻塞式(O_SYNC)的,如果想要非阻塞式的就用(O_NONBLOCK)。
阻塞式的对结果有保障但时间没保障,非阻塞式的与之相反。
6、linux系统中提供一个perror函数,将错误信息转换为字符串利用print打印出来。
7、inode(i节点)是一个结构体,里面是记录了一个文件的信息列表。硬盘管理的时候是以文件为单位,每个文件
都会有一个inode,每个inode有一个数字编号,对应一个结构体,里面记录了关于文件的各种信息。
8、vnode的作用同inode,不一样的是inode用来管理在硬盘中的文件(静态文件),vnode用来管理在内存中的文件(
动态文件),用文件描述符就可以找到对应的vnode。
9、默认情况下两次打开同一个文件,读取的时候为分别读(得到两个文件描述符),写的时候为分别写(也就是后
一个写入文件的内容,会覆盖前一个写入的内容)。如果在open是加O_APPEND标志,就可以变为接续写。
10、文件共享就是同一个文件同一个inode,pathname)被多个独立的读写体(可以理解为多个文件描述符)去同时
操作(一个尚未关闭就去操作另一个)。
实现文件共享的核心就是怎样弄出来对个文件描述符指向同一个文件,由一下三种情况:
1)在同一个进程中多次使用open打开同一个文件
2)在不同的进程中使用open打开同一个文件(此时文件描述符的数字可以相同也可以不相同)
3)利用dup和dup2两个API复制文件描述符。
11、文件描述符表是一个数组,索引为文件描述符fd,值为文件表指针。然后可以通过文件表指针间接的得到
含有文件各种信息的文件表。
12、利用dup()函数进行文件描述符的复制,会返回一个新的文件描述符(例如,原来是3,返回的就是4(但也不一定
,这个值是由系统分配得到的))。用dup返回的文件描述符和原来的文件描述符都是指向原来文件描述符指向的文件,并且
同时向文件里面写内容时,为接续写。
举例:fd2=dup(fd1)
13、dup2()函数与dup()的区别就是,dup2()可以指定新文件描述符的数字
举例:fd2=dup(fd1,5)则fd2的文件描述符为5,也是指向fd1指向的文件。
14、可以利用>符号将ls,pwd等命令的输出结果重定位到一个指定的文件中,其内部的实现原理就是open+close+dup2
举例:ls > 1.txt就是把ls后打印的结果输出到1.txt。
15、fcntl(fd,cmd,…)可以改变已打开的文件性质,fcntl针对描述符提供控制。参数fd是被参数cmd操作的描述符。
举例;
fcntl(fd,F_DUPF,arg)这个命令的功能是从可用的fd列表中找到一个>=arg的最小的数字作为一个新的文件描述符。
16、C库函数是对API的进一步封装,所以更好用一些。
17、目录文件(d director)、字符设备文件(c character)、块设备文件(b block)
管道文件(p pipe)、套接字文件(s socket)、符号链接文件(l link)
18、获取文件属性的API:
stat、fstat、lstat三者都是查看文件的属性信息,并且stst可以在linux命令行下面加文件名直接使用。
三者原型:
int stat(const char *path, struct stat *buf); 传入的参数是文件名和结构体buf的指针
int fstat(int fd, struct stat *buf);传入的参数是文件描述符和结构体buf的指针
int lstat(const char *path, struct stat *buf);传入的参数与stst一样
struct stat结构体是内核定义的一个结构体,结构体中所有的元素加起来就是我们的文件属性信息。
stat适用于文件未打开时,fstst适用于已经打开的文件,lstst查阅的是符号链接文件本身的属性信息。
19、可以用chown去修改一个文件的属主,可以用chgrp去修改文件所在的组。
20、可以用access函数测试当前执行程序的用户在当前环境下对目标文件是否具有某种操作权限。
使用方法就是access(文件名,模式)
模式:
F_OK是否具有打开权限
R_OK是否具有可读权限
W_OK是否具有可写权限
X_OK是否具有可执行权限
21、时间相关的API
time的得到由现在距离标准时间起点的所过的所有秒数
ctime可以由time_t出发的秒数得到一个容易观察的字符串格式的时间
gmtime得到国家时间
localtime得到本地时间
asctime效果和ctime一样,只不过asctime是从struct tm出发
strftime可以的到自定义格式的字符串时间
22、我们可以通过/proc/xxx/文件来实时的观察内核中特定数据结构的值。
/sys下面的文件作用和/proc一样,所不同的是/proc中的文件是只读的,/sys下面的文件可以读写
两者都是用来调试内核,文件大小都是0,都不是硬盘文件而是内核中的数据结构的可视化接口。
23、利用atexit()函数来注册进程终止时候的处理函数。(也就是在进程终止的时候会执行什么函数)
atexit()接收一个参数,该参数是一个函数名(就是进程终止要执行的那个函数)。
atexit()函数先注册的会后执行。
return和exit退出的程序,都会执行进程终止处理函数;_exit终止的进程不会执行axite注册的进程终止处理函数。
24、二重指针等于指针数组等于字符串数组。
25、利用getenv获取指定的环境变量的值:
用法是getenv(“要获取的环境变量的名字”);返回值是字符串形式的环境变量的值。
26、getpid()获取当前进程的id号,getppid()获取当前进程父进程的id号;
getuid()获取实际用户id,geteuid()获取有效用户id;一般情况下二者相等;
getgid()获取实际用户组id,getegid()获取有效用户组id;一般情况下二者相等。
27、fork()创建子进程的时候可以不需要传任何参数,调用一次会返回两次;
返回值等于0的是子进程,返回值大于0(这个大于0的值就是子进程的id)的是父进程。
28、要是先打开一个文件得到文件描述符,然后再fork创建子进程,之后在父子进程中各自向文件中写入内容,
则表现出为接续写;这表明父进程在没有fork之前自己做的事情会对子进程有很大的影响。
要是先fork创建子进程,然后各自在父子进程中打开同一个文件得到文件描述符,之后在父子进程中各自向文件中写入内容,
则表现为分别写;因为这时候父子进程已经分离,父子进程各自有文件指针,因此两次读写是完全独立的。
29、父进程可以显示的调用wait或waitpid函数来回收子进程,但是就算父进程在运行过程中没有显式的
调用wait或waitpid函数来回收子进程,在父进程结束的时候一样会回收子进程(相当于自动调用)。
30、wait和waitpid函数的区别就是waitpid函数可以回收指定进程号的子进程。
31、exec族函数的作用就是让子进程开启属于自己的程序;exec族函数可以直接把一个编译好的可执行程序直接
在子进程中加载运行。
32、进程的5种状态:
就绪态、运行态、僵尸态、等待态、暂停态。
33、system函数=fork + exec;属于原子操作可以有效的避免竞争状态;
使用:int system(“commend”)
返回值:成功返回参数的命令结果,失败返回-1
34、守护进程时与控制台脱离(也就是说关闭终端,守护进程不会被关闭,除非关机)的长期运行的进程,像
一般服务器程序都是守护进程。
35、任何一个进程都可以变为一个守护进程,只要满足下面6点:
1)利用fork创建子进程(或子进程已经存在),并将父进程退出
2)子进程使用setsid函数创建新的会话期,目的是为了脱离控制台(摆脱原会话、原进程组、原控制终端的控制)
3)调用chdir函数将当前的工作目录设置为/
4)利用umask(0),取消任何文件的权限屏蔽
5)关闭所有文件描述符
6)将0、1、2(文件描述符)定位到/dev/null
程序描述:
第一步
pid_t pid = 0;
pid = fork();
if (pid < 0)
{
perror(“fork”);
exit(-1);
}
if (pid > 0)
{
exit(0); // 父进程直接退出
}
// 执行到这里就是子进程
第二步
// setsid将当前进程设置为一个新的会话期session,目的就是让当前进程
// 脱离控制台。
pid = setsid();
if (pid < 0)
{
perror(“setsid”);
exit(-1);
}
第三步
// 将当前进程工作目录设置为根目录
chdir("/");
第四步
// umask设置为0确保将来进程有最大的文件操作权限
umask(0);
第五步
// 关闭所有文件描述符,如不关闭会浪费资源
// 先要获取当前系统中所允许打开的最大文件描述符数目
int cnt = sysconf(_SC_OPEN_MAX);
int i = 0;
for (i=0; i<cnt; i++)
{
close(i);
}
第六步
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
36、我们可以通过openlog、syslog、closelog三个函数来进行日志文件(其实里面记录了通过syslog函数写入的各种
调试信息,syslog写入的信息都在/var/log/syslog文件中存储)的打开、写入、关闭。
举例说明:
openlog(“my_demon”, LOG_CONS, LOG_DAEMON);//打开(或创建)一个日志文件my_demon
syslog(LOG_NOTICE, “this demon is runing\n”);//向日志文件中写入信息
closelog();//关闭
37、signal与sigaction函数是信号处理函数。
signal函数绑定一个捕获函数后,信号发生后会自动执行绑定的捕获函数。
SIGINT 信号是Ctrl+C
SIGPOLL / SIGIO 指示一个异步IO事件
SIGKILL 杀死进程的办法
SIGPIPE 涉及管道和socket
SIGCHLD 子进程终止或停止OS向其父进程发次信号
sa_handler为sigaction绑定的处理函数的函数指针。
38、所谓的信号处理说白了就是当信号发生之后,系统怎样去处理信号或者应该执行什么相应的函数。
39、pause函数的作用是让当前进程暂停运行,交出CPU给其它进程去执行。当当前进程进入pause状态后当前
进程会表现为阻塞住,要退出pause状态,就需要当前进程被信号唤醒。
40、鼠标的操作文件是/dev/input/mouse1,键盘(标准输入)是默认被打开的,文件描述符是0
41、为文件添加非阻塞属性:
1)当可以利用open打开文件时,那就直接在打开文件的时候添加非阻塞属性
fd = open("/dev/input/mouse1", O_RDONLY | O_NONBLOCK)
2)如果文件本来默认就是打开的(标准输入、标准输出、标准错误),不能用open打开文件时添加非阻塞属性步骤如下:
int flag = -1;
flag = fcntl(0, F_GETFL); //先获取原来的flag
flag |= O_NONBLOCK; //添加非阻塞属性
fcntl(0, F_SETFL, flag); //更新flag
这三步之后,文件描述符0(键盘)文件就变成非阻塞式的了。
42、文件默认是阻塞式的,阻塞式的适用于单路IO,非阻塞式的适用于多路IO
43、多路复用IO时,用到poll和select两个关键函数。nfds是大的文件描述符加1。FD_ISSET函数用来监测哪路IO到了。
44、异步IO其实就是操作系统用程序实现的一套中断响应;
当我们当前进程注册一个异步IO事件(使用signal函数注册一个信号SIGIO的处理函数),然后当前进程可以正常处理
自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号,从而执行绑定的处理函数去处理这个异步事件。
涉及的函数:
F_GETFL、F_SETFL获取与更改fd
O_ASYNC 设置可接受异步通知
F_SETOWN 设置通知谁
举例:将鼠标设置为一个异步事件
int flag = -1;
fd = open("/dev/input/mouse1", O_RDONLY);
//三步将鼠标的读取设置为可以接受异步IO
flag = fcntl(fd, F_GETFL);
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
//把异步事件IO的接收进程设置为当前进程
fcntl(fd, F_SETOWN, getpid());
//注册当前进程的SIGIO信号的捕获函数(就是当有异步事件发生(有SIGIO信号产生),会去执行func函数里面的内容)
signal(SIGIO, func);
4
5、pthread_create()函数可以用来创建一个进程。进程时参与内核调度的最小单元,一个进程可以有多个线程。
同一个进程的多个线程之间很容易高效率通信。
常见的关于线程的函数:
pthread_cretate():主线程(本来的进程中的线程)用来创造子线程的;成功返回0.
pthread_join():主线程用来等待(阻塞方式)回收子线程
pthread_detach():主线程用来分离子线程,分离后主线程不必再回收子线程
pthread_cancel():主线程用来取消子线程
pthread_setcancelstate():子线程用来设置自己是否允许被取消
pthread_setcancelytpe():取消模式
pthread_exit():子线程返回
pthread_cleanup_push():与栈有关
pthread_cleanup_pop():与栈有关
46、下面的信号量是Posix信号量,常用于线程
信号量的函数都是以sem_开头的,线程中使用的基本信号量函数有4个:
sem_init():用于创建信号量。
sem指向一个sem_t类型定义的对象;pshared控制信号量的类型,如果值为0就表示这个信号量是当前进程的局部信号量,
否则信号量就可以在多个进程之间共享;value为sem的初始值。调用成功返回0,失败返回-1.
sem_wait():以原子操作的方式将信号量的值减1。成功返回0,失败返回-1
sem_post():以原子操作的方式将信号量的值加1。成功返回0,失败返回-1
sem_destroy():用于对用完信号量的清理。成功返回0,失败返回-1
信号量中当信号量的值为-1的时候线程就会阻塞,为非-1的时候就会执行。只要一创建完线程之后就会去执行绑定的函数。
一般:执行sem_wait()线程就会被阻塞住,当执行sem_poat()线程就会被继续执行。
下面的信号量是system V信号量,常用于进程的同步
semget():获得一组信号量(得到一个信号量标识符)
semop():信号量操作
semctl():信号量控制操作(可以设置为销毁信号量)
47、互斥锁是一种特殊的信号量,只有0和1,主要用来实现保护关键段。
互斥锁的常用函数:
pthread_mutex_init():初始化互斥锁
pthread_mutex_destroy():销毁互斥锁
pthread_mutex_lock():锁定互斥锁
pthread_mutex_unlock():解锁锁定互斥锁
在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法在获得锁。
48、互斥锁和条件变量通常配合使用,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。
而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。
一般的互斥锁:若A线程已经加锁,B线程再加锁的时候会被阻塞,直到A释放锁,B再获得锁运行
而条件变量同样是阻塞,还需要通知才能唤醒,线程被唤醒后,他将重新检查判断条件是否满足,如果还不满足
该线程就休眠了,应该仍阻塞在这里,等待条件满足后被唤醒,节省了线程不断运行浪费的资源。
49、条件变量相关函数
pthread_cond_init():初始化条件变量
pthread_cond_wait():基于条件变量阻塞
pthread_cond_signal():解除阻塞特定线程
pthread_cond_broadcast():解除阻塞所有线程
pthread_cond_destroy():销毁条件变量状态
也就是在pthread_cond_wait()处线程被阻塞,在pthread_cond_signal()或pthread_cond_broadcast()处被唤醒,
继续从pthread_cond_wait()处继续往下执行。
50、网络通信其实就是位于网络中不同主机上面的2个进程之间的通信。
51、socket接口就是操作系统提供的API。
52、网络编程的时候我们重点关注应用层(也就是socket接口) 。
53、服务器端socket、bind、listen后处于监听状态;
客户端socket后,直接connect去发起连接;
服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时时双向的,而且双方均可发起和关闭连接。
54、网络编程常用函数
socket()函数是一个API,类似于open,用来打开一个网络连接,如果成功返回一个网络文件描述符,
之后我们操作这个网络连接就是通过这个网络描述符进程操作的。
bind()绑定函数
listen()监听函数
connect()连接函数
55、struct sockaddr这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等
函数中都需要这个结构体,这个结构体兼容IPV4和IPV6,在实际编程中这个结构体会被一个struct sockaddr_in
或者一个struct sockaddr_in6所填充。
56、socket()函数中的参数:
第一个参数
AF_INET:IPV4网络
AF_INET6:IPV6网络
第二个参数
SOCK_STREAM:提供面向连接的稳定数据传输,即TCP协议。
SOCK_DGRAM:使用不连续可靠的数据包连接
SOCK_SEQPACKET:提供连续可靠的数据包连接
SOCK_RAW:提供原始网络协议存取
SOCK_RDM:提供可靠的数据包连接
SOCK_PACKET:与网络驱动程序直接通信
57、struct sockaddr_in结构体
struct in_addr
{
in_addr_t s_addr;
}
struct sockaddr_in
{
__SOCKADDR_COMMON(sin_family);//确定是IPV4还是IPV6
in_port_t sin_port;//设置地址的端口号信息
struct in_addr sin_addr;//设置IP地址
unsigned char sin_zeor[sizeof(struct sockaddr)-
__SOCKADDR_COMMON_SIZE-
sizeof(in_port_t)-
sizeof(struct in_addr)];
}
58、IP地址转换函数
inet_pton:将IP地址从点分十进制转换为二进制
inet_ntop:将IP地址从二进制转换为点分十进制
inet_addr:将一个点分十进制的IP地址转换为一个长整型数
59、监听函数listen()中backlog参数:
服务器在监听时,在每次处理一个客户端的连接时是需要一定时间的,这个时间非常的短,但是这个时间还是存在的。
而这个backlog存在的意义就是:在这段时间里面除了第一个连接请求是正在处理以外,其它的连接请求都在请求队列
中等待,而如果超过了队列的最大等待个数时,其它的请求将被忽略或者不处理,这个backlog的值就是影响这个队列的大小。
60、accept函数用来服务端接受客户端的请求,第一个参数是服务器端的网络描述符,包含的是服务器的ip地址和port信息。
第二个参数用于存放客户端的地址,第三个参数在调用函数时被设置为指向的客户端区域的长度,在函数调用结束后
被设置为实际地址信息的长度。本函数会阻塞等待直到有客户端请求到达。
正确返回值是一个新的网络文件描述符(套接字描述符),表示服务端和客户端建立了连接(TCP连接),
以后我们就通过这个被返回的文件描述符来进行服务端和客户端的读写操作。
61、socket返回的fd叫做监听fd,用来监听客户端的,不能用来和任何客户端进行读写
accept返回的fd叫做连接fd,用来和连接的客户端进行读写的。
62、send发送内容,recv接收内容
63、在进行服务器端与客户端之间的读写时的文件描述符使用的重点:
1)当服务器端写(发),客户端读(接收)的时候;服务器端send函数中使用的文件描述符是accept函数所返回的
文件描述符,客户端recv函数中使用的文件描述符是socket函数所返回的文件描述符。
2)当服务器端读(接收),客户端写(发)的时候;服务器端recv函数中使用的文件描述符是accept函数所返回的
文件描述符,客户端send函数中使用的文件描述符是socket函数所返回的文件描述符。
总结:
在服务器端和客户端进行通信读写的时候,无论谁发谁接,服务器端发送和接收函数用的文件描述符都是accept函数
返回的(一般是clifd),客户端发送和接收函数用的文件描述符都是socket函数返回的(一般是sockfd)。
64、在scanf中将输入的数据存放在地址中,所以scanf()里的后半段里应该写地址,因而要在stu.num前加&。
65、一般网络通信的干活方式是,客户端先发–》服务器端接收,干活–》服务器端再发–》客户端进行结果判断