一、linux系统编程简介
1)好处:1)可以让我们的程序跑的更快,2)可以让我们的程序和远端的另一个程序进行通信或数据交换
2)包含内容:
文件IO:可以让我们程序中的数据可以持久化的保持在我们的电脑上
进程与线程:可以让我们的程序实现并发(跑的更快)
网络编程:可以实现网络中的两个设备能够进行通信
3)如何学
这个阶段的学习和C语言不一样,它不在讲语法,主要学的是应用,内容相对比较抽象
如何学好它:
1.知道学的这个知识点时做什么的,有什么特点:文件可以持久化的保持程序运行中数据
2.使用这个知识点 它的基本的使用流程:打开文件,读写文件,关闭文件
3.学会使用在知识点的流程中涉及的函数,我们在看到它后 能够使用它。
二、文件IO
1)什么是文件
文件就是存储在磁盘上的数据集合。
在linux中,一切都是文件,1)可以实现数据的保存 2) 文件是连接用户和设备之间的一个桥梁 /dev
2)如何来标识一个文件
从用户的角度,通过文件名来标识计算机中的一个文件,文件名:=文件路径+文件主名+扩展名
D:\2511\DAC_TEST\Drivers\STM32F4xx_HAL_Driver\Inc\aa.txt
文件名是给看用户看的,用来标识一个静态文件。
文件描述符是给进程看的,使用标识在一个进程中被打开的文件。
3)文件分类
普通文件-:记事本,源码文件,视频,音频文件,pdf,word,压缩包等
目录文件d:文件夹
链接文件l :快捷方式
管道文件p:用来存储进程间通信的数据
块设备文件b:硬盘,U盘
字符设备文件c:液晶屏文件,键盘,鼠标
套接字文件:用来存储进程间通信的数据
4)文件描述符
在一个进程中,每打开一个文件,系统内核会返回该文件的一个编号,这个编号就叫该文件的描述符,它是一个非负的正整数。
默认是1024.在同一个进程中,文件描述符是不能重复的。
5) 文件的操作流程
#include <unistd.h>
- 打开文件:
int open(const char *pathname, int flags); //打开一个已存在的文件
int open(const char *pathname, int flags, mode_t mode);//创建一个文件并打开
2.读写文件:
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
3.关闭文件:
#include <unistd.h>
6) 操作文件的属性数据
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
7)目录文件的操作
主要包括:创建目录,删除目录,遍历目录,,切换目录,获取当前的目录路径等
创建目录:int mkdir(const char *pathname, mode_t mode); 或者system("mkdir yang");
删除目录:int rmdir(const char *pathname);
遍历目录:
DIR *opendir(const char *name);打开目录
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
切换目录:int closedir(DIR *dirp);
获取当前目录: char *getcwd(char *buf, size_t size);
检测当前文件是否存在:int access(const char *pathname, int mode);
8)其他函数:
int fcntl(int fd, int cmd, ... /* arg */ );
9)系统IO和标准IO的区别
系统IO主要应用在特定的平台上开发程序
标准IO可以跨平台使用
============================================================================================
三、进程与线程
3.1 进程简介
1)什么是进程
进程就是一个正在运行的程序以及它所依赖资源的总称,它是系统进程资源分配的基本单位。 ***
进程与程序的关系
程序是进程的执行文本,进程是程序的执行过程。 一个程序可以对应多个进程,一个进程对应一个程序。
2)如何查看进程
linux: ps -e 或者top
windows:任务管理器
3)如何标识一个进程
在计算机中,每启动一个进程,系统会给改进程分配一个id号(>=0 0~65535),进程描述符。
获取当前进程的id号:getpid()
获取当前进程父进程的id: getppid()
0~2这三个号时特殊的进程ID,一般不要关闭。
- 进程控制块PCB
系统每启动一个进程,内核都会给该进程在内核中创建一个结构体来描述该进程,系统通过这个结构体来感知进程的存在。
5)进程特点
1.独立性
2.并发性
3.异步性
4.动态性
6)进程的状态
就绪:除了cpu以外资源没有获得的进程
运行:进程获得了CPU资源
阻塞:进程需要等待外部的一个事件,如果等到了 就重新放到就绪队列。
7)创建一个进程
- fork(); //用来创建一个进程,用户调用一次,返会两次,在父进程空间返回子进程的ID号在子进程空间返回为0.
2.vfork();//x,父进程和子进程共享数据区
3.system();//多用来执行一个外部的命令。system("ls");,特点:就是会启动一个新的进程来执行你指定的命令程序,命令进程在没有执行完之前会阻塞当前进程
#include "unistd.h"
#include "stdio.h"
int main()
{
printf("start\n");
system("ping 127.0.0.1");
printf("end\n");
}
4)exec函数族 (夺舍函数)--将当前的进程的内存四区用你指定程序的内存四区对其做替换,相当于当前进程重获新生。
#include "unistd.h"
#include "stdio.h"
#include "sys/types.h"
int main()
{
printf("start\n");
execl("/bin/ls","ls","-l",NULL);
printf("sdfsdf\n"); //它不会被打印,因为被ls程序覆盖了当前进程的内存四区。
printf("end\n");
}
8)进程的等待
wait() //它会阻塞调用进程,知道等待的子进程结束 就会解除阻塞,还可以回收子进程在内核中所占的资源。
waitpid()
僵尸进程:子进程先于父进程结束,此时父进程没有调用wait函数,这样的子进程叫僵尸进程
孤儿进程:父进程先于子进程结束,此时子进程就变成了孤儿进程,最终孤儿进程被1号进程直接或间接收养。
9)进程的退出
分两种情况:
正常退出(在main函数中遇到return)
在程序的任意函数遇到了exit()或则_exit()
异常退出:abort();
3.2 进程间通信
1)目的:实现数据交换或者实现数据共享或者实现同步或控制。
2)进程间通信的本质:就是在参与通信的进程中,创建一个双方都能看得见的区域(内核)实现通信。
3)通信的方式:
1)管道通信
2)消息队列
3)信号
4)信号量
5)共享内存
6)socket
4)管道通信
作用:实现设备内进程之间的单向通信
特点: 半双工,适合小规模的数据传递,管道的声明周期跟随进程,它传递是是没有结构的数据流。
分类:有名管道和匿名管道。
匿名管道:只能在有亲缘关系的进程之间通信;它的数据存在于内核,不会在文件系统中体现出一个文件
匿名管道的使用流程:
1)创建管道:pipe();
2)读写管道:read()/write()
3)关闭管道close()
有名管道特点:可以在亲缘关系的进程之间和没有关系的亲缘之间都可以实现通信,会在文件系统中体现出一个文件。
流程:
1)先创建管道:int mkfifo(const char *pathname, mode_t mode);
2)打开管道文件:open()
3)读写管道:read()/write()
4)关闭管道close()
5)信号
作用:它也是进程间通信的一种方式,比较简单,它的本质是一个整数。0~63.
特点:通信简单,用户发送的信号是系统提前定义好的,不可以发送没有定义的信号,适合简单通信。
通信时,信息的发送方需要通过信号函数实现对指定信号的发送,常用的信号函数:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);//通用信号函数
sigqueue() //可以在发送信号的同时携带数据
unsigned int alarm(unsigned int seconds);//专用信号函数 SIGALRM
useconds_t ualarm(useconds_t usecs, useconds_t interval);//秒以下的可以考虑
信号的绑定
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);//实现指定信号与响应函数的绑定。
int sigaction(int signum, const struct sigaction *act, //主要对携带数据的信号进行绑定 该函数经常与 sigqueue()函数进行搭配
struct sigaction *oldact);
流程:
发送信号
绑定信号并响应信号
接收方收到信号之后 对信号的态度:
忽略信号
给信号绑定函数
按照信号默认行为(大部分信号会杀死接收进程)
6)共享内存
特点:适合进程间实现较大数据块传递,它的通信效率是最高的。
缺点:它缺少一种同步的机制,容易造成读写出错。往往可以使用信号量来解决。
使用共享内存进行进程间通信的基本流程:
1)通信双方的一方负责创建共享内存,另一方获取共享内存 (靠一个key值):shmget() IPC_CREAT
int shmget(key_t key, size_t size, int shmflg);//shmflg=IPC_CREAT表示创建
key_t key=ftok("/bin/ls",1);//通过ftok函数来生成KEY值,注意的是要确保通信双方的key一样,这样才能保证我们找的是内核中同一个内存块。
2)建立映射 (将内核中申请的内存块的首地址存放到当前的进程中):shmat();
3)读写共享内存:(和过去操作堆内存是一样):通过指针或内存的操作函数比如memcpy
-
断开映射:shmdt();
-
销毁共享内存:shmctl(shmid,IPC_RMID,NULL);
6)信号量
特点: 信号量的本质是一个计数器,是一个整数,它不是以传递数据为目的,而是给临界资源提供一种访问秩序。
信号量的操作:
P操作:申请资源,做减法 说名服务能力减弱了
V操作:释放资源 做加法,说明我的服务能力增强了
一般要成对出现。
信号量的使用流程:
1)创建信号量或获取信号量 //int a;--- semget() semflg=IPC_CREAT代表创建 没有代表获取
int semget(key_t key, int nsems, int semflg);//创建一个指定数量(nsems)元素的整数数组
key值也是有ftok()函数来生成
2)初始化信号量 //a=1;----semctl(); cmd=SETVAL 设置信号量的值
3)PV操作 //a++,a-- --semop()
4)销毁信号量 //释放变量a内存资源 // -semctl()
int semctl(int semid, int semnum, int cmd, ...);
cmd=IPC_RMID代表销毁
信号量的分类:
二元信号量:一般做互斥锁,实现互斥访问临界资源
非二元信号量 比如用在同时下载电影的数量方面,并发规模限制
7)消息队列
特点:1.发送的带结构的消息 2.接收方可以有选择的读取感兴趣消息。
场景:适合两个进程需要频繁进行数据交换的场景。
消息队列发送数据前,需要先封装消息
struct mymsg
{
long msgType;//消息类型
type msg_body;//消息体
};
消息队列的流程:
1)创建消息队列:msgget();
2)消息发送与接收//msgsnd/msgrecv()
nt msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
3)销毁消息队列
cmd=IPC_RMID代表销毁
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
8)套接字通信
特点:可以跨设备或主机实现两者之间的通信
分两种情况:基于TCP/ip 基于UDP/ip
分两个角色:服务器 客户端
TCP/ip 服务器的构建流程:
1)创建套接字 :socket(AF_INET,SOCK_STREAM,0);
2)绑定地址: bind();
3)监听:listen();
4)接收链接:accpet(serid,NULL,NULL);
5)读写数据:read/write()
6)关闭: close()
TCP/ip 客户端:
1)创建套接字:socket()
2)发送链接:connect()
3)收发数据:read/write()
4)关闭:close()
udp/ip 服务器的构建流程:
1)创建套接字socket(AF_INET,SOCK_DGRAM,0);
2)绑定地址:bind()
3)读写数据: :sendto/recvfrom()
4)关闭:close()
udP/ip 客户端:
1)创建套接字
2)收发数据
3)关闭
如何选择TCP/UDP?
1.如果看重可靠性 选择TCP,如果看重速度 选择UDP
3.3 线程
1)什么是线程thread**
线程就是进程中的一个执行流,同时它也是系统进行任务调度的最小单元。
2)线程与进程区别**
进程是线程的容器,一个进程中可以包含多线程
线程间通信比进程间通信更节省资源
同一个进程内的线程间通信比进程间通信更容易
线程更容易来发挥电脑的多核性能。
?协程
3) 包含pthread.h
4)线程的基本使用流程:
1.先定义线程的任务函数:void *task(void *arg){}
2.创建线程:pthread_create(&tid,NULL,task,NULL);
//形参1:做返回值,返回所建线程的ID号
形参2:设置线程的属性信息 ,比如分离属性 pthread_detach()
形参3:指定线程要干的活
形参4:线程的任务函数所需要的数据,如何没有数据提供够可以设置NULL
3.如何没有设置分离属性,pthread_join回收线程在内核所占的资源。(不是必须的)
5)线程退出
当线程函数遇到了最后一条语句就会退出
如果在线程函数中碰到: pthread_exit();
-
获取当前线程的id号: pthread_self();
-
如何来回收线程的资源:pthread_join() //阻塞主线程,实现父子线程同步;回收子线程的资源;获取子线程结束时的返回值。
8)线程取消:
发送线程的取消信号: pthread_cancel(线程ID);
设置取消的类型:pthread_setcanceltype
- 线程间的通信:全局变量或堆内存
10)线程间的互斥
线程的互斥锁
作用:当多个线程要操作一个公共的资源,需要加锁保护,否则容易将数据改乱。
流程:
1.定义互斥锁变量:pthread_mutex_t lock;
2.初始化锁变量:pthread_mutex_init();
3.使用锁变量:
pthraed_mutex_lock();
pthread_mutex_unlock();
4.销毁锁变量:pthread_mutex_destroy();
线程的读写锁: 在读的频次比写的频次高的场景 推荐使用
1.定义变量: pthread_rwlock_t lock
- 初始化锁变量 pthread_rwlock_init();
3.使用锁变量:
上读锁:pthread_rwlock_rdlock();
上写锁:pthread_rwlock_wrlock();
解锁pthread_rwlock_unlock();
4.销毁锁变量:pthread_rwlock_destroy();
11)线程间的同步
用全局变量实现同步,还可以用条件变量做
通过条件变量实现线程间的同步(构建多个线程执行的先后顺序)
基本流程:
1.定义条件变量:pthread_cond_t cond;
2.对条件变量做初始化:pthread_cond_init(); 注意:条件变量在使用时需要互斥锁的包含
3.使用条件变量: pthread_cond_wait();//条件变量等待
唤醒:pthread_cond_signal()
pthread_cond_broadcast();
4.销毁条件变量:pthread_cond_destroy();
五、网络编程
1)作用:主要解决设备之间的联网通信问题。
2)为了解决网络中不同设备间的通信问题,因此 有一些组织就制定了关于网络的各种协议。
代表:TCP,UDP,ip .
- ip协议
ip属于网络层的协议,它的职责是实现网络中不同节点之间的数据搬运。
ip协议中有一个重要的组成部分:ip地址,主要用来定位设备在网络中的位置。
ip协议:
IPV4协议 ipv6协议
192.168.157.1
fe80::cc39:137c:7c7f:168d
windows: ipconfig
linux: ifconfig
ip地址分:内网IP,公网IP
4)域名
域名的本质就是IP地址的别名 www.baidu.com
5)TCP协议
TCP协议是一种属于传输层协议它给数据传输提供一种保障机制。
TCP协议中有一个重要的内容:端口
端口:因为ip地址可以定位到网络中的某个计算机,但是这个计算机在某个时刻,上边跑了很多程序,网卡不知道将数据交
给那一个上层的应用程序,因此需要一个端口来指定。
端口:0~65535 查看端口:netstat
相同协议 端口不能一样,不同协议端口可以一样。 >=1024
8)udp协议
1.TCP与UDP协议的区别 ***
从可靠性方面:TCP比UDP可靠。
连接:TCP是面向连接的一种协议,UDP是面向无连接协议
TCP支持一对一通信,UDP支持 一对一,一对多通信,多对多。
9)网络字节序
主要规定数据在传输的时候 是先高位还是先发地位。
大端模式。
#include "arpa/inet.h"
htons();//将本机字节序转换成网络字节序
inet_addr();//可以将一个字符串形式的IP地址转换成网络字节序。
10)网络架构模型 ***
OSI七层模型: 应用层,表示层,会话层,传输层,网络层,数据链路层,物理层,
TCP/ip的四层模型:应用层,传输层,网络层,网络接口层
11)套接字socket
套接字就是对通信节点的抽象描述,一般有两部分组成:ip+端口
套接字类型:
流式套接字:SOCK_STREAM ---tcp
数据报套接字:SOCK_DGRAM---UDP
原始套接字:SOCK_RAM
12)TCP/ip通信
13)udp/ip通信
14)并发通信
多线程,多进程,IO复用
void * talk(void *arg)
{
int clifd=*((int*)arg);
while(1)
{
//聊天逻辑
read(clifd,buff,100);
}
}
int main()
{
int serfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(8000)
addr.sin_addr.s_addr=inet_addr("127.0.0.1");
bind(serfd,&addr);
listen(serfd,10);
while(1)
{
int clifd=accept(serfd,NULL,NULL);
pthread_create(&tid,NULL,talk,&clifd);
pthread_detach(tid);//设置
}
}
15)IO复用
1.什么IO复用
用单个线程或进程来监听多个文件描述符,一旦有描述符准备就绪 对其进行处理。
2.IO复用的技术
select poll epoll
3。实现IO复用的基本流程
1.创建套接字并绑定监听
2.创建一个容器来保存你要监控的文件描述符对象集合
3.将监听套接字添加到监控集合中
4.开始进行监控
5.如果是监听套接字有数据到来 调佣accept进行接收,如果不是监听套接字,则调用read()进行读取
6.关闭
man 7 epoll
#define MAX_EVENTS 10
struct epoll_event ev, eventsMAX_EVENTS;
int listen_sock, conn_sock, nfds, epollfd;
1.创建套接字并绑定监听
/* Code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted */
创建一个容器(红黑树)
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
将监听套接字添加到监控集合中
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
开始进行监控
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
如果是监听套接字有数据到来 调佣accept进行接收,如果不是监听套接字,则调用read()进行读取
for (n = 0; n < nfds; ++n) {
if (eventsn.data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);//设置客户端套接字为非阻塞模式 fcntl ioctl
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(eventsn.data.fd);//实现读取的一个函数
}
}
}