UC初级
错误处理
-
全局变量errno记录错误类型编号,默认值为0,需包含头文件errno.h
-
char* strerror(int errnum) 需要string.h头文件
- 功能:将整数形式的错误号转换为有意义的字符串
- 参数: errnum 错误号
- 返回值:返回与参数错误号对应的描述字符串
-
void perror(char const* tag)
- 功能:在标准出错设备上打印一次函数调用的错误信息
- 参数:tag 为用户自己制定的内容,输出时会自动在该提示内容和错误信息之间添加冒号进行分隔
静态库
-
命名规范:库名以lib开头,.a结尾
-
ar [选项] <静态库文件> <目标文件列表>
-
使用其他目录的库 gcc main.c -lmath -L…
-l选项后直接跟库名(省略了lib前缀和.a后缀 libmath.a) -L后跟路径
-
优点:执行速度快;可执行程序不依赖库的存在
-
缺点:文件体积较大;更新困难,维护成本搞
-
接口文件
#ifndef __MATH_H__ #define __MATH_H__ #include"calc.h" #include"show.h" #endif
动态库
又叫共享库
-
库名以lib开头,以.so结尾
-
打包成动态库
- gcc -c -fpic calc.c
- gcc -c -fpic show.c
- gcc -shared calc.o show.o -o libmath.so
-
运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径用以告知链接器在运行时链接动态库 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
-
优点:可执行文件体积小;易于链接,便于更新维护
-
缺点:文件执行速度相对较慢;可执行程序依赖库文件的存在
动态加载
-
动态加载:在程序执行的过程中,开发人员可以动态加载共享库,用一组特殊的函数实现,使用这些函数需要包含头文件#include<dlfcn.h>,编译时要关联对应库-ldl 如:gcc load.c -ldl -o load
-
void* dlopen(char* filename,int flag)
- 功能:将共享库载入内存并获得其访问句柄
- 参数:动态库路径,若只给文件名不带目录,则根据LD_LIBRARY_PATH查找;加载方法,可以取以下值:RTLD_LAZY-延迟加载,使用动态库中的符号时才真的加载进内存。RTLD_NOW-立即加载
- 返回值:成功返回动态库的访问句柄,失败返回NULL
- 句柄:唯一地标识了系统内核所维护的共享库对象,将作为后续函数调用的参数
-
void* dlsym(void* handle,char const* symbol)
- 功能:从已被加载的动态库中获取特定名称的符号地址
- 参数:动态库访问句柄;符号名
- 返回值:成功返回给定符号的地址,失败返回NULL
-
int dlclose(void* handle)
- 功能:从内存中卸载动态库
- 参数:动态库句柄
- 返回值:成功返回0,失败返回非0
- 所卸载的共享库不一定真的从内存中立即消失,因为其他程序可能还要使用该库
- 只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真的释放
- 无论所卸载的共享库是否真的被释放,传递给该函数的句柄都会在函数成功返回后立即失效
-
char* dlerror(void)
- 功能:获取在加载、使用和卸载共享库过程中发生的错误
- 返回值:有错误则返回对应信息的字符串指针,否则返回NULL
-
示例
//动态库的动态加载 #include<stdio.h> #include<dlfcn.h> int main(void){ //将动态库载入内存 void* handle = dlopen("./shared/libmath.so",RTLD_NOW); if(handle == NULL){ fprintf(stderr,"dlopen:%s\n",dlerror()); return -1; } //获取库中函数地址 int (*add)(int,int) = dlsym(handle,"add"); if(add == NULL){ fprintf(stderr,"dlsym:%s\n",dlerror()); return -1; } int (*sub)(int,int) = dlsym(handle,"sub"); if(sub == NULL){ fprintf(stderr,"dlsym:%s\n",dlerror()); return -1; } void (*show)(int,char,int,int) = dlsym(handle,"show"); if(show == NULL){ fprintf(stderr,"dlsym:%s\n",dlerror()); return -1; } //使用函数 int a = 10,b = 7; show(a,'+',b,add(a,b)); show(a,'-',b,sub(a,b)); //从内存中卸载库 dlclose(handle); return 0; } -
辅助工具
-
查看符号表:nm
列出目标文件、可执行程序静态库或共享库中的符号。如:nm libmath.a
-
查看依赖:ldd
查看可执行文件或者共享库所依赖的共享库。如:ldd a.out
-
内存
-
#include<sys/mman.h>
-
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset)
-
功能:建立虚拟内存到物理内存磁盘文件的映射
-
参数:start:映射区虚拟内存的起始地址,NULL系统自动选定后返回;length:映射区字节数,自动按页圆整;prot:映射区操作权限,可取下值(若要多选选项,可对选项做按位或运算传入):
PROT_READ-映射区可读;PROT_WRITE-映射区可写;PROT_EXEC-映射区可执行;PROT_NONE-映射区不可访问;
flags:映射标志,可取下值:MAP_ANONYMOUS-匿名映射,将虚拟内存映射到物理内存而非文件,忽略fd和offset参数;MAP_PRIVATE-对映射区的写操作只反映到缓冲区中,并不会真正写入文件;MAP_SHARED-对映射区的写操作直接反映到文件中;
fd:文件描述符;offset:文件偏移量,自动按页(4K)对齐
-
返回值:成功返回映射区虚拟内存的起始地址,失败返回MAP_FAILED(-1)
-
-
int munmap(void* start,size_t length)
- 功能:解除虚拟内存到物理内存或磁盘文件的映射
- 参数:映射区虚拟内存的起始地址;映射区字节数,自动按页圆整
- 返回值:成功0,失败-1
- munmap允许对映射区的一部分解映射,但必须按页处理
-
内存映射的建立和解除示例
//内存映射 #include<stdio.h> #include<string.h> #include<sys/mman.h> // mmap() munmap() int main(void){ //建立映射关系 char* start = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_PRIVATE,0,0); if(start == MAP_FAILED){ perror("mmap"); return -1; } printf("start = %p\n",start); //访问内存 strcpy(start,"铁锅炖大鹅"); printf("%s\n",start); //解除映射关系 if(munmap(start,4096) == -1){ perror("munmap"); return -1; } //printf("%s\n",start);//段错误 return 0; } -
#include<unistd.h>
-
void* sbrk(intptr_t increment)
- 功能:以相对方式分配和释放虚拟内存
- 参数:堆内存的字节增量(以字节为单位)>0-分配内存;<0-释放内存;=0-当前堆尾
- 返回值:成功返回调用该函数前的堆尾指针,失败返回-1
- 堆尾指针指向最后一个被使用的字节的后一个地址,若发现内存页耗尽或空闲则自动追加或取消内存页映射
-
虚拟内存的分配和释放示例
//内存分配和释放 #include<stdio.h> #include<unistd.h> int main(void){ printf("当前堆尾:%p\n",sbrk(0)); int* pi = sbrk(4); printf("pi = %p\n",pi); *pi = 100; double* pd = sbrk(8); printf("pd = %p\n",pd); *pd = 3.14; printf("%d %lg\n",*pi,*pd); sbrk(-(4 + 8)); printf("当前堆尾:%p\n",sbrk(0)); return 0; } -
int brk(void* end_data_segment)
- 功能:以绝对方式分配和释放虚拟内存
- 参数:堆尾指针的目标位置 >队尾指针的原位置-分配内存;<队尾指针的原位置-释放内存;=队尾指针的原位置-空操作
- 返回值:成功0,失败-1
-
示例
//内存分配和释放 #include<stdio.h> #include<unistd.h> int main(void){ printf("当前堆尾:%p\n",sbrk(0)); int* pi = sbrk(0); printf("pi = %p\n",pi); brk(pi+1); *pi = 100; double* pd = sbrk(0); printf("pd = %p\n",pd); brk(pd+1); *pd = 3.14; brk(pi); printf("当前堆尾:%p\n",sbrk(0)); return 0; }
文件
ls -i 查看i节点号
文件打开与关闭
-
#include<fcntl.h>
-
int open(char const* pathname,int flags,mode_t mode)
-
功能:打开已有的文件或创建新文件
-
参数:文件路径;flags:状态标志,可取下值:
O_RDNOLY-只读;O_WRONLY-只写;O_RDWR-读写;O_APPEND-追加;O_CREAT-不存在即创建,已存在即打开;O_EXCL-不存在即创建,已存在即报错;O_TRUNC-不存在即创建,已存在即清空;
mode 权限模式:仅在创建新文件时有效,可用形如0XXX的三位八进制数表示,由高位到低位依次表示拥有者用户、同组用户和其它用户的读、写和执行权限,读权限用4表示,写权限用2表示,执行权限用1表示,最后通过加法将不同的权限组合在一起
-
返回值:所返回的一定是当前未被使用的,最小文件描述符
-
-
调用进程的权限掩码会屏蔽掉创建文件时指定的权限位,如创建文件时指定权限0666,进程权限掩码0022,所创建文件的实际权限为: 0666&~0022=0644 (r w-r–r–)
-
#include<unistd.h>
-
int close(int fd)
- 功能:关闭处于打开状态的文件描述符
- 参数:处于打开状态的文件描述符
-
示例
//文件的打开和关闭 #include<stdio.h> #include<unistd.h>// close() #include<fcntl.h> // open() int main(void){ //打开文件 int fd = open("./open.txt",O_RDWR | O_CREAT | O_TRUNC,0777); if(fd == -1){ perror("open"); return -1; } printf("fd = %d\n",fd); //关闭文件 close(fd); return 0; }
文件描述符
系统内核默认为每个进程打开三个文件描述符,它们在unistd.h中被定义为三个宏
- STDIN_FILENO 0 标准输入
- STDOUT_FILENO 1 标准输出
- STDERR_FILENO 2 标准错误
-
输出重定向示例
//输出重定向 #include<stdio.h> #include<unistd.h> #include<fcntl.h> int main(void){ // printf ---> 1 ---> out.txt close(1);// 1 空闲了 int fd = open("./out.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } printf("fd = %d\n",fd);// return 0; }
文件的读写
-
#include<unistd.h>
-
ssize_t write(int fd,void const* buf,size_t count)
-
功能:向指定文件写入数据
-
参数:文件描述符;内存缓冲区,即要写入的数据;期望写入的字节数
-
返回值:成功返回实际写入字节数,失败返回-1
-
示例
//写入文件 #include<stdio.h> #include<string.h>// strlen() #include<fcntl.h>// open() #include<unistd.h>// write() close() int main(void){ // 打开文件 int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } // 向文件写入数据 char* buf = "hello world!"; ssize_t size = write(fd,buf,strlen(buf)); if(size == -1){ perror("write"); return -1; } printf("实际写入文件%ld个字节\n",size); // 关闭文件 close(fd); return 0; }
-
-
ssize_t read(int fd,void* buf,size_t count)
-
功能:从指定文件读取数据
-
参数:文件描述符;内存缓冲区,存读取到的数据;期望读取的字节数
-
返回值:成功返回实际读取字节数,失败返回-1
-
示例
//读取文件 #include<stdio.h> #include<fcntl.h> #include<unistd.h> int main(void){ //打开文件 int fd = open("./shared.txt",O_RDONLY); if(fd == -1){ perror("open"); return -1; } //读取文件 char buf[32] = {}; ssize_t size = read(fd,buf,sizeof(buf)-1); printf("实际读取%ld个字节\n",size); printf("%s\n",buf); //关闭文件 close(fd); return 0; }
-
顺序与随机读写
-
#include<unistd.h>
-
off_t lseek(int fd,off_t offset,int whence)
-
功能:人为调整文件读写位置
-
参数:文件描述符;文件读写位置偏移字节数;offset参数的偏移七点,可取下值:
SEEK_SET-从文件头(首字节)开始;SEEK_CUR-从当前位置(最后被读写字节的下一个字节)开始;SEEK_END-从文件尾(最后一个字节的下一个字节)开始
-
返回值:成功返回调整后的文件读写位置,失败返回-1
-
示例
//调整文件读写位置 #include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> int main(void){ //打开文件 int fd = open("./lseek.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } //向文件写入数据 hello world! 12 char* buf = "hello world!"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //修改文件读写位置 if(lseek(fd,-6,SEEK_END) == -1){ perror("lseek"); return -1; } //再次写入数据 linux! buf = "linux!"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } // hello linux! // 再次修改文件读写位置 if(lseek(fd,8,SEEK_END) == -1){ perror("lseek"); return -1; } //第三次写入 buf = "<---this is a hole"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //关闭文件 close(fd); return 0; }
-
标准I/O与系统I/O
-
标准I/O
//标准IO #include<stdio.h> int main(void){ FILE* fp = fopen("./std.txt","w"); if(fp == NULL){ perror("fopen"); return -1; } for(int i = 0;i < 1000000;i++){ if(fwrite(&i,sizeof(i),1,fp) == -1){ perror("fwrite"); return -1; } } fclose(fp); return 0; } -
系统I/O
//系统IO #include<stdio.h> #include<fcntl.h> #include<unistd.h> int main(void){ int fd = open("./sys.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } for(int i = 0;i < 1000000;i++){ if(write(fd,&i,sizeof(i)) == -1){ perror("write"); return -1; } } close(fd); return 0; }
文件描述符的复制
-
#include<unistd.h>
-
int dup(int oldfd)
-
功能:复制文件描述符表的特定条目到最小可用项
-
参数:源文件描述符
-
返回值:成功返回目标文件描述符,失败返回-1
-
-
int dup2(int oldfd,int newfd)
- 功能:复制文件描述符表的特定条目到指定项
- 参数:源文件描述符;目标文件描述符
- 返回值:成功返回目标文件描述符(newfd),失败返回-1
-
示例
//文件描述符的复制 #include<stdio.h> #include<string.h> #include<unistd.h> #include<fcntl.h> int main(void){ //打开文件,oldfd int oldfd = open("./dup.txt",O_CREAT | O_TRUNC | O_WRONLY,0664); if(oldfd == -1){ perror("open"); return -1; } printf("oldfd = %d\n",oldfd); //复制文件描述符oldfd ,得到newfd //int newfd = dup(oldfd); int newfd = dup2(oldfd,STDOUT_FILENO); if(newfd == -1){ perror("dup"); return -1; } printf("newfd = %d\n",newfd); //通过oldfd向文件写入数据 hello world! char* buf = "hello world!"; if(write(oldfd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //通过newfd修改文件读写位置 if(lseek(newfd,-6,SEEK_END) == -1){ perror("lseek"); return -1; } //通过oldfd再次写入数据 linux! buf = "linux!"; if(write(oldfd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //关闭文件 close(oldfd); close(newfd); return 0; }
访问测试
-
#include<unistd.h>
-
int access(char const* pathname,int mode)
-
功能:判断当前进程是否可以对某个给定的文件执行某种访问
-
参数:文件路径;被测试权限,可取下值:
R_OK-可读否;W_OK-可写否;X_OK-可执行否;F_OK-存在否
-
返回值:成功返回0,失败返回-1
-
修改文件大小
-
#include<unistd.h>
-
int truncate(char const* path,off_t length)
-
int ftruncate(int fd,off_t length)
- 功能:修改指定文件的大小
- 参数:path:文件路径;length:文件大小;fd:文件描述符
- 返回值:成功返回0,失败返回-1
-
示例
//修改文件大小 #include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> int main(void){ //打开文件 int fd = open("./trunc.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } //向文件写入数据 char* buf = "abced"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //修改文件大小 if(truncate("./trunc.txt",3) == -1){ perror("truncate"); return -1; } //再次 修改文件大小 if(ftruncate(fd,5) == -1){ perror("ftruncate"); return -1; } //关闭文件 close(fd); return 0; }
文件锁
-
#include<fcntl.h>
-
int fcntl(int fd,F_SETLK/FF_SETLKW,struct flock* lock)
-
功能:加解锁
-
参数:F_SETLK 非阻塞模式加锁,F_SETLKW 阻塞模式加锁;lock 对文件要加的锁
-
返回值:成功返回0,失败返回-1
-
struct flock{ short l_type;//锁类型:F_RDLCK/F_WRLCK/F_UNLCK short l_whence;//锁区偏移起点:SEEK_SET/SEEK_CUR/SEEK_END off_t l_start;//锁区偏移字节数 off_t l_len;//锁区字节数 pid_t l_pid;//加锁进程的PID,-1表示自动设置 };
-
-
示例
//通过文件锁解决写冲突 #include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<errno.h> int main(int argc,char* argv[]){ // ./a.out hello //打开文件 int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664); if(fd == -1){ perror("open"); return -1; } //阻塞加锁 struct flock l;//锁信息 l.l_type = F_WRLCK;//写锁 l.l_whence = SEEK_SET; l.l_start = 0; l.l_len = 0;//一直锁到文件尾 l.l_pid = -1; /*if(fcntl(fd,F_SETLKW,&l) == -1){ perror("fcntl"); return -1; }*/ //非阻塞方式加锁 while(fcntl(fd,F_SETLK,&l) == -1){ if(errno == EACCES || errno == EAGAIN){ printf("文件被锁定,不等了,干点别的去\n"); sleep(1); }else{ perror("fcntl"); return -1; } } // argv[1] --> "hello" for(int i = 0;i < strlen(argv[1]);i++){ if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){ perror("write"); return -1; } sleep(1); } //解锁 struct flock ul;//锁信息 ul.l_type = F_UNLCK;//解锁 ul.l_whence = SEEK_SET; ul.l_start = 0; ul.l_len = 0;//一直锁到文件尾 ul.l_pid = -1; if(fcntl(fd,F_SETLKW,&ul) == -1){ perror("fcntl"); return -1; } //关闭文件 close(fd); return 0; }
进程
- pstree 以树状结构显示当前所有进程关系,快照
- ps 以简略方式显示当前用户拥有控制终端的进程信息,可加上以下选项
- a-显示所有用户拥有控制终端的进程信息
- x-也包括没有控制终端的进程
- u-以详尽方式显示
- w-以更大列宽显示
- top 显示实时进程信息
- 相关函数 #include<unistd.h>
- pit_t getpid(void)//返回调用进程的PID
- pit_t getppid(void)//返回调用进程的父进程的PID
- uid_t getuid(void)//返回调用进程的实际用户ID
- gid_t getgid(void)//返回调用进程的实际组ID
- uid_t geteuid(void)//返回调用进程的有效用户ID
- gid_t getegid(void)//返回调用进程的有效组ID
创建子进程
-
#include<unistd.h>
-
pid_t fork(void)
- 功能:创建调用进程的子进程
- 返回值:成功分别在父子进程中返回子进程的PID和0,失败返回-1
- 注意:该函数调用一次返回两次,在父进程中返回创建的子进程的PID,而在子进程中返回0,函数的调用者可根据返回值的不同,分别为父子进程编写不同的处理分支
- 子进程会执行fork之下的代码
-
示例
//创建子进程 #include<stdio.h> #include<unistd.h>// fork() int main(void){ printf("%d进程:我是父进程,准备创建子进程了\n",getpid()); pid_t a = fork(); if(a == -1){ perror("fork"); return -1; } //子进程代码 if(a == 0){ printf("%d进程:我是子进程\n",getpid()); return 0; } //父进程代码 printf("%d进程:我是父进程\n",getpid()); return 0; /*if(a == 0){ printf("%d进程:排骨炖豆角\n",getpid()); return 0; }else{ printf("%d进程:猪肉炖粉条\n",getpid()); return 0; } printf("%d进程:铁锅炖大鹅\n",getpid()); return 0;*/ } -
子进程是父进程的副本,子进程变量映射的虚拟地址和父进程的相同,但物理地址不同
//子进程是父进程的副本 #include<stdio.h> #include<stdlib.h> #include<unistd.h> int global = 10;//数据区 int main(void){ int local = 20;//栈区 int* heap = malloc(sizeof(int));//堆区 *heap = 30; printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,global, &local,local,heap,*heap); //创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码 if(pid == 0){ printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,++global, &local,++local,heap,++*heap); return 0; } //父进程代码 sleep(1);//延时 printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,global, &local,local,heap,*heap); return 0; } -
子进程复制父进程的文件描述符表
//子进程复制父进程的文件描述符表 #include<stdio.h> #include<string.h> #include<unistd.h> #include<fcntl.h> int main(void){ //父进程打开文件 int fd = open("./ftab.txt",O_WRONLY | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } //向文件中写入数据 hello world! 12 char* buf = "hello world!"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //父进程创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码,修改文件读写位置 if(pid == 0){ if(lseek(fd,-6,SEEK_END) == -1){ perror("lseek"); return -1; } close(fd); return 0; } //父进程代码,再次写入数据 linux! //sleep(1);//延时 buf = "linux!"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } close(fd); return 0; }
进程终止
-
正常终止
-
从main函数中返回可令进程终止
- main函数的返回值即为进程的退出码,父进程可以在回收子进程的同时获得该码,以了解导致其终止的原因
-
调用exit函数终止
-
#include<stdlib.h>
-
void exit(int status)
-
功能:令进程终止
-
参数:进程的退出码,相当于main函数的返回值
-
注意:该函数不返回;虽然exit函数的参数和main函数的返回值都是int类型,但只有其中最低数位的字节可被其父进程回收,高三个字节会被忽略,因此在设计进程的退出码时最好不要超过一字节的值域范围
-
示例
//进程的退出 #include<stdio.h> #include<stdlib.h>//exit() #include<unistd.h> //退出处理函数 void exitfun(void){ printf("我是退出处理函数\n"); } //退出处理函数 void exitfun1(int status,void* arg){ printf("status = %d\n",status); printf("arg = %s\n",(char*)arg); } int fun(void){ printf("我是fun函数,我被调用了\n"); //exit(0); _exit(0); return 10; } int main(void){ //注册退出处理函数 atexit(exitfun); on_exit(exitfun1,"hello"); int res = fun(); printf("fun函数返回%d\n",res); return 0; }
-
-
-
调用_exit/_Exit函数令进程终止
- #include<unistd.h>
- void _exit(int status)
- 参数:进程退出码,相当于main函数的返回值;该函数不返回
- #include<stdlib.h>
- void _Exit(int status)
- 参数:进程退出码,相当于main函数的返回值;该函数不返回
-
-
注册退出处理函数
-
int atexit(void (*function)(void))
- 参数:function 函数指针,指向退出处理函数
- 返回值:成功返回0,失败返回-1
- 注意:注意atexit函数本身并不调用退出处理函数,而只是将function参数所表示的退出处理函数地址,保存(注册)在系统内核的某个地方(进程表项)。待到exit函数被调用或在main函数里执行return语句时,再由系统内核根据这些退出处理函数的地址来调用它们。此过程亦称回调
-
int on_exit(void (*function)(int,void),void* arg)
-
参数:function 函数指针,指向退出处理函数。其中第一个参数来自传递给exit函数的status参数或在main函数里执行return语句的返回值,而第二个参数来自传递给on_exit函数的arg参数;arg 泛型指针,将作为第二个参数传递给function所指向的退出处理函数
-
返回值:成功返回0,失败返回-1
-
-
-
异常终止
- 当进程执行了某些在系统看来具有危险性的操作,或系统本身发生了某种故障或意外,内核会向相关进程发送特定的信号。如果进程无意针对收到的信号采取补救措施,那么内核将按照缺省方式将进程杀死,并视情形生成核心转储文件(core)以备事后分析,俗称吐核
- SIGILL(4):进程试图执行非法指令
- SIGBUS(7):硬件或对齐错误
- SIGFPE(8):浮点异常
- SIGSEGV(11):无效内存访问
- SIGPWR(30):系统供电不足
- 人为触发信号
- SIGINT(2): Ctrl +C
- SIGQUIT(3): Ctrl+\
- SIGKILL(9):不能被捕获或忽略的进程终止信号
- SIGTERM(15):可以被捕获或忽略的进程终止信号
- 向进程自己发送信号
- #include<stdlib.h>
- void abort(void)
- 功能:向调用进程发送SIGABRT(6)信号,该信号默认情况下可使进程结束;无参数,不返回
- 当进程执行了某些在系统看来具有危险性的操作,或系统本身发生了某种故障或意外,内核会向相关进程发送特定的信号。如果进程无意针对收到的信号采取补救措施,那么内核将按照缺省方式将进程杀死,并视情形生成核心转储文件(core)以备事后分析,俗称吐核
进程回收
-
#include<sys/wait.h>
-
pid_t wait(int* status)
-
功能:等待并回收任意子进程
-
参数:用于输出子进程的终止状态,可置NULL
-
返回值:成功返回所回收的子进程的PID,失败返回-1
-
示例
//收尸 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> int main(void){ //创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程,假装很耗时 if(pid == 0){ printf("%d进程:我是子进程,就不结束\n",getpid()); //sleep(5); //return 301; //exit(300); //_exit(300); //abort(); int* p = NULL; *p = 10; } //父进程,等待收尸 int s;//用来输出子进程的终止状态 pid_t childpid = wait(&s); if(childpid == -1){ perror("wait"); return -1; } printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid); if(WIFEXITED(s)){ printf("正常终止:%d\n",WEXITSTATUS(s)); }else{ printf("异常终止:%d\n",WTERMSIG(s)); } return 0; } -
回收多个子进程
//回收多个子进程 #include<stdio.h> #include<unistd.h> #include<sys/wait.h> #include<errno.h> int main(void){ for(int i = 0;i < 5;i++){ pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } if(pid == 0){ printf("%d进程:我是子进程\n",getpid()); sleep(i + 1); return 0; } } //父进程收尸 for(;;){ pid_t pid = wait(NULL);//不要子进程的终止状态 if(pid == -1){ if(errno == ECHILD){ printf("%d进程:没有子进程可回收了\n",getpid()); break; }else{ perror("wait"); return -1; } } printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid); } return 0; }
-
-
子进程的终止状态通过wait函数的status参数输出给该函数调用者。<sys/wait.h>头文件提供了几个辅助分析进程终止状态的工具宏
- WIFEXITED(status)
- 真:正常终止 WEXITSTATUS(status) -> 进程退出码
- 假:异常终止 WTERMSIG(status) -> 终止进程的信号
- WIFSIGNALED(status)
- 真:异常终止 WTERMSIG(status) -> 终止进程的信号
- 假:正常终止 WEXITSTATUS(status) -> 进程退出码
- WIFEXITED(status)
-
pid_t waitpid(pid_t pid,int* status,int options)
-
功能:等待并回收任意或特定子进程
-
参数:pid 可取下值:
-1 等待并回收任意子进程,相当于wait函数;>0 等待并回收特定子进程;
status 用于输出子进程的终止状态,可置NULL;
options 可取下值:
0 阻塞模式,若所等子进程仍在运行,则阻塞直至其终止;WNOHANG非阻塞模式,若所等子进程仍在运行,则返回0
-
返回值:成功返回所回收子进程的PID或者0。失败返回-1
-
waitpid(-1,&status,0)等价于wait(&status);
-
示例
//非阻塞方式收尸 #include<stdio.h> #include<unistd.h> #include<sys/wait.h> int main(void){ //创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程,暂时不结束 if(pid == 0){ printf("%d进程:我是子进程,暂时不结束\n",getpid()); sleep(10); return 0; } //父进程,非阻塞收尸 for(;;){ pid_t childpid = waitpid(pid,NULL,WNOHANG); if(childpid == -1){ perror("waitpid"); return -1; }else if(childpid == 0){ printf("%d进程:子进程在运行,收不了,干点别的去\n",getpid()); sleep(1); }else{ printf("%d进程:回收了%d进程的僵尸\n",getpid(),childpid); break; } } return 0; }
-
创建新进程
-
exec不是一个函数而是一堆函数(共6个),一般称为exec函数族。它们的功能是一样的,用法也很相近,只是参数的形式和数量略有不同
-
#include <unistd.h>
-
int execl (const char* path,const char* arg,…);
-
int execlp (const char* file,const char* arg,…);
-
int execle (const char* path,const char* arg,…,char* const envp[]);
-
int execv (const char* path, char* const argv[]);
-
int execvp (const char* file, char* const argv[]);
-
int execve (const char* path, char* const argv[],char* const envp[]);
-
exec函数族一共包括6个函数, 它们的函数名都是在exec后面加上一到两个字符后缀,不同的字符后缀代表不同的含义
- l:即list, 新进程的命令行参数以字符指针列表(const char* arg, …)的形式传入,列表以空指针结束
- p:即path, 若第一个参数中不包含"/",则将其视为文件名,并根据PATH环境变量搜索该文件
- e:即environment,新进程的环境变量以字符指针数组(char* const envp[])的形式传入,数组以空指针结束,不指定环境变量则从调用进程复制
- v:即vector,新进程的命令行参数以字符指针数组(char* const argv[])的形式传入,数组以空指针结束
-
示例
new.c
#include<stdio.h> #include<unistd.h> int main(int argc,char* argv[],char* envp[]){ printf("PID:%d\n",getpid()); printf("命令行参数\n"); for(char** pp = argv;*pp;pp++){ printf("%s\n",*pp); } printf("环境变量\n"); for(char**pp = envp;*pp;pp++){ printf("%s\n",*pp); } printf("---------------------------\n"); return 0; }exec.c
//让新进程取代原本的旧进程 #include<stdio.h> #include<unistd.h> int main(void){ printf("%d进程:我要变身了\n",getpid()); /*if(execl("./new","new","hello","123",NULL) == -1){ perror("execl"); return -1; }*/ /*if(execlp("ls","ls","-a","-i","-l",NULL) == -1){ perror("execlp"); return -1; }*/ //char* envp[4] = {"NAME=laozhang","AGE=18","FOOD=guobaorou",NULL}; /*if(execle("./new","new","hello","123",NULL,envp) == -1){ perror("execle"); return -1; }*/ char* argv[] = {"new","hello","123",NULL}; char* envp[4] = {"NAME=laozhang","AGE=18","FOOD=guobaorou",NULL}; if(execve("./new",argv,envp) == -1){ perror("execve"); return -1; } printf("%d进程:我变身完成了\n",getpid()); return 0; } -
既想创建新进程,又想原来的进程继续存在,则可考虑fork+exec
//fork + exec #include<stdio.h> #include<unistd.h> #include<sys/wait.h> int main(void){ //父进程创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码 if(pid == 0){ if(execl("/bin/ls","ls","-i","-l",NULL) == -1){ perror("execl"); return -1; } //return 0; } //父进程代码 int s;//用来输出终止状态 if(waitpid(pid,&s,0) == -1){ perror("waitpid"); return -1; } if(WIFEXITED(s)){ printf("正常终止:%d\n",WEXITSTATUS(s)); }else{ printf("异常终止:%d\n",WTERMSIG(s)); } return 0; } -
#include<stdlib.h>
-
int system(const char* command)
-
功能:执行shell命令
-
参数:shell命令字符串
-
返回值:成功返回command进程的终止状态,失败返回-1
-
system函数执行command参数所表示的命令行,并返回命令进程的终止状态;若command参数取NULL,返回非0表示Shell可用,返回0表示Shell不可用
-
示例
//system #include<stdio.h> #include<stdlib.h>// system #include<sys/wait.h> int main(void){ int s = system("ls -i -l"); if(s == -1){ perror("system"); return -1; } if(WIFSIGNALED(s)){ printf("异常终止:%d\n",WTERMSIG(s)); }else{ printf("正常终止:%d\n",WEXITSTATUS(s)); } return 0; }
-
信号
信号处理
-
#include<signal.h>
-
typedef void (*sighandler_t)(int);
-
sighandler_t signal(int signum,sighandler_t handler)
-
功能:设置调用进程针对特定信号的处理方式
-
参数:信号编号;handler 信号的处理方式,可取下值:
SIG_IGN-忽略;SIG_DFL-默认;信号处理函数指针-捕获
-
返回值:成功返回原信号处理方式,如果之前未处理过则返回NULL,失败返回SIG_ERR
-
-
示例
//信号处理 #include<stdio.h> #include<signal.h>// signal #include<unistd.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } int main(void){ //对2号信号进行忽略处理 if(signal(SIGINT,SIG_IGN) == SIG_ERR){ perror("signal"); return -1; } //对2号信号新型捕获处理 if(signal(SIGINT,sigfun) == SIG_ERR){ perror("signal"); return -1; } //对2号信号进行默认处理 if(signal(SIGINT,SIG_DFL) == SIG_ERR){ perror("signal"); return -1; } for(;;); return 0; }
太平间信号
-
在信号处理函数执行期间,如果有多个相同的信号到来,只保留一个,其余丢失
-
示例
//太平间信号 #include<stdio.h> #include<unistd.h> #include<signal.h> #include<sys/wait.h> #include<errno.h> //信号处理函数,负责收尸 void sigchild(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); sleep(2);//假装信号处理函数比较函数 for(;;){ pid_t pid = waitpid(-1,NULL,WNOHANG); if(pid == -1){ if(errno == ECHILD){ printf("%d进程:没有子进程了\n",getpid()); break; }else{ perror("waitpid"); return; } }else if(pid == 0){ printf("%d进程:子进程在运行\n",getpid()); break; }else{ printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid); } } /*for(;;){ pid_t pid = wait(NULL); if(pid == -1){ if(errno == ECHILD){ printf("%d进程:没有子进程了\n",getpid()); break; }else{ perror("wait"); return; } } printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid); }*/ } int main(void){ //对17号信号进行捕获处理 if(signal(SIGCHLD,sigchild) == SIG_ERR){ perror("signal"); return -1; } //创建多个子进程 for(int i = 0;i < 5;i++){ pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } if(pid == 0){ printf("%d进程:我是子进程\n",getpid()); //sleep(1 + i); sleep(1); return 0; } } //父进程代码 //创建老六 pid_t oldsix = fork(); if(oldsix == -1){ perror("fork"); return -1; } if(oldsix == 0){ printf("%d进程:我是老六!!!!!!!!\n",getpid()); sleep(15); return 0; } for(;;); return 0; }
信号处理的继承与恢复
-
fork函数创建的子进程会继承父进程的信号处理方式
-
父进程中对某个信号进行捕获,则子进程中对该信号依然捕获
-
父进程中对某个信号进行忽略,则子进程中对该信号依然忽略
-
示例
//验证子进程是否继承父进程信号处理方式 #include<stdio.h> #include<unistd.h> #include<sys/wait.h> #include<signal.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } int main(void){ //对2号信号进行忽略处理 if(signal(SIGINT,SIG_IGN) == SIG_ERR){ perror("signal"); return -1; } //对3号信号进行捕获处理 if(signal(SIGQUIT,sigfun) == SIG_ERR){ perror("signal"); return -1; } //创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } // kill -2 PID if(pid == 0){ printf("%d进程:我是子进程\n",getpid()); for(;;); return 0; } //父进程代码 if(wait(NULL) == -1){ perror("wait"); return -1; } return 0; }
-
-
exec家族函数创建的新进程对信号的处理方式和原进程稍有不同
-
原进程中被忽略的信号,在新进程中依然被忽略
-
原进程中被捕获的信号,在新进程中被默认处理
-
示例
//新进程 #include<stdio.h> int main(void){ for(;;); return 0; }//验证新进程是否继承旧进程的信号处理方式 #include<stdio.h> #include<unistd.h> #include<signal.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } int main(void){ //忽略2号信号 if(signal(SIGINT,SIG_IGN) == SIG_ERR){ perror("signal"); return -1; } //捕获3号信号 if(signal(SIGQUIT,sigfun) == SIG_ERR){ perror("signal"); return -1; } //变身 if(execl("./new","new",NULL) == -1){ perror("execl"); return -1; } return 0; }
-
发送信号
-
用专门的系统命令发送信号
- kill [-信号] PID
- 若不指明具体信号,缺省发送SIGTERM(15)信号
- 若要指明具体信号,可以使用信号编号,也可以使用信号名称,而且信号名称中的"SIG"前缀可以省略不写。 例如
- kill-91234
- kill -SIGKILL 1234 5678
- kill-KILL -1 给所有信号发送9号信号
- 超级用户可以发给任何进程,而普通用户只能发给自己的进程
-
#include<signal.h>
-
int kill(pid_t pid,int signum)
-
功能:向指定的进程发送信号
-
参数:pid 可取下值
-1 -向系统中所有进程发送信号;>0 -向特定进程(由pid标识)发送信号;
信号编号,取0可用于检查pid进程是否存在,如不存在kill函数会返回-1,且errno为ESRCH
-
返回值:成功(至少发出去一个信号)返回0,失败返回-1
-
示例
//kill函数 #include<stdio.h> #include<unistd.h> #include<signal.h> #include<sys/wait.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } int main(void){ //父进程创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码,对2号信号进行捕获处理 if(pid == 0){ /*if(signal(SIGINT,sigfun) == SIG_ERR){ perror("signal"); return -1; }*/ for(;;); return 0; } //父进程代码,给子进程发送2号信号 getchar(); if(kill(pid,SIGINT) == -1){ perror("kill"); return -1; } //判断子进程是否存在 不收尸的话进程依旧以僵尸形式存在 getchar(); if(kill(pid,0) == -1){ printf("子进程不存在\n"); }else{ printf("子进程存在\n"); } //收尸 if(wait(NULL) == -1){ perror("wait"); return -1; } //再次判断 getchar(); if(kill(pid,0) == -1){ printf("子进程不存在\n"); }else{ printf("子进程存在\n"); } return 0; }
-
-
int raise(int signum)
- 功能:向调用进程自己发送信号
- 参数:信号编号
- 返回值:成功返回0,失败返回非0
- kill(getpid(),signum) 等价于该函数
暂停、睡眠与闹钟
-
#include <unistd.h>
-
int pause(void);
-
功能:无限睡眠
-
返回值:成功阻塞,失败返回-1
-
该函数使调用进(线)程进入无时限的睡既状态,直到有信号终止了该进程或被其捕获。如果有信号被调用进程捕获,在信号处理函数返回以后,pause函数才会返回,其返回值-1,同时置errno为EINTR,表示阻塞的系统调用被信号打断。pause函数要么不返回,要么返回-1,永远不会返回0。
-
示例
//暂停 #include<stdio.h> #include<unistd.h> #include<signal.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:%d号信号处理开始\n",getpid(),signum); sleep(3); printf("%d进程:%d号信号处理结束\n",getpid(),signum); } int main(void){ //对2号信号进行捕获处理 if(signal(SIGINT,sigfun) == SIG_ERR){ perror("signal"); return -1; } int res = pause(); printf("%d进程:pause函数返回%d\n",getpid(),res); return 0; }
-
-
unsigned int sleep(unsigned int seconds)
-
功能:有限睡眠
-
参数:以秒为单位的睡眠时限
-
返回值:0或剩余秒数
-
该函数调用进程睡眠seconds秒,除非有信号终止了调用进程或被其捕获
-
如果有信号被调用进程捕获,在信号处理函数返回后,sleep函数才会返回,且返回值为剩余的秒数,否则该函数返回0,表示睡眠充足
-
示例
//睡眠 #include<stdio.h> #include<unistd.h> #include<signal.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:%d号信号处理开始\n",getpid(),signum); sleep(3); printf("%d进程:%d号信号处理结束\n",getpid(),signum); } int main(void){ //对2号信号进行捕获处理 if(signal(SIGINT,sigfun) == SIG_ERR){ perror("signal"); return -1; } int res = sleep(8); printf("%d进程:sleep函数返回%d\n",getpid(),res); return 0; }
-
-
int usleep(useconds_t usec)
- 功能:更精确的有限睡眠
- 参数: usec以微秒(1微秒= 10^-6秒)为单位的睡眠时限
- 返回值:成功返回0,失败返回-1
- 如果有信号被调用进程捕获,在信号处理函数返回以后,usleep函数才会返回,且返回值为-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断
-
unsigned int alarm(unsigned int seconds)
-
功能:设置闹钟
-
参数:以秒为单位的闹钟时间
-
返回值:返回0或先前设置的闹钟剩余的秒数
-
alarm函数使系统内核在该函数被调用以后seconds秒的时候,向调用进程发送SIGALRM(14)信号
-
若在调用该函数前已设过闹钟且尚未到期,则该函数会重设闹钟,并返回先前所设闹钟的剩余秒数,否则返回0
-
若seconds取0,则表示取消先前设过且尚未到期的闹钟
-
示例
//闹钟 #include<stdio.h> #include<unistd.h> #include<signal.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } int main(void){ //对14号信号进行捕获处理 if(signal(SIGALRM,sigfun) == SIG_ERR){ perror("signal"); return -1; } printf("alarm(10)返回%u\n",alarm(10));//不是阻塞函数 getchar(); printf("alarm(5)返回%u\n",alarm(5)); for(;;); return 0; }
-
信号集
-
多个信号组成的信号合集称为信号集
-
系统内核用sigset_t类型表示信号集,sigset_t类型是一个结构体,但该结构体中只有一个成员,是一个包含32个元素的整数数组(针对32位系统而言)
-
#include<signal.h>
-
int sigfillset(sigset_t* sigset)
- 功能:填满信号集,即将信号集的全部信号位置1
- 参数:信号集
- 返回值:成功0,失败-1
-
int sigemptyset(sigset_t* sigset)
- 功能:清空信号集,即将信号集的全部信号位清0
- 参数:信号集
- 返回值:成功0,失败-1
-
int sigaddset(sigset_t* sigset,int signum)
- 功能:加入信号,即将信号集中与指定信号编号对应的信号位置1
- 参数:信号集;信号编号
- 成功0,失败-1
-
int sigdelset(sigset_t* sigset,int signum)
- 功能:删除信号,即将信号集中与指定信号编号对应的信号位清0
- 参数:信号集;信号编号
- 成功0,失败-1
-
int sigismember(const sigset_t* sigset,int signum)
- 功能:判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号是否为1
- 参数:信号集;信号编号
- 有则返回1,无则返回0,失败-1
-
示例
//信号集演示 #include<stdio.h> #include<unistd.h> #include<signal.h> //打印一个字节,8位内容 void printb(char byte){ for(int i = 0;i < 8;i++){ printf("%d",byte & 1 << 7 - i ? 1 : 0); } printf(" ");//空格 } //打印一个存储区里若干个字节的比特位 void printm(void* buf,size_t size){ for(int i = 0;i < size;i++){ printb(((char*)buf)[size-1-i]); if((i + 1) % 8 == 0){ printf("\n"); } } } int main(void){ //信号集 sigset_t set;// 128 printf("填满信号集\n"); sigfillset(&set); printm(&set,sizeof(set)); printf("清空信号集\n"); sigemptyset(&set); printm(&set,sizeof(set)); printf("添加2号信号\n"); sigaddset(&set,SIGINT); printm(&set,sizeof(set)); printf("删除2号信号\n"); sigdelset(&set,SIGINT); printm(&set,sizeof(set)); return 0; }
信号屏蔽
-
#include<signal.h>
-
int sigprocmask(int how,const sigset_t* sigset,sigset_t* oldset)
-
功能:设置调用进程的信号掩码
-
参数:how 修改信号掩码的方式,可取下值
SIG_BLOCK-将sigset中的信号加入当前信号掩码;SIG_UNBLOCK-从当前信号掩码中删除sigset中的信号;SIG_SETMASK-把sigset设置成当前信号掩码;
信号集,取NULL则忽略此参数;输出原信号掩码,取NULL则忽略此参数
-
返回值:成功返回0,失败返回-1
-
示例
//信号掩码 #include<stdio.h> #include<unistd.h> #include<signal.h> #include<sys/wait.h> //信号处理函数 void sigfun(int signum){ printf("%d进程:捕获到%d号信号\n",getpid(),signum); } //更新数据库 void updatedb(void){ for(int i = 0;i < 5;i++){ printf("正在更新第%d条数据\n",i+1); sleep(1); } } int main(void){ int signum = /*SIGINT*/ 50; //父进程捕获2号信号 if(signal(signum,sigfun) == SIG_ERR){ perror("signal"); return -1; } //屏蔽2号信号 printf("屏蔽%d号信号\n",signum); sigset_t sigset;//信号集 sigemptyset(&sigset);//清空 sigaddset(&sigset,signum);//添加2号信号 sigset_t oldset;//用来输出修改前的信号掩码 if(sigprocmask(SIG_SETMASK,&sigset,&oldset) == -1){ perror("sigprocmask"); return -1; } //创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } if(pid == 0){ for(int i = 0;i < 5;i++){ printf("给父进程发送%d号信号\n",signum); if(kill(getppid(),signum) == -1){ perror("kill"); return -1; } } return 0; } //父进程 updatedb(); //解除屏蔽 printf("解除对%d号信号的屏蔽\n",signum); if(sigprocmask(SIG_SETMASK,&oldset,NULL) == -1){ perror("sigprocmask"); return -1; } //收尸 if(wait(NULL) == -1){ perror("wait"); return -1; } return 0; }
-
UC高级
管道
- 管道的工作模式均为半双工,数据只能沿着一个方向流动
有名管道
-
有名管道亦称FIFO,是一种特殊的文件,通过mkfifo命令可以创建管道文件
-
即使毫无亲缘关系的进程也可以通过管道文件通信
-
管道文件在磁盘上只有i节点没有数据块,也不保存数据
-
#include<sys/stat.h>
-
int mkfifo(char const* pathname,mode_t mode)
- 功能:创建有名管道
- 参数:有名管道名,即管道文件的路径;权限模式
- 返回值:成功返回0,失败返回-1
-
示例
//向管道文件写入数据 #include<stdio.h> #include<string.h> #include<unistd.h> #include<fcntl.h> #include<sys/stat.h> int main(void){ //创建管道文件 printf("%d进程:创建管道文件\n",getpid()); if(mkfifo("./fifo",0664) == -1){ perror("mkfifo"); return -1; } //打开管道文件 printf("%d进程:打开管道文件\n",getpid()); int fd = open("./fifo",O_WRONLY); if(fd == -1){ perror("open"); return -1; } //写入管道文件 printf("%d进程:发送数据\n",getpid()); for(;;){ //通过键盘获取数据 char buf[64] = {}; fgets(buf,sizeof(buf),stdin); //什么时候输入!,什么时候退出 if(strcmp(buf,"!\n") == 0){ break; } //写入管道文件 if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } } //关闭管道文件 printf("%d进程:关闭管道文件\n",getpid()); close(fd); //删除管道文件 printf("%d进程:删除管道文件\n",getpid()); unlink("./fifo"); printf("%d进程:大功告成\n",getpid()); return 0; }//读取管道文件 #include<stdio.h> #include<unistd.h> #include<fcntl.h> int main(void){ //打开管道文件 printf("%d进程:打开管道文件\n",getpid()); int fd = open("./fifo",O_RDONLY); if(fd == -1){ perror("open"); return -1; } //读取管道文件 printf("%d进程:接收数据\n",getpid()); for(;;){ //读取管道 char buf[64] = {}; size_t size = read(fd,buf,sizeof(buf)-1); if(size == -1){ perror("read"); return -1; } if(size == 0){ printf("%d进程:对方关闭管道文件\n",getpid()); break; } //显示 printf("%s",buf); } //关闭管道文件 printf("%d进程:关闭管道文件\n",getpid()); close(fd); printf("%d进程:大功告成\n",getpid()); return 0; }
无名管道
-
无名管道是一个与文件系统无关的内核对象,主要用于父子进程之间的通信,需要用专门的系统调用函数创建
-
#include<unistd.h>
-
int pipe(int piped[2])
- 功能:创建无名管道
- 参数:输出两个文件描述符:piped[0]-用于从无名管道中读取数据;piped[1]-用于向无名管道中写入数据
- 返回值:成功返回0,失败返回-1
-
示例
//无名管道 #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> int main(void){ //父进程创建无名管道,得到读端写端描述符 int fd[2] = {};//用来输出读端和写端描述符 if(pipe(fd) == -1){ perror("pipe"); return -1; } printf("fd[0] = %d\n",fd[0]); printf("fd[1] = %d\n",fd[1]); //父进程创建子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码,读取数据 if(pid == 0){ printf("%d进程:接收数据\n",getpid()); printf("%d进程:关闭写端\n",getpid()); close(fd[1]); for(;;){ //从读端读取数据 char buf[64] = {}; ssize_t size = read(fd[0],buf,sizeof(buf)-1); if(size == -1){ perror("read"); return -1; } if(size == 0){ break; } //显示 printf("%s",buf); } printf("%d进程:关闭读端\n",getpid()); close(fd[0]); printf("%d进程:大功告成\n",getpid()); return 0; } //父进程代码,发送数据 printf("%d进程:发送数据\n",getpid()); printf("%d进程:关闭读端\n",getpid()); close(fd[0]); for(;;){ //通过键盘获取要写入的数据 char buf[64] = {}; fgets(buf,sizeof(buf),stdin); // ! 退出 if(strcmp(buf,"!\n") == 0){ break; } //将数据写入管道 if(write(fd[1],buf,strlen(buf)) == -1){ perror("write"); return -1; } } printf("%d进程:关闭写端\n",getpid()); close(fd[1]); //父进程,收尸 if(wait(NULL) == -1){ perror("wait"); return -1; } printf("%d进程:大功告成\n",getpid()); return 0; }
IPC对象
- #include<sys/ipc.h>
- key_t ftok(const char* pathname,int proj_id)
- 功能:用于合成一个键
- 参数:一个真实存在的路径名;项目ID,仅低8位有效,取0到255之间的数
- 返回值:成功返回可用于创建或获取IPC对象的键,失败返回-1
共享内存
-
#include<sys/shm.h>
-
int shmget(key_t key,size_t size,int shmflg)
-
功能:创建新的或获取已有的共享内存
-
参数:键;字节数,自动按页取整;创建标志,可取下值:
0-获取,不存在即失败;IPC_CREAT-创建,不存在即创建,已存在即获取;IPC_EXCL-排它,不存在即创建,已存在即失败;通过位或组合读写权限
-
返回值:成功返回共享内存的ID,失败返回-1
-
-
void* shmat(int shmid,void const* shmaddr,int shmflg)
-
功能:加载共享内存,将物理内存中的共享区域映射到进程用户空间的虚拟内存中
-
参数:共享内存的ID;映射到共享内存的虚拟内存起始地址,取NULL由系统自动选择;加载标志,可取下值:
0-以读写方式使用共享;SHM_RDONLY-以只读方式使用共享内存
-
返回值:成功返回共享内存的起始地址,失败返回-1
-
-
int shmdt(void const* shmaddr)
- 功能:卸载共享内存
- 参数:共享内存的起始地址
- 返回值:成功返回0,失败返回-1
-
int shmctl(int shmid,IPC_RMID,NULL)
- 功能:销毁共享内存
- 参数:shmid:共享内存对象ID
- 返回值:成功返回0,失败返回-1
- 销毁共享内存。其实并非真的销毁,而只是做一一个销毁标记,禁止任何进程对该共享内存销毁共享内存。其实并非真的销毁,而只是做一一个销毁标记,禁止任何进程对该共享内存为0时,共享内存才会真的被销毁
-
示例
wshm.c
//写入共享内存 #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/shm.h> int main(void){ //合成键 printf("%d进程:合成键\n",getpid()); key_t key = ftok(".",123); if(key == -1){ perror("ftok"); return -1; } //创建共享内存 printf("%d进程:创建共享内存\n",getpid()); int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL | 0664); if(shmid == -1){ perror("shmget"); return -1; } //加载共享内存 printf("%d进程:加载共享内存\n",getpid()); getchar(); char* shmaddr = shmat(shmid,NULL,0); if(shmaddr == (void*)-1){ perror("shmat"); return -1; } //写入共享内存 printf("%d进程:写入共享内存\n",getpid()); getchar(); strcpy(shmaddr,"UC真好玩"); //卸载共享内存 printf("%d进程:卸载共享内存\n",getpid()); getchar(); if(shmdt(shmaddr) == -1){ perror("shmdt"); return -1; } //销毁共享内存 printf("%d进程:销毁共享内存\n",getpid()); getchar(); if(shmctl(shmid,IPC_RMID,NULL) == -1){ perror("shmctl"); return -1; } printf("%d进程:大功告成\n",getpid()); return 0; }rshm.c
//读取共享内存 #include<stdio.h> #include<unistd.h> #include<sys/shm.h> int main(void){ //合成键 printf("%d进程:合成键\n",getpid()); key_t key = ftok(".",123); if(key == -1){ perror("ftok"); return -1; } //获取共享内存 printf("%d进程:获取共享内存\n",getpid()); int shmid = shmget(key,0,0); if(shmid == -1){ perror("shmget"); return -1; } //加载共享内存 printf("%d进程:加载共享内存\n",getpid()); getchar(); char* shmaddr = shmat(shmid,NULL,0); if(shmaddr == (void*)-1){ perror("shmat"); return -1; } //读取共享内存 printf("%d进程:读取共享内存\n",getpid()); getchar(); printf("%s\n",shmaddr); //卸载共享内存 printf("%d进程:卸载共享内存\n",getpid()); getchar(); if(shmdt(shmaddr) == -1){ perror("shmdt"); return -1; } return 0; }
消息队列
消息队列是一个由系统内核负责存储和管理,并通过消息队列标识符引用的消息链表队列
-
#include<sys/msg.h>
-
int msgget(key_t key,int msgflg)
-
功能:创建新的或获取已有的消息队列
-
参数:键;创建标志,可取下值:
0-获取,不存在即失败;IPC_CREAT-创建,不存在即创建,已存在即获取;IPC_EXCL-排它,不存在即创建,已存在即失败;通过位或组合读写权限
-
返回值:成功返回消息队列的ID,失败返回-1
-
-
int msgsnd(int msgid,void const* msgp,size_t msgsz,int msgflg)
- 功能:发送消息
- 参数:消息队列的ID;指向一个包含消息类型和消息数据的内存块。该内存块的前4个字节必须是一个大于0的整数,代表消息类型,其后紧跟消息数据;msgsz:期望发送消息数据(不含消息类型)的字节数;msgflg:发送标志,一般取0即可;
- 返回值:成功返回0,失败返回-1
-
int msgrcv(int msgid,void* msgp,size_t msgsz,long msgtyp,int msgflg)
-
功能:接收消息
-
参数:消息队列的ID;指向一块包含消息类型(4字节,64位系统为8字节)和消息数据的内存;期望接收消息数据(不含消息类型)的字节数;消息类型,可取下值:
0-提取消息队列的第一条消息;>0-若msgflg参数不包含MSG_ EXCEPT位,则提取消息队列的第一条类型为msgtyp的消息;若msgflg参数包含MSG_ EXCEPT位,则提取消息队列的第一条类型不为msgtyp的消息;<0-提取消息队列中类型小于等于msgtyp的绝对值的消息,类型越小的消息越被优先提取;
接收标志,一般取0即可,可取下值:
MSG_NOERROR:接收数据长度大于msgze参数的消息时,截取前msgze个大小的消息返回,其余部分丢弃;IPC_NOWAIT:非阻塞方式
-
返回值:成功返回实际接收到的消息数据字节数,失败返回-1
-
-
int msgctl(int msgid,IPC_RMID,NULL)
- 功能:销毁消息队列
- 参数:消息队列的ID
- 返回值:成功返回0,失败返回-1
-
示例
wmsg.c
//写入消息队列 #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/msg.h> int main(void){ //合成键 printf("%d进程:合成键\n",getpid()); key_t key = ftok(".",100); if(key == -1){ perror("ftok"); return -1; } //创建消息队列 printf("%d进程:创建消息队列\n",getpid()); int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0664); if(msgid == -1){ perror("msgget"); return -1; } //发送消息 printf("%d进程:发送消息\n",getpid()); for(;;){ //通过键盘获取数据 struct { long type;//类型 char data[64];//内容 }buf = {1234}; fgets(buf.data,sizeof(buf.data),stdin); //!退出 if(strcmp(buf.data,"!\n") == 0){ break; } //发送 if(msgsnd(msgid,&buf,strlen(buf.data),0) == -1){ perror("msgsnd"); return -1; } } //销毁消息队列 printf("%d进程:销毁消息队列\n",getpid()); if(msgctl(msgid,IPC_RMID,NULL) == -1){ perror("msgctl"); return -1; } printf("%d进程:大功告成\n",getpid()); return 0; }rmsg.c
//读取消息队列 #include<stdio.h> #include<unistd.h> #include<sys/msg.h> #include<errno.h> int main(void){ //合成键 printf("%d进程:合成键\n",getpid()); key_t key = ftok(".",100); if(key == -1){ perror("ftok"); return -1; } //获取消息队列 printf("%d进程:获取消息队列\n",getpid()); int msgid = msgget(key,0); if(msgid == -1){ perror("msgget"); return -1; } //接收消息 printf("%d进程:接收消息\n",getpid()); for(;;){ //接受消息 struct { long type;//类型 char data[64];//内容 }buf = {}; if(msgrcv(msgid,&buf,sizeof(buf.data)-1,1234,0) == -1){ if(errno == EIDRM){ printf("%d进程:消息队列被销毁\n",getpid()); break; }else{ perror("msgrcv"); return -1; } } //显示 printf("%ld>>%s",buf.type,buf.data); } printf("%d进程:大功告成\n",getpid()); return 0; }
IPC命令
- 查看系统中IPC对象
- ipcs -m (memory,共享内存)
- ipcs -q (message queue,消息队列)
- ipcs -s (semphore,信号量集)
- ipcs -a (all,所有的)
- 删除系统中的IPC对象
- ipcrm -m 删除共享内存
- ipcrm -q 删除消息队列
- ipcrm -s 删除信号量集
网络
套接字
-
#include<sys/socket.h>
-
int socket(int domain,int type,int protocol)
-
功能:创建套接字
-
参数:domain:通信域,协议族,可取下值:
PF_LOCAL/PF_UNIX-本地套接字,进程间通信;PF_INET-基于IPv4的网络通信;PF_INET6-基于IPv6的网络通信;PF_PACKET-基于底层包的网络通信;
type:套接字类型,可取下值:
SOCK_STREAM-流式套接字,基于TCP协议;SOCK_DGRAM-数据报套接字,基于UDP协议;SOCK_RWA-原始套接字,工作在传输层以下;
protocol:特殊协议,对于流式和数据报套接字而言,只能取0
-
返回值:成功返回表示套接字对象的文件描述符,失败返回-1
-
-
套接字接口库通过地质结构定位一个通信主体,可以是一个文件,可以是一台远程主机,也可以是执行者自己
-
基本地址结构,本身没有实际意义,仅用于泛型化参数
struct sockaddr{
sa_family_t sa_family;//地址族
char sa_Data[14];//地址值
};
-
本地地址结构,用于AF_LOCAL/AF_UNIX域的本地同i性能
struct sockaddr{
sa_family_t sun_family;//地址族(AF_LOCAL/AF_UNIX)
char sun_path[];//本地套接字文件的路径
};
-
网络地址结构,用于AF_INET域的IPv4网络通信
struct sockaddr_in{
sa_family_t sin_family;//地址族(AF_INET)
in_port_t sin_port;//端口号(0~65535)- unsigned short
struct in_addr sin_addr;//IP地址 -unsigned int
};
struct in_addr{
in_Addr_t s_addr;
}
typedef uint16_t in_port_t;//无符号16位整数
typedef uint32_t in_addr_t;//无符号32位整数
-
-
int bind(int sockfd,struct sockaddr const* addr,socklen_t addrlen)
- 功能:将套接字和本机的地址构绑定在一起
- 参数:套接字描述符;自己的地址结构;地址结构的字节数
- 返回值:成功返回0,失败返回-1
-
int connect(int sockfd,struct sockaddr const* addr,socklen_t addrlen)
- 功能:将套接字和对方的地址结构连接在一起
- 参数:套接字描述符;对方的地址结构;地址结构的字节数
- 返回值:成功返回0,失败返回-1
字节序转换
-
网络应用与单机应用不同,经常需要在具有不同硬件架构和操作系统的计算机之间交换数据,因此编程语言里一些多字节数据类型的字节序问题就需要特别予以关注
-
套接字接口库规定在网络传输过程中采用网络字节序,也就是大端字节序,而本机数据可能是小短字节序
- 小端字节序:数据的低位存放在低地址
- 大端字节序:数据的低位存放在高地址
-
转换函数
-
uint32_ t htonl(uint32_ t hostlong); //长整形主机字节序到网络字节序
-
uint32_ t ntohl(uint32 _t netllong); //长整形网络字节序到主机字节序
-
uint16_ t htons(uint16_ t hostshort);//短整形主机字节序到网络字节序
-
uint16_ t ntohs(uint16_ t netshort);//短整形网络字节序到主机字节序
-
in_ addr_t inet_addr(char const* ip);//点分十进制字符串地址 --> 网络字节序形式整数地址
-
int inet_ aton(char const* ip, struct in_addr* nip);//点分十进制字符串地址 --> 网络字节序形式整数地址
-
char* inet_ ntoa(struct in _addr nip);//网络字节序形式整数地址 --> 点分十进制字符串地址
-
TCP常用函数
-
#include<sys/socket.h>
-
int listen(int sockfd,int backlog)
- 功能:启动侦听,在指定套接字上启动对连接请求的侦听功能
- 参数:套接字描述符,在调用此函数之前是一个主动套接字,是不能感知连接请求的,在调用此函数并成功返回后,是一个被动套接字(原来的功能也具备),具有感知连接请求的能力;未决连接请求队列的最大长度,一般取不小于1024的值
- 返回值:成功返回0,失败返回-1
-
int accept(int sockfd,struct sockaddr* addr,socklent_t* addrlen)
- 功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成
- 参数:侦听套接字描述符;输出连接请求发起方的地址信息;输出连接请求发起方的地址信息字节数
- 返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1
-
ssize_t recv(int sockfd,void* buf,size_t count,int flags)
-
功能:接收数据
-
参数:若flags取0则与read函数完全等价,另外也可取下值:
MSG_DONWAIT-以非阻塞方式接收数据;MSG_OOB-接收带外数据;MSG_WAITALL-等待所有数据,即不接收到count字节就不返回
-
返回值:成功返回实际接收到的字节数,失败返回-1
-
-
ssize_t send(int sockfd,void const* buf,size_t count,int flags)
-
发送数据
-
参数:若flags取0则与write函数完全等价,另外也可取下值:
MSG_DONWAIT-以非阻塞方式接收数据;MSG_OOB-接收带外数据;MSG_DONTROUTE-不查路由表,直接在本地网络中寻找目的主机
-
返回值:成功返回实际发送的字节数,失败返回-1
-
-
示例
服务端 tcpser.c
//基于tcp的服务器 #include<stdio.h> #include<string.h> #include<ctype.h>// toupper() #include<unistd.h> #include<signal.h> #include<errno.h> #include<sys/wait.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> //信号处理函数,负责收尸 void sigchild(int signum){ for(;;){ pid_t pid = waitpid(-1,NULL,WNOHANG); if(pid == -1){ if(errno == ECHILD){ printf("没有子进程\n"); break; }else{ perror("waitpid"); return; } }else if(pid == 0){ printf("子进程在运行\n"); break; }else{ printf("回收了%d进程的僵尸\n",pid); } } } int main(void){ //对17号信号进行捕获处理 if(signal(SIGCHLD,sigchild) == SIG_ERR){ perror("signal"); return -1; } //买了一部手机 printf("服务器:创建套接字\n"); int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd == -1){ perror("socket"); return -1; } //办张电话卡 printf("服务器:组织地址结构\n"); struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(8980); ser.sin_addr.s_addr = inet_addr("192.168.222.136"); //绑定,电话卡插入手机 printf("服务器:绑定套接字和地址结构\n"); if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){ perror("bind"); return -1; } //套接字 主动 --> 被动 能玩三次握手 printf("服务器:启动侦听\n"); if(listen(sockfd,1024) == -1){ perror("listen"); return -1; } for(;;){ printf("服务器:等待客户端的连接\n"); struct sockaddr_in cli;//用来输出客户端的地址结构 socklen_t len = sizeof(cli);//输出地址结构的大小 int conn = accept(sockfd,(struct sockaddr*)&cli,&len); if(conn == -1){ perror("accept"); return -1; } printf("服务器:接受到%s:%hu的客户端的连接\n", inet_ntoa(cli.sin_addr),ntohs(cli.sin_port)); //父进程创建子进程,子进程扶着和客户端通信 pid_t pid = fork(); if(pid == -1){ perror("fork"); return -1; } //子进程代码 if(pid == 0){ close(sockfd); printf("服务器:业务处理\n"); for(;;){ //接收客户端发来的小写的串 char buf[64] = {}; ssize_t size = read(conn,buf,sizeof(buf)-1); if(size == -1){ perror("read"); return -1; } if(size == 0){ break; } //转大写 for(int i = 0;i < strlen(buf);i++){ buf[i] = toupper(buf[i]); } //回传给客户端 if(write(conn,buf,strlen(buf)) == -1){ perror("write"); return -1; } } printf("服务器:关闭套接字\n"); close(conn); return 0; } //父进程代码 close(conn); } close(sockfd); return 0; }客户端 tcpcli.c
//基于TCP的客户端 #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> int main(void){ printf("客户端:创建套接字\n"); int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd == -1){ perror("socket"); return -1; } printf("客户端:准备服务器地址结构\n"); struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(8980); ser.sin_addr.s_addr = inet_addr("192.168.222.136"); printf("客户端:发起连接\n"); if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){ perror("connect"); return -1; } printf("客户端:业务处理\n"); for(;;){ //通过键盘获取小写的串 char buf[64] = {}; fgets(buf,sizeof(buf),stdin); //!退出 if(strcmp(buf,"!\n") == 0){ break; } //发送给服务器 if(send(sockfd,buf,strlen(buf),0) == -1){ perror("send"); return -1; } //接收回传的大写的串 if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){ perror("recv"); return -1; } //显示 printf("%s",buf); } printf("客户端:关闭套接字\n"); close(sockfd); return 0; }
UDP常用函数
-
#include<sys/socket.h>
-
ssize_t recvfrom(int sockfd,void* buf,size_t count,int flags,struct sockaddr* src_addr,socklen_t* addrlen)
- 功能:从哪里接收数据
- 参数:前四个参数和函数recv相同;src_addr:输出源主机的地址信息;addrlen:输入输出源主机的地址信息的字节数
- 返回值:成功返回实际接收的字节数,失败返回-1
-
ssize_t sendto(int sockfd,void const* buf,size_t count,int flags,struct sockaddr const* dest_addr,socklen_t addrlen)
- 功能:发送数据到哪里
- 参数:前四个参数和函数send相同;dest_addr:目的主机的地址信息;addrlen:目的主机的地址信息的字节数
- 返回值:成功返回实际发送的字节数,失败返回-1
-
示例
udpser.c
//udp服务器 #include<stdio.h> #include<string.h> #include<ctype.h> #include<unistd.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> int main(void){ printf("服务器:创建套接字\n"); int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(sockfd == -1){ perror("socket"); return -1; } printf("服务器:组织地址结构\n"); struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(9999); ser.sin_addr.s_addr = inet_addr("192.168.222.136"); printf("服务器:绑定套接字和地址结构\n"); if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){ perror("bind"); return -1; } printf("服务器:业务处理\n"); for(;;){ //接受客户端发来小写的串 char buf[64] = {}; struct sockaddr_in cli;//用来输出对方的地址结构 socklen_t len = sizeof(cli);//用来输出对方的地址结构大小 ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&cli,&len); if(size == -1){ perror("recvfrom"); return -1; } //转大写 for(int i = 0;i < strlen(buf);i++){ buf[i] = toupper(buf[i]); } //回传给客户端 if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&cli,len) == -1){ perror("sendto"); return -1; } } printf("服务器:关闭套接字\n"); close(sockfd); return 0; }udpcli.c
//udp的客户端,自己完成~~~ #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> int main(void){ //创建套接字 printf("客户端:创建套接字\n"); int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(sockfd == -1){ perror("socket"); return -1; } printf("客户端:组织服务器地址结构\n"); struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(9999); ser.sin_addr.s_addr = inet_addr("192.168.222.136"); printf("客户端:业务处理\n"); for(;;){ //通过键盘获取小写的串 char buf[64] = {}; fgets(buf,sizeof(buf),stdin); //!退出 if(strcmp(buf,"!\n") == 0){ break; } //发给服务器 if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&ser,sizeof(ser)) == -1){ perror("sendto"); return -1; } //接受服务器回传的串 if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){ perror("recv"); return -1; } //显示 printf("%s",buf); } printf("客户端:关闭套接字\n"); close(sockfd); return 0; }
域名解析
-
#include<netdb.h>
-
struct hostent* gethostbyname(char const* host_name)
- 功能:通过参数所传的主机域名,获取主机信息
- 参数:主机域名
- 返回值:函数执行成功返回表示主机信息的结构体指针,失败返回NULL
- 注意:该函数需要在联网情况下使用
-
struct hostent{ char *h_name;//主机官方名 char **h_aliases;//主机别名表 int h_addrtype;//地址类型 int h_length;//地址长度 char **h_addr_list;//IP地址表 } -
示例
//域名解析 #include<stdio.h> #include<unistd.h> #include<netdb.h> #include<arpa/inet.h> int main(int argc,char* argv[]){ // ./dns www.baidu.com struct hostent* h = gethostbyname(argv[1]); if(h == NULL){ perror("gethostbyname"); return -1; } printf("主机官方名:\n"); printf("%s\n",h->h_name); printf("主机别名表:\n"); for(char** pp = h->h_aliases;*pp;pp++){ printf("%s\n",*pp); } printf("IP地址表:\n"); for(struct in_addr** pp = (struct in_addr**)h->h_addr_list;*pp;pp++){ printf("%s\n",inet_ntoa(**pp)); } return 0; }
HTTP协议
-
示例
//http协议 #include<stdio.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<sys/types.h> #include<arpa/inet.h> int main(void){ //套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd == -1){ perror("socket"); return -1; } //百度地址结构 struct sockaddr_in ser; ser.sin_family = AF_INET; ser.sin_port = htons(80); ser.sin_addr.s_addr = inet_addr("220.181.38.149"); //发起连接 if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){ perror("connect"); return -1; } //发送请求 char request[1024] = {}; sprintf(request,"GET / HTTP/1.1\r\n" "Host: www.baidu.com\r\n" "Accept: */*\r\n" "Connection: close\r\n\r\n"); if(send(sockfd,request,strlen(request),0) == -1){ perror("send"); return -1; } //接收响应 for(;;){ char respond[1024] = {}; ssize_t size = recv(sockfd,respond,sizeof(respond)-1,0); if(size == -1){ perror("recv"); return -1; } if(size == 0){ break; } printf("%s",respond); } printf("\n"); //关闭套接字 close(sockfd); return 0; }
线程
POSIX线程
-
#include<pthread.h>
-
int pthread_create(pthread_t* tid,pthread_attr_t const* attr,void* (*start_toutine)(void*),void* arg)
- 功能:创建新线程
- 参数:输出线程ID,pthread_t即unsigned long int;线程属性,NULL表示缺省属性;线程过程函数指针,所指向的函数将在被创建的线程中执行;传递给线程过程函数的参数
- 返回值:成功返回0,失败返回错误码
-
线程过程函数
- void thread_proc(void* arg){…}
- 定义线程过程函数,该函数会在所创建的线程中执行,代表了线程的任务
- main函数其实就是主线程的线程过程函数
- main函数一旦返回,主线程即借书。主线程一旦结束,进程即结束。进程一旦结束,其所有的子线程统统结束
-
示例
gcc create.c -o create -lpthread
//线程的创建 #include<stdio.h> #include<string.h> #include<pthread.h>//pthread_create #include<unistd.h> //线程过程函数 void* pthread_fun(void* arg){ // arg ---> "hello" printf("%lu线程:我是子线程\n",pthread_self()); printf("%lu线程: %s\n",pthread_self(),(char*)arg); return NULL; } int main(void){ printf("%lu线程:我是主线程\n",pthread_self()); pthread_t tid;//用来输出线程的ID int error = pthread_create(&tid,NULL,pthread_fun,"hello"); if(error){ fprintf(stderr,"pthread_create:%s\n",strerror(error)); return -1; } sleep(1);//延时 return 0; } -
线程参数示例,主线程可通过数据区的全局变量拿到结果,也可通过传入参数拿到结果
//线程参数 #include<stdio.h> #include<unistd.h> #include<pthread.h> #define PI 3.14 double s = 0;//数据区 //线程过程函数,计算圆的面积 void* area(void* arg){ // arg ===> &r double r = *(double*)arg; //s = PI * r * r; *(double*)arg = PI * r * r; return NULL; } int main(void){ double r = 10; pthread_t tid;//用来输出线程的ID pthread_create(&tid,NULL,area,&r); sleep(1); //printf("圆的面积是%lg\n",s); printf("圆的面积是%lg\n",r); return 0; }
汇合线程
-
#include<pthread.h>
-
int pthread_join(pthread_t tid,void** retval)
- 功能:等待子线程终止,即其线程过程函数返回,并与之 汇合,同时回收该线程的资源
- 参数:线程ID;输出线程过程函数的返回值
- 返回值:成功返回0,失败返回错误码
-
示例
//pthread_join函数 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> #define PI 3.14 double s = 0;//数据区 //线程过程函数,计算圆的面积 void* area(void* arg){ // arg ===> &r double r = *(double*)arg; //static double s; //s = PI * r * r; //return &s; double* s = malloc(sizeof(double)); *s = PI * r * r; return s; } int main(void){ double r = 10; pthread_t tid;//用来输出线程的ID pthread_create(&tid,NULL,area,&r); double r2 = 20; pthread_t tid2; pthread_create(&tid2,NULL,area,&r2); double* p;//用来输出线程的返回结果 pthread_join(tid,(void**)&p);//该函数结束后, p ---> &s printf("圆的面积是%lg\n",*p); free(p); double* p2; pthread_join(tid2,(void**)&p2); printf("圆的面积是%lg\n",*p2); free(p2); return 0; }
分离线程
-
一个线程在默认情况下都是可汇合线程,这样的线程在终止以后其资源会被保留,其中含有线程过程函数的返回值及线程ID等信息,父线程可以通过pthread_join函数获得线程过程函数的返回值,同时释放这些资源。如果在创建线程以后,对其调用pthread_detach函数并返回成功,该线程即成为分离线程,这样的线程终止以后其资源会被系统自动回收,不需要也无法通过pthread_join函数汇合它
-
#include<pthread.h>
-
int pthread_detach(pthread_t tid)
- 功能:使指定的线程进入分离状态
- 参数:线程的ID
- 返回值:成功返回0,失败返回错误码
-
示例
//分离线程演示 #include<stdio.h> #include<string.h> #include<unistd.h> #include<pthread.h> //线程过程函数 void* pthread_fun(void* arg){ for(int i = 0;i < 100;i++){ putchar('-');//getchar(); usleep(50000); } return NULL; } int main(void){ setbuf(stdout,NULL);//关闭输出缓冲区 pthread_t tid; pthread_create(&tid,NULL,pthread_fun,NULL); //设置分离线程 pthread_detach(tid); int error = pthread_join(tid,NULL); if(error){ fprintf(stderr,"pthread_join:%s\n",strerror(error)); return -1; } for(int i = 0;i < 100;i++){ putchar('+'); usleep(100000); } return 0; }
线程ID
-
pthread_t类型的线程ID是POSIX线程库内部维护的线程的唯一标识,通常表现为一个很大的整数,跨平台,可移植
- #include<pthread.h>
- pthread_t pthread_self(void);
- 返回调用线程的(POSIX线程库的)TID
-
syscall(SYS_gettid)函数返回是一个long int类型整数,是系统内核产生线程唯一标识。一个进程的PID其实就是它的主线程的TID
- #include<sys/syscall.h>
- long int syscall(SYS_gettid)
- 返回调用线程的(系统内核的)TID
-
pthread_t类型在不同系统会被实现为不同的数据类型,甚至可能会使用结构体。因此判断两个线程ID是否相等或不相等,最好不要使用"= ="或”!="运算符,因为这些关系运算符只能用于C语言内置的简单类型,而对于结构类型的数据不适用。
- #include<pthread.h>
- int pthread_equal(pthread_t t1, pthread_t t2);
- 若两个参数所表示的TID相等返回非零,否则返回0
-
示例
//线程id #include<stdio.h> #include<pthread.h> #include<unistd.h> #include<sys/syscall.h>// syscall() //线程过程函数 void* pthread_fun(void* arg){ printf("子线程: PID : %d\n",getpid()); printf("子线程: POSIX TID : %lu\n",pthread_self()); printf("子线程: 内核 TID : %ld\n",syscall(SYS_gettid)); return NULL; } int main(void){ printf("主线程: PID : %d\n",getpid()); printf("主线程: POSIX TID : %lu\n",pthread_self()); printf("主线程: 内核 TID : %ld\n",syscall(SYS_gettid)); pthread_t tid; pthread_create(&tid,NULL,pthread_fun,NULL); pthread_join(tid,NULL); return 0; }
互斥锁
-
任何时候都只能一个线程持有互斥锁,即加锁成功,在其持有该互斥锁的过程中,其它线程对该锁的加锁动作都会引发阻塞,只有当持有互斥锁的线程主动解锁,那些在加锁动作上阻塞的线程中的一个采用恢复运行并加锁成功。
-
pthread_mutex_t表示互斥锁数据类型
-
#include<pthread.h>
-
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t const* attr)
- 功能:初始化互斥体
- 参数:互斥体;互斥体属性
- 返回值:成功返回0,失败返回错误码
- 也可静态方法初始化互斥锁:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
int pthread_mutex_destroy(pthread_mutex_t* mutex)
- 功能:销毁互斥体
- 参数:互斥体
- 返回值:成功返回0,失败返回错误码
-
int pthread_mutex_lock(pthread_mutex_t* mutex)
- 功能:锁定互斥体
- 参数:互斥体
- 返回值:成功返回0,失败返回错误码
-
int pthread_mutex_unlock(pthread_mutex_t* mutex)
- 功能:解锁互斥体
- 参数:互斥体
- 返回值:成功返回0,失败返回错误码
-
示例
//使用互斥锁解决并发冲突 #include<stdio.h> #include<pthread.h> int a = 0;//数据区 //互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //线程过程函数 void* add(void* arg){ //加锁 //pthread_mutex_lock(&mutex); for(int i = 0;i < 1000000;i++){ //加锁 pthread_mutex_lock(&mutex); a++; //解锁 pthread_mutex_unlock(&mutex); } //解锁 //pthread_mutex_unlock(&mutex); return NULL; } int main(void){ pthread_t t1,t2; pthread_create(&t1,NULL,add,NULL); pthread_create(&t2,NULL,add,NULL); pthread_join(t1,NULL); pthread_join(t2,NULL); printf("a = %d\n",a); return 0; }
条件变量
-
一个线程在某种条件不满足的情况下,无法进行后续工作,这时它就可以睡入某个条件变量,这时会有其它线程为其创建条件,一旦条件满足可以唤醒那些在相应条件变量中睡眠的线程继续运行
-
通过pthread_cond_t类型来表示条件变量
-
#include<pthread.h>
-
int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* attr)
- 功能:初始化条件变量
- 参数:条件变量;条件变量属性
- 返回值:成功返回0,失败返回错误码
- 也可静态方式初始化条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER
-
int pthread_cond_destroy(pthread_cond_t* cond)
- 功能:销毁条件变量
- 参数:条件变量
- 返回值:成功返回0,失败返回错误码
-
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex)
- 功能:睡入条件变量
- 参数:条件变量;互斥锁
- 返回值:成功返回0,失败返回错误码
-
int pthread_cond_signal(pthread_cond_t* cond)
- 功能:唤醒在条件变量中睡眠的一个线程
- 参数:条件变量
- 返回值:成功返回0,失败返回错误码
-
示例
//通过条件变量解决生产者消费者问题 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> char g_storage[10];//仓库 int g_stock = 0;//库存量 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁 pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;//生产者卧室 pthread_cond_t ccond = PTHREAD_COND_INITIALIZER;//消费者卧室 //显示生寒和消费的过程 生产者:ABC<---X 消费者:ABC--->X void show(char* who,char* op,char prod){ printf("%s:",who); for(int i = 0;i < g_stock;i++){ printf("%c",g_storage[i]); } printf("%s%c\n",op,prod); } //生产者线程 void* producer(void* arg){ char* who = (char*)arg; for(;;){ //加锁 pthread_mutex_lock(&mutex); //判满 if(g_stock == 10){ printf("%s:满仓\n",who); pthread_cond_wait(&pcond,&mutex); } //生产 char prod = 'A' + rand() % 26; show(who,"<---",prod); g_storage[g_stock] = prod; g_stock++; //唤醒消费者 pthread_cond_signal(&ccond); //解锁 pthread_mutex_unlock(&mutex); usleep((rand() % 100) * 1000); } return NULL; } //消费者线程 void* consumer(void* arg){ char* who = (char*)arg; for(;;){ //加锁 pthread_mutex_lock(&mutex); //判空 if(g_stock == 0){ printf("%s:空仓\n",who); pthread_cond_wait(&ccond,&mutex); } //消费 char prod = g_storage[--g_stock]; show(who,"--->",prod); //唤醒生产者 pthread_cond_signal(&pcond); //解锁 pthread_mutex_unlock(&mutex); usleep((rand() % 100) * 1000); } return NULL; } int main(void){ //种种子 srand(getpid()); pthread_t t1,t2; pthread_create(&t1,NULL,producer,"生产者"); pthread_create(&t2,NULL,consumer,"消费者"); getchar(); return 0; }
该博客围绕C语言UC编程展开,涵盖初级和高级内容。初级部分介绍错误处理、静态库、动态库、内存、文件、进程、信号等;高级部分涉及管道、IPC对象、网络、线程等。详细讲解各部分函数的功能、参数和返回值,并给出示例。





