一、C语言学习心得记录

目录

  1. 函数递归
  2. 函数参数之数组指针传入

函数递归

编写顺序

  1. 终结条件
  2. 输入下一级递归参数,调用下一级递归函数.
  3. 当前递归函数的操作代码,在下一级递归函数执行完成后执行的操作代码.

函数参数之数组指针传入

知识点:

任何数组名,都可能有两个含义:例如 int a[3]; 1,代表整块数组:
a. 在定义语句中的时候: int a[3];
b. 在取址的时候:&a;
c. 在使用 sizeof 计算内存大小的时候:printf("%d\n", sizeof(a)); 2,除了上述情况之外,其他任何情况都代表首元素的地址

注意: 当数组作为函数参数传递的时候,表示是指针,不能用sizeof(名称),求出来是计算机指针字长。

知识点:

sumup(a, b, c, 4); // 等价于sumup(&a[0], &b[0], &c[0]);

知识点:三者等价

void sumup(int a[ ], int b[ ], int c[ ], int len)
void sumup(int *a, int *b, int *c, int len) // 本质形式
void sumup(int a[4], int b[4], int c[4], int len)

二维指针传入:代码

//函数外定义
int m, n;
int a[m][n];
int b[m][n];
copy(m, n, a, b);
copy(m, n, &a[0], b);
//函数定义
void copy(int m, int n, int (*a)[n], int b[m][n])

理解int (*a)[n]:

  1. (*a) *声明a是指针变量, (*a)括起来提前声明为指针.
  2. int [n] 声明a指针变量指向的类型,是int型的数组

C语言变量空间分布

在这里插入图片描述
在这里插入图片描述

枚举 enum

枚举类型 枚举常量列表
重要作用:
1,用有意义的单词,代替无意义的数字
2,限定整型的数值范围
3,如果没有对任何枚举常量赋值,默认从0开始递增
如果有赋值x,那么后续的枚举常量x开始递增
注意:
C语言只实现了枚举的部分特性,不包含以上第2条
因此在C语言中,枚举类型跟int型语法上完全一样
C++才真正实现了枚举

enum colors {red=0, blue=1, green=2};
colors =100;//可以这样无意义赋值

// C语言中,经常使用宏来定义常量,代替枚举
// 约定俗成的规矩: 宏用大写字母
// C++中,宏没落了,枚举有用正规的定义

#define RED 0
#define BLUE 1
#define GREEN 2

联合体 union

4.1 定义

联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。如下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把两者同时赋予它。联合类型的定义和联合变量的说明:一个联合类型必须经过定义之后,才能把变量说明为该联合类型。

联合体,也叫共用体
本质特征: 所有的成员,共用同一块内存
1,有什么用: 存储一些互斥的量
2,只有最后一个被赋值的成员是有效的
3,最常用的场合:
作为结构体的一员,表达一些互斥的信息
联合体很少单独使用

标签:
1,用来区分不同的联合体
2,可以省略,但省略之后只能在模板中定义变量
一般情况下,作为单独的联合体,不省略标签
当作为结构体的成员时,一般会省略标签

联合体的尺寸、m值:
1,联合的尺寸取决于最大的成员
2,联合的m值取决于成员中最大的m值

4.2联合体的应用示例

1、检测当前处理器是大端模式还是小端模式?
之前分享的《什么是大小端模式?》中已经有介绍怎么判断当前处理器的大小端问题:
在这里插入图片描述

现在,可以使用联合体来做判断:
在这里插入图片描述

2、分离高低字节
单片机中经常会遇见分离高低字节的操作,比如进行计时中断复位操作时往往会进行

(65535-200)/256,

(65535-200)%256

这样的操作,而一个除法消耗四个机器周期,取余也需要进行一系列复杂的运算,如果在短时间内需要进行很多次这样的运算无疑会给程序带来巨大的负担。其实进行这些操作的时候我们需要的仅仅是高低字节的数据分离而已,这样利用联合体我们很容易降低这部分开销。

代码:

union div
{
int n; // n中存放要进行分离高低字节的数据
char a[2]; // 在keil c中一个整形占两个字节,char占一个字节,所以n与数组a占的字节数相同
}test;
test.n = 65535-200; // 进行完这句后就一切ok了,下面通过访问test中数组a的数据来取出高低字节的数据
TH1 = test.a[0]; // test.a[0]中存储的是高位数据
TL1 = test.a[1]; // test.a[1]中储存了test.n的低位数据

联合体内数据是按地址对齐的。具体是高位数据还是低位数据要看平台的大小端模式,51是大端,stm32默认是小端,如果其他编译器还请自测。仅仅用了一条减法指令就达到了除法、取余的操作,在进行高频率定时时尤为有用。

3、寄存器封装
看看TI固件库中寄存器是怎么封装的:在这里插入图片描述在这里插入图片描述
所有的寄存器被封装成联合体类型的,联合体里边的成员是一个32bit的整数及一个结构体,该结构体以位域的形式体现。这样就可以达到直接操控寄存器的某些位了。比如,我们要设置PA0引脚的GPAQSEL1寄存器的[1:0]两位都为1,则我们只操控两个bit就可以很方便的这么设置:

GpioCtrlRegs.GPAQSEL1.bit.GPIO0 = 3

或者直接操控整个寄存器:

GpioCtrlRegs.GPAQSEL1.all |=0x03 

结构体 struct

知识点

64位系统: 字长64位
即意味着:
1,long型数据是64位的
2,任何指针尺寸也都是64位的

任何一个变量都有所谓的m值,对此,约定:
1,m值代表该变量的地址,必须是m的整数倍
2,m值代表改变量的尺寸,必须是m的整数倍

另外,m值越大,代表该变量对位置的要求越高,越挑剔
另外,m值是可以调整的,当然只能调大不能调小
另外,数组的m值等于数组元素类型的m值

// 以下结构体的大小是4+1+1+2 = 8字节
// 其中,第二个1是系统为了地址对齐自动填补上去的
struct node1
{int a;  // 4,m1=4char c; // 1, m2=1short d;// 2, m3=2
}; // M=max{m1, m2, m3}=4
//以下结构体的大小是32+32 = 64字节
struct node2
{char c; // 大小为1字节, m2=1int a;  // 大小为4字节,m1=4// 大小为2字节, m3=32short d __attribute__((aligned(32)));}; // M=max{m1, m2, m3}=32

解释:
第二个 char c 的地址作为结构体的首地址,所以取3者最大的m值32,后续int a可以放大char c的后面,所以int a和char c的大小加起来一起为32字节.

宏定义 define

// 复合赋值语句: ({…})
// 常用语宏中,将多语句合并成一条语句
// 以下的斜杠,用来将多个物理行,变成一个逻辑行
// (void)(&_x==&_y); 的作用:
// 1,当用户使用两个不同的类型进行比对的时候,给出警告
// 2,void的作用是骗过编译器,不对后续比对本身给出警告

#define MAX(x, y)  \({ \typeof(x) _x = x; \typeof(y) _y = y; \(void)(&_x==&_y); \_x>_y ? _x : _y; \})

头文件

// 仔细分析,头文件可以包含如下内容:
// 0,其他头文件
// 1,普通函数的声明
// 2,宏定义
// 3,全局变量的声明
// 4,静态函数的定义

头文件定义

// 以下两句话,能防止头文件被重复包含
#ifndef __HEADER_H // 如果没有定义该宏,则编译以下内容
#define __HEADER_H // 立刻定义该宏,使得下一次的判断不成立

静态函数

// 静态函数的定义
// 普通函数:在所有的.c文件中都可见
// 静态函数:只在.c文件内部可见

static void static_func(void)
{printf("我是一个静态函数\n");
}

因为只在.c文件内部可见,所以每个.c文件都可以有static_func(void)函数。
猜测效果:不同c文件有相同的函数名,但是不同的函数操作,是否可以实现类似于c++的多态

在这里插入图片描述

const只读变量

const int a =1 / int const a=1 变量a的值不能改变
const int *a=&b/ int const *a=&b (常目标指针)指针变量a指向的值不能改变,b可以改变内容(常用)
int * const a=&b (常指针)指针的指向不能改变
int const *const a=&b 指针指向不能改变,指向的值也不能改变

理解:代码从右往左看,const靠近的那个变量就修饰为只读。
int const *a=&b *a等于是引用变量,const把引用变量的方式修饰为只读 int声明为整数型,放前放后都可以
int * const a=&b a为地址值,指向地址只读=指针变量指向不可改变

判断系统是小端序还是大端序

大端模式:高字节保存在内存的低地址
小端模式:高字节保存在内存的高地址
int–char的强制转换,是将低地址的数值截断赋给char

int a = 0x12345678;
int b= 0x87654321;
char *p_a = (char*)&a;
char *p_b = (char*)&b;
char *p_a_tail = ((char*)&a)+3;
printf("address of a:%p\r\n",&a);
printf("address of b:%p\r\n",&b);
printf("p_a:%x\r\n",*p_a);
printf("p_b:%x\r\n",*p_b);
printf("address of p_a:%p\r\n",p_a);
printf("address of p_a_tail:%p\r\n",p_a_tail);
printf("p_a = %x\r\n",*p_a);
printf("p_a_tail = %x\r\n",*p_a_tail);
printf("address of p_b:%p\r\n",p_b);;
if (*p_a == 0x78)
{printf("小端序");
}
else if (*p_a == 0x12)
{printf("大端序");
}

未解决:在栈中地址是如何分布的

linux

address of a:0x7fffad867070
address of b:0x7fffad867074
p_a:78
p_b:21
address of p_a:0x7fffad867070
address of p_b:0x7fffad867074
little endian

address of a:0x7ffd11df2728
address of b:0x7ffd11df272c
p_a:78
p_b:21
address of p_a:0x7ffd11df2728
address of p_a_tail:0x7ffd11df272b
p_a = 78
p_a_tail = 12
address of p_b:0x7ffd11df272c
little endian

windows

address of a:0060FEF4
address of b:0060FEF0
p_a:78
p_b:21
address of p_a:0060FEF4
address of p_b:0060FEF0
little endian

address of a:0060FEE0
address of b:0060FEDC
p_a:78
p_b:21
address of p_a:0060FEE0
address of p_a_tail:0060FEE3
p_a = 78
p_a_tail = 12
address of p_b:0060FEDC
little endian
在这里插入图片描述

C语言、内存管理、堆、栈、动态分配

栈是由高地址向低地址扩展的数据结构,有先进后出的特点,即依次定义两个局部变量,首先定义的变量的地址是高地址,其次变量的地址是低地址。函数参数进栈的顺序是从右向左(主要是为了支持可变长参数形式)。

理解 类似于函数嵌套进行运行,第一个函数运行,里面的第二个函数调用在函数基础上再运行;退出也是先退出第二个函数,再退出第一个函数

https://blog.csdn.net/weixin_39371711/article/details/81783780

字符指针数组

char *a[ ]=(Morning", "Afternoon", "Evening","Night");

字符数组引用

c i[s]等价于*(i+s)等价于*(s+i)等价于s[i]

printf("%c","xyz"[1]);//'y'
printf("%c",*("xyz"+1));//'y'
printf("%c",*(1+"xyz"));//'y'
printf("%c",1["xyz"]);//'y'

习惯把"xyz"[1]看成*("xyz"+1)来思考问题,本质就是字符指针+1,再加*取内容运算符进行取内容操作。

switch区间匹配

switch(tmp)
{
case 0 … 9 :
tmp += tmp + ‘0’;
break;
case 10 … 15 :
tmp += tmp + ‘A’;
break;
}

switch参数宏定义(方便查看)

#define LEFT 1
#define RIGHT 2
switch (touchpanel(tp)){case LEFT://...break;case RIGHT://...break;}

linux C中的__ASSEMBLY__的作用

转载地址:http://my.oschina.net/u/930588/blog/134751

某些常量宏会同时出现被c和asm引用,而c与asm在对立即数符号的处理上是不同的。asm中通过指令来区分其操作数是有符号还是无符号,而不是通过操作数。而c中是通过变量的属性,而不是通过操作符。c中如要指名常量有无符号,必须为常量添加后缀,而asm则通过使用不同的指令来指明。如此,当一个常量被c和asm同时包含时,必须作不同的处理。故AFLAGS中将添加一项D__ASSEMBLY__,来告知预处理器此时是asm。

下面的代码取自kernel 2.6.10,include/asm-i386/page.h,L123-127

#ifdef __ASSEMBLY__
#define __PAGE_OFFSET        (0xC0000000)
#else
#define __PAGE_OFFSET        (0xC0000000UL)
#endif
 类似的大家也可以去分析一下__KERNEL__的作用.

逻辑左移和算术右移

C语言中移位操作符实现的是逻辑左移和算术右移,但是算术左移和逻辑左移的效果相同,算术右移和逻辑右移的效果不同,要实现逻辑右移可将操作数强制类型转化为无符号数

数组方式访问地址偏移

C51 宏定义:
#define XBYTE ((unsigned char volatile xdata *) 0)
调用方法: 
#define PA XBYTE[0x7cff]
PA = 0X80;

通常是这么用的: *(unsigned char volatile xdata )(0x3000)=0xFF这类的方式来进行对外部绝对地址的字节访问。
其实XBYTE就相当于一个指向外部数据区的无符号字符型变量的指针(的名称,且当前的指针指向外部RAM的0地址),而在C里面指针一般和数组是可以混用的。可以直接用XBYTE[0xnnnn]或
(XBYTE+0xnnnn)访问外部RAM了。

等于使用[]的寻址方法,调用从0偏移0x7cff个8位char型地址进行写值操作

二、Linux系统命令

1.linux终端的复制和粘贴

在终端选中文字就是复制,右键文字就是粘贴

2.linux终端的复制和粘贴

关于环境变量
查看所有的环境变量
ubuntu:~$ env

查看单个指定的环境变量
ubuntu:~$ echo $PATH

知识点:环境变量 PATH 的作用是:规定系统中的可执行文件的位置。只要是处于这些位置中的可执行文件,执行的时候就不需要指定路径,直接执行即可。

设置 PATH(在其原有的路径的基础上,增添一个路径/home/gec)
// 临时设定 PATH :(所谓临时,指的是关闭终端之后就失效)
gec@ubuntu:~$ PATH=$PATH:/home/gec

// 永久设定 PATH :
将语句 PATH=$PATH:/home/gec 添加到文件 ~/.bashrc 的末尾

知识点:终端tty也是可以添加变量的。
所以可以临时设定PATH

ubuntu:~$ a = apple
ubuntu:~$ echo $a
apple

3.linux网络设置

设置网络参数

配置文件:/etc/network/interfaces,有两种配置方法:

固定IP:

# interfaces(5) file used by ifup(8) and ifdown(8) 
auto lo 
iface lo inet loopbackauto ens33 
iface ens33 inet static 
address 169.254.54.200 # IP地址,根据具体的网络环境来写 
netmask 255.255.0.0    # 子网掩码 
gateway 169.254.54.1  #网关地址
  1. 动态IP(自动获取IP)
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopbackauto ens33
iface ens33 inet dhcp

将static改成dhcp,就由原来的静态固定IP改成动态自动获取IP。

重新加载网络配置和重启网络服务

gec@ubuntu:~$ sudo service networking force-reload
gec@ubuntu:~$ sudo service networking restart
    注意:老版本的Ubuntu可能不支持以上命令,可以试试下面这个
gec@ubuntu:~$ sudo /etc/init.d/networking force-reload
gec@ubuntu:~$ sudo /etc/init.d/networking restart

测试网络是否连通(ping)

gec@ubuntu:~$ ping www.qq.com
PING public-v6.sparta.mig.tencent-cloud.net (14.18.175.154) 56(84) bytes of data.
64 bytes from 14.18.175.154 (14.18.175.154): icmp_seq=1 ttl=52 time=12.0 ms
64 bytes from 14.18.175.154 (14.18.175.154): icmp_seq=2 ttl=52 time=11.7 ms
64 bytes from 14.18.175.154 (14.18.175.154): icmp_seq=3 ttl=52 time=10.8 ms
64 bytes from 14.18.175.154 (14.18.175.154): icmp_seq=4 ttl=52 time=11.8 ms
注:只要有返回延时时间,就代表网络是通的;如果卡主不动,代表网络不通或者网络拥塞

查看指定的网址的IP地址:

gec@ubuntu:~$ host www.qq.com
www.qq.com is an alias for public-v6.sparta.mig.tencent-cloud.net.
public-v6.sparta.mig.tencent-cloud.net has address 14.18.175.154
public-v6.sparta.mig.tencent-cloud.net has address 113.96.232.215
public-v6.sparta.mig.tencent-cloud.net has IPv6 address 240e:ff:f101:10::15f
注:
host成功返回域名的IP地址,代表当前网络是通的。
host成功返回域名的IP地址,代表当前系统的DNS解析是正常的。
DNS解析,就是通过域名,查询其对应的IP
如果ping成功,但host不成功,代表当前电脑的DNS配置有问题,解决办法:
gec@ubuntu:~$ sudo vi /etc/resolv.conf

在以上文件中,添加如下信息:

nameserver x.x.x.x(可以上百度搜索,选择离你家最近的)
gec@ubuntu:~$ sudo service systemd-resolved restart
以上命令用来重启 DNS 服务

查看或修改网络接口配置信息:(ifconfig)

查看当前活跃的网络接口

gec@ubuntu:~$ ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500inet 192.168.1.103  netmask 255.255.255.0  broadcast 192.168.1.255inet6 fe80::20c:29ff:fe80:949c  prefixlen 64  scopeid 0x20<link>ether 00:0c:29:80:94:9c  txqueuelen 1000  (Ethernet)RX packets 2020  bytes 266623 (266.6 KB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 8299  bytes 548748 (548.7 KB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536inet 127.0.0.1  netmask 255.0.0.0inet6 ::1  prefixlen 128  scopeid 0x10<host>loop  txqueuelen 1000  (Local Loopback)RX packets 37191  bytes 2722682 (2.7 MB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 37191  bytes 2722682 (2.7 MB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    注:其中,ens33是当前虚拟机的虚拟网卡lo是Linux系统的本地回环设备,一般不用管它

启停指定的网络接口(网卡)

gec@ubuntu:~$ sudo ifconfig ens33 up   ==> 启用ens33
gec@ubuntu:~$ sudo ifconfig ens33 down ==> 停用ens33以上命令也可以用如下命令替代,注意:有些系统不支持
gec@ubuntu:~$ sudo ifup ens33          ==> 启用ens33
gec@ubuntu:~$ sudo ifdown ens33        ==> 停用ens33

临时修改指定的网络接口的IP地址(即重启后失效)

gec@ubuntu:~$ sudo ifconfig ens33 192.168.1.103

查看当前网络状态:查看各种协议的网络信息

gec@ubuntu:~$ netstat

重新启动Ubuntu:

  sudo shutdown -r now

5.kill

向指定的一堆进程发送信号:(killall)

gec@ubuntu:~$ killall while       ==> 杀死当前系统中所有名为while的进程

6.find xargs grep

在当前目录下所有.cpp文件中查找efg函数
find . -name “*.cpp” | xargs grep ‘efg’
xargs展开find获得的结果,使其作为grep的参数

另外 rm mv等命令对大量文件操作时报错 -bash: /bin/rm: Argument list too long也可用xargs 解决
删除当前目录下所有.cpp文件
find . -name “*.cpp” | xargs rm

下面用find和grep来进一步说明:
// 将find找到的*.h文件列表的名称直接作为grep要查找的数据(输入)

gec@ubuntu:~$ find . -name "*.h" | grep 'abc'

// 将find找到的*.h文件列表的名称直接作为grep要查找的文件(参数)

gec@ubuntu:~$ find . -name "*.h" |xargs grep 'abc'
注:
1.find命令是把找到的结果输出到标准终端输出流中,管道改变输出流指向为指向grep的输入流。sort排序指令,把文件的文本内容提取出来输出缓冲区,清空文件;在缓冲区排序后,输出到文件,并且输出到标准终端输出流(方便配合管道指令)。
2. 第一种就是.h就是文本输入,第二种.h的文字段识别为文件,然后打开文件进行操作(或者说每行的内容识别成一个文件,把他打开再进行搜索)

7.查看文本内容

gec@ubuntu:~$ cat a.txt  ==> 查看 a.txt 的内容
gec@ubuntu:~$ head -n 20  a.txt  ==> 查看 a.txt 的前20行的内容
gec@ubuntu:~$ tail -n 20  a.txt  ==> 查看 a.txt 的末20行的内容
gec@ubuntu:~$ less a.txt  ==> 分屏查看 a.txt 的内容
gec@ubuntu:~$ more a.txt  ==> 分屏查看 a.txt 的内容(推荐?)
gec@ubuntu:~$ od a    ==> 查看二进制 a 的内容(默认八进制)
gec@ubuntu:~$ od -d a ==> 查看二进制 a 的内容(以十进制)
gec@ubuntu:~$ od -x a ==> 查看二进制 a 的内容(以十六进制)

8.od查看二进制文件

查看二进制文件,用od或hexdump命令。

$ od -tx1 -tc -Ax binFile
000000  61  62  63  64  65  31  32  33  34  35  61  62  63  64  65  31a   b   c   d   e   1   2   3   4   5   a   b   c   d   e   1
000010  32  33  34  35  61  62  63  64  65  31  32  33  34  35  61  622   3   4   5   a   b   c   d   e   1   2   3   4   5   a   b
000020  63  64  65  31  32  33  34  35  0ac   d   e   1   2   3   4   5  \n
000029
-tx1选项表示将文件中的字节以十六进制的形式列出来,每组一个字节(类似hexdump的-c选项)-tc选项表示将文件中的ASCII码以字符形式列出来(和hexdump类似,输出结果最左边的一列是文件中的地址,默认以八进制显示)-Ax选项要求以十六进制显示文件中的地址

https://zhidao.baidu.com/question/1860941808766537387.html

9.将文件内容排序、去除文件中的重复的相邻行

sort / uniq
作用:将文件内容排序、去除文件中的重复的相邻行
用法:

gec@ubuntu:~$ sort file ==> 将文件file的内容排序输出
gec@ubuntu:~$ uniq file ==> 将文件file的重复相邻行删除后输出
gec@ubuntu:~$ sort file | uniq ==> 先排序,再去除重复行

10.管道命令

管道: |
作用:将不同的命令连接起来,将前面命令的输出,作为后面命令的输入或者参数
用法:

gec@ubuntu:~$ cmd1 |      cmd2 ==> 将cmd1的输出,作为cmd2的输入
gec@ubuntu:~$ cmd1 |xargs cmd2 ==> 将cmd1的输出,作为cmd2的参数

11. 显示ELF格式目标文件的信息(查看代码段)

readlf -S

11. 查看外网ip

curl icanhazip.com

12. 设置终端代理

export http_proxy="http://127.0.0.1:10808"
export https_proxy="http://127.0.0.1:10808"
export http_proxy="socks5://127.0.0.1:10800"
export https_proxy="socks5://127.0.0.1:10800"

13. 查看历史命令

history

三、文件IO

1.把一个文件的内容复制到另一个文件

注: read函数特性,无法确定的把100个字节全部读取进入字节流,所以需要while进行处理
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[]) // ./copy    a       b     ...// argv[0] argv[1] argv[2] ...// argc = 外部参数总数
{// 判断外部参数(包括程序本身)个数的合法性if(argc != 3){printf("参数错误!用法: <源文件> <目标文件>\n");exit(0);}// 1,打开你要操作的文件// 打开第一个参数指定的文件,并将模式指定为只读: O_RDONLY// 此种打开模式代表: 文件必须存在,否则报错int fd1 = open(argv[1], O_RDONLY); // aif(fd1 == -1){// 万一不幸打不开,输出失败的原因perror("打开源文件失败");exit(0);}// 打开第二个参数指定的文件,并将模式指定为只写: O_WRONLY// 另外://    O_CREAT代表如果文件不存在则创建文件,并将权限设置为0644//    O_TRUNC代表如果文件已存在则清空文件int fd2 = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); // bif(fd2 == -1){// 万一不幸打不开,输出失败的原因perror("打开目标文件失败");exit(0);}// 2,不断地读取源文件内容,并放入目标文件中char *buf = calloc(1, 100);while(1){// 从文件fd1中读取最多100个字节,放入buf中// read的返回值n,代表实际读取的字节数(n<=100)int n = read(fd1, buf, 100);// 读完了if(n == 0){printf("复制完毕!收工!\n");break;}// 出错了!if(n == -1){perror("读取源文件内容失败");break;}// 将buf中最多n个字节,写入fd2中// write的返回值m,代表实际写入的字节数(即m<=n)char *tmp = buf;while(n > 0){int m = write(fd2, tmp, n);n   -= m;tmp += m;}}// 3,关闭文件,释放相关资源close(fd1);close(fd2);free(buf);return 0;
}

2. 静态库、动态库的制作

静态库、动态库基本概念
静态库(相当于书店,东西只卖不借)
原理:编译时,库中的代码将会被直接复制一份到程序中
优点:程序不依赖于库、执行效率稍高
缺点:浪费存储空间、无法对用户升级迭代

动态库(相当于图书馆,东西只借不卖)
原理:编译时,程序仅确认库中功能模块的匹配关系,并未复制
缺点:程序依赖于库、执行效率稍低
优点:节省存储空间、方便对用户升级迭代

库文件的命名
都以 lib 作为前缀,比如 libx.a、liby.so
静态库的后缀为 .a ,动态库的后缀为 .so

静态库、动态库的制作和使用
不管是静态库还是动态库,其原料都是 *.o 文件
不管是静态库还是动态库,天生都是用来被其他程序链接的功能模块,一定不能包含main
编译生成 *.o 文件的方法如下:

gec@ubuntu:~$ gcc a.c -o a.o -c -fPIC

静态库的制作与使用:

// 将a.o b.o c.o ... 制作成一个静态库文件libx.a
gec@ubuntu:~$ ar rcs libx.a   a.o b.o c.o ...// 链接静态库libx.a
gec@ubuntu:~$ gcc main.c -o main -L . -lx
动态库的制作与使用:
// 将a.o b.o c.o ... 制作成一个动态库文件liby.so
gec@ubuntu:~$ gcc -shared -fPIC -o liby.so   a.o b.o c.o ...// 链接动态库liby.so
gec@ubuntu:~$ gcc main.c -o main -L . -ly

链接了动态库的程序怎么运行?
由于程序和动态库在编译之后,都可能会随着系统的变迁而发生位置改变,因此链接了动态库的程序在每次运行时都会动态地去寻找其所依赖的库文件,这是他们为什么被称为动态库的原因。
三种办法可以使得程序在运行时成功找到其所依赖的动态库:
设置环境变量:

gec@ubuntu:~$ export LD_LIBRARY_PATH=...

编译时预告:

gec@ubuntu:~$ gcc a.c -o a -L . -ly -Wl,-rpath=xxx/
注意:此处xxx/代表程序在运行时,动态库所在的路径

修改系统默认库路径(!!危险!!):

gec@ubuntu:~$ sudo vi /etc/ld.so.conf.d/libc.conf

在以上文件中,添加动态库所在路径即可。
此处要小心编辑,一旦写错可能会导致系统无法启动,这是一种污染系统的做法,不推荐

数据结构

内核链表

为什么内核链表是大结构体里面有小结构体链表+数据的形式?

理解:内核链表中的链表元素不与特定类型相关,具有通用性。如果是结构链表+结构数据,还需要指定结构体数据的地址,那就需要确认结构体数据的形式。而且在项目封装的时候,如果第二次定义结构链表+结构数据的时候,需要全部重新修改程序封装 。但是内核链表的小结构体则是直接脱离了本身,内核链表可以小结构体独立走,然后任意形式的大结构体包住小结构体即可实现携带传输。

利用小结构体获取大结构体的地址

/**
* list_entry – get the struct for this entry
* @ptr:    the &struct list_head pointer.
* @type:    the type of the struct this is embedded in.
* @member:    the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

理解 :
(char *)(ptr):使得指针的加减操作步长为一字节(理解:不然减偏移量的时候就是移动结构体指针)
(unsigned long)(&((type *)0)->member):获取结构体中指向的member地址与该member所在结构体的基地址的偏移量。


> 测试代码
typedef struct node
{int data; // 数据域 4 4+8+8struct list_head list; // 小结构体 16
}node;typedef struct node2
{struct list_head list; // 小结构体 16int data; // 数据域 4
}node2;
int main(void)
{node* a = (node*)malloc( sizeof(node) );if(a != NULL){// 数据域a->data = 123;// 小结构体要自己形成循环链表INIT_LIST_HEAD(&a->list);}node* p_a = ((node *)((char *)(&a->list)-(unsigned long)(&((node *)0)->list)));printf("address of a:%p\r\n",a);printf("address of data:%p\r\n",&a->data);printf("address of list:%p\r\n",&a->list);printf("address of (char *)&a->list:%p\r\n",(char *)&a->list);printf("address of ((node *)0)->list:%p\r\n",&((node *)0)->list);printf("(unsigned long)(&((node *)0)->list)=%ld\r\n",(unsigned long)(&((node *)0)->list));printf("address of (char *)(&a->list)-(unsigned long)(&((node *)0)->list:%p\r\n",\(char *)(&a->list)-(unsigned long)(&((node *)0)->list));printf("address of p_a:%p\r\n",p_a);printf("sizeof(a->data)=%ld\r\n",sizeof(a->data));printf("sizeof(a->list)=%ld\r\n",sizeof(a->list));node2* b = (node2*)malloc( sizeof(node2) );if(b != NULL){// 数据域b->data = 123;// 小结构体要自己形成循环链表INIT_LIST_HEAD(&b->list);}node2* p_b = ((node2 *)((char *)(&b->list)-(unsigned long)(&((node2 *)0)->list)));printf("address of b:%p\r\n",b);printf("address of data:%p\r\n",&b->data);printf("address of list:%p\r\n",&b->list);printf("address of (char *)b->list:%p\r\n",(char *)&b->list);printf("address of ((node2 *)0)->list:%p\r\n",&((node2 *)0)->list);printf("(unsigned long)(&((node2 *)0)->list)=%ld\r\n",(unsigned long)(&((node2 *)0)->list));printf("address of (char *)(&b->list)-(unsigned long)(&((node2 *)0)->list:%p\r\n",\(char *)(&b->list)-(unsigned long)(&((node2 *)0)->list));printf("address of p_b:%p\r\n",p_b);printf("sizeof(b->data)=%ld\r\n",sizeof(b->data));printf("sizeof(b->list)=%ld\r\n",sizeof(b->list));return 0;
}

结果
address of a:0x55ce33cc9260
address of data:0x55ce33cc9260
address of list:0x55ce33cc9268
address of (char *)&a->list:0x55ce33cc9268
address of ((node *)0)->list:0x8
(unsigned long)(&((node *)0)->list)=8
address of (char *)(&a->list)-(unsigned long)(&((node *)0)->list:0x55ce33cc9260
address of p_a:0x55ce33cc9260
sizeof(a->data)=4
sizeof(a->list)=16
address of b:0x55ce33cc9690
address of data:0x55ce33cc96a0
address of list:0x55ce33cc9690
address of (char *)b->list:0x55ce33cc9690
address of ((node2 *)0)->list:(nil)
(unsigned long)(&((node2 *)0)->list)=0
address of (char *)(&b->list)-(unsigned long)(&((node2 *)0)->list:0x55ce33cc9690
address of p_b:0x55ce33cc9690
sizeof(b->data)=4
sizeof(b->list)=16

在这里插入图片描述

四、linux系统文件操作(建立工程模板)

包含头文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>#include <linux/fb.h>
#include <linux/input.h>#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>

1.参数初始化(设计功能后,列一个特殊情况检查表格进行检查)

if(argc != 2){printf("参数错误,用法: <BMP图片>\n");exit(0);}// 判断命令行参数// 最终要显示的目标target,可能是当前文件夹(默认)// 也可能是用户指定的一个文件夹,或者图片char *target = ((argc==1) ? "." : argv[1]);struct stat info;bzero(&info, sizeof(info));stat(target, &info);if(S_ISREG(info.st_mode)){if(!isbmp(target)){printf("我只能显示BMP图片,再见!\n");exit(0);}struct node *picnode = new_node(target);display(picnode);}// 遍历文件夹else if(S_ISDIR(info.st_mode)){// 搞一个空链表struct node * head = init_list();DIR *dp = opendir(target);chdir(target);// 读取目录,将所有的BMP图片信息存入链表中(不读取RGB)struct dirent *ep;while(1){ep = readdir(dp);// 读完目录了if(ep == NULL)break;// 只挑BMP图片放入链表if(isbmp(ep->d_name)){printf("图片:[%s]入链表\n", ep->d_name);// 搞个新节点,并置入链表struct node * picnode = new_node(ep->d_name);head = list_add_tail(head, picnode);}}if(empty(head)){printf("你指定的目录中没有BMP图片,再见!\n");exit(0);}}elseprintf("我是相册程序,你指定的文件不是我的菜!\n");return 0;
}

1.1理解 argc argv参数之间的关系

在这里插入图片描述

2.打开文件

 int pic = open(argv[1], O_RDONLY);

3.读取图片的格式头,获取其各种信息

3.1 bmp图片的读取

    struct bitmap_header head;struct bitmap_info   info;bzero(&head, sizeof(head));bzero(&info, sizeof(info));read(pic, &head, sizeof(head));read(pic, &info, sizeof(info));

3.2 读取触摸屏的数据

struct input_event buf;int x, y;int xnew, ynew;bool xready = false;bool yready = false;while(1){bzero(&buf, sizeof(buf));read(tp, &buf, sizeof(buf));if(buf.type == EV_ABS && buf.code == ABS_X){xnew = buf.value;xready = true;}if(buf.type == EV_ABS && buf.code == ABS_Y){ynew = buf.value;yready = true;}if(xready && yready && (xnew!=x && ynew!=y)){printf("(%d, %d)\n", xnew, ynew);xready = false;yready = false;x = xnew;y = ynew;}}

4.映射LCD的显存(对应3.1图片读取)

int lcd = open("/dev/ubuntu_lcd", O_RDWR);
if(lcd == -1)
{perror("打开LCD失败");exit(0);
}
char *p = mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE,MAP_SHARED, lcd, 0);
if(p == MAP_FAILED)
{perror("映射内存失败");exit(0);
}
bzero(p, 800*480*4); // 清屏(纯黑色)

5. 将图片的RGB读出,并置入显存中

int rgb_size = head.size-sizeof(head)-sizeof(info);
char *rgb = calloc(1, rgb_size);// 将图片中的RGB放入rgb中
int total = 0;
while(1)
{int n = read(pic, rgb+total, rgb_size);if(n == 0)break;total += n;rgb_size -= n;
}// 计算每一行的无效字节数
int pad = (4 - ((info.width*3) % 4))%4; // 0-3// 将rgb指向最末一行的起始位置
rgb += (info.width*3 + pad) * (info.height-1);// 将太大的图片按比例缩小
int wt = info.width/801+1;
int ht = info.height/481+1;
int scale = wt>ht ? wt : ht; // >=1// 让图片居中显示
int x = (800-info.width/scale)/2;
int y = (480-info.height/scale)/2;
char *tmp = p + (y*800 + x)*4;// 将rgb中的数据,妥善地放入LCD显存
for(int j=0, m=0; j<480-y && m<info.height; j++, m+=scale)
{for(int i=0, k=0; i<800-x && k<info.width; i++, k+=scale){int lcd_offset = 4*i + 800*4*j;int rgb_offset = 3*k - (info.width*3+pad)*m;memcpy(tmp+lcd_offset, rgb+rgb_offset, 3);}
}

6.释放资源

munmap(p, 800*480*4);
close(lcd);
close(pic);

五、LINUX系统编程

1. 创建进程

#include "common.h"int main()
{pid_t pid = fork();if(pid > 0){printf("PID:%d, PPID:%d\n", getpid(), getppid());// 判断子进程的退出值int status;wait(&status);switch (WEXITSTATUS(status)){case 0:printf("子进程成功执行了任务!\n");break;case 1:printf("子进程任务失败:xxx\n");break;case 2:printf("子进程任务失败:yyy\n");break;default:printf("子进程任务失败:不明原因\n");break;}}if(pid == 0){// exec函数族// 功能: 让进程加载一段新的代码(旧瓶装新酒),覆盖原有的代码// 其中://   "./childProcess" 是要加载的程序//   "./childProcess", "ab", "12", NULL 是程序的参数列表
#if 0printf("%d\n", __LINE__);execl("./childProcess", "./childProcess", "ab", "12", NULL);printf("%d\n", __LINE__);
#endif#if 1// 没有获取环境变量PATH的值execl("/bin/ls", "/bin/ls", NULL);
#else// 从终端获取了环境变量PATH的值execlp("ls", "ls", NULL);
#endif}return 0;
}

理解:C程序调用shell脚本共有三种法子:system()、popen()、exec系列函数 。
system()不用你自己去产生进程,它已经封装了,直接加入自己的命令;exec 需要你自己 fork 进程,然后exec
自己的命令;popen()也可以实现执行你的命令,比system 开销小。

execl函数特点:: 当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

用另一个新程序替换了当前进程的正文、数据、堆和栈段。

当前进程的正文都被替换了,那么execl后的语句,即便execl退出了,都不会被执行。

1.1fork()函数(两个进程)

// 将本进程复刻一个子进程
// 1,fork函数将本进程复制一份,并使之成为独立的子进程
// 2,子进程拥有父进程拷贝过来的一切代码,但只从fork函数往下开始执行
// 3,父子进程同时并发运行,此刻无法确定他们的执行次序.
// 4,fork函数在父子进程两处,分别返回不同的值(大于0是父进程,等于0是子进程)

pid_t pid = fork();
理解:子进程内没有子进程,所以返回值为0

1.2 缓冲区中的数据

进程间的数据是独立的

printf("缓冲区中的数据");pid_t pid = fork();if(pid > 0){// 查看内存中各个区域的数据是否受到影响printf("global:%d\n", global);printf("x:%d\n", x);printf("*p:%d\n", *p);}

输出结果:

在这里插入图片描述

理解:因为`printf("缓冲区中的数据");`没有加换行,所以没有打印大屏幕,而且存储在内存中。
fork()就复制了一份子进程,然后下一个换行符一并打印出来。

2.创建daemon进程(建立工程模板)

1.可以被kill结束
2.目的:脱离终端,类似tcp连接这些,伪系统进程
#include "daemon.h"int main(void)
{pid_t a;int max_fd, i;/*********************************************1. ignore the signal SIGHUP, prevent theprocess from being killed by the shutdownof the present controlling termination**********************************************/signal(SIGHUP, SIG_IGN);/***************************************2. generate a child process, to ensuresuccessfully calling setsid()****************************************/a = fork();if(a > 0)exit(0);/******************************************************3. call setsid(), let the first child process runningin a new session without a controlling termination*******************************************************/setsid();/*************************************************4. generate the second child process, to ensurethat the daemon cannot open a terminal fileto become its controlling termination**************************************************/a = fork();if(a > 0)exit(0);/*********************************************************5. detach the daemon from its original process group, toprevent any signal sent to it from being delivered**********************************************************/setpgrp();/*************************************************6. close any file descriptor to release resource**************************************************/max_fd = sysconf(_SC_OPEN_MAX);for(i=0; i<max_fd; i++)close(i);/******************************************7. clear the file permission mask to zero*******************************************/umask(0);/****************************************8. change the process's work directory,to ensure it won't be uninstalled*****************************************/chdir("/");// Congratulations! Now, this process is a DAEMON!//pause();openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);return 0;
}

在这里插入图片描述

3.dup2()文件描述符重定向

//    int fd = open("a.txt", O_RDWR);//    // 功能: 复制0号描述符为fd
//    // 即从此之后,fd对应的文件与0一样
//    dup2(0, fd);//    char buf[50];
//    bzero(buf, 50);
//    read(fd, buf, 50);//    printf("%s", buf);
int fd = open("b.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);// 功能: 将1覆盖fd,fd丧失了原来的文件关联,fd就变成跟1一样dup2(1, fd);write(fd, "abc", 3); // 向屏幕输出abc

3.1重定向到管道文件进行通信,理解ls | wc

 // 1,创建无名管道int fd[2];pipe(fd);// 2,创建子进程pid_t pid = fork();// 父进程if(pid > 0){// 将标准输出(1号描述符)重新定向到管道的写端dup2(fd[1], STDOUT_FILENO); // cp file1 file2// 此时,执行ls,他将会把数据写入1号描述符,即管道的写端execlp("ls", "ls", NULL);}// 子进程if(pid == 0){// 将标准输入(0号描述符)重新定向到管道的读端dup2(fd[0], STDIN_FILENO); // cp file1 file2close(fd[1]);// 此时,执行wc,他将会从0号描述符读取数据,即管道的读端execlp("wc", "wc", "-w", NULL);}

3.管道

3.1 无名管道

无名管道不存在管道文件,其借助于父子进程共享fork之前打开的文件描述符。(文件打开机制)其数据存储在内存中。

无名管道限制:只能使用于父子进程之间(无法跨越父子关系)

int main()
{// 创建无名管道int fd[2];pipe(fd);// 创建子进程pid_t pid = fork();// parentif(pid > 0){// 父进程只负责读取管道数据,所以最好关闭写端close(fd[1]);char buf[20];// 静静地等待子进程的消息...bzero(buf, 20);read(fd[0], buf, 20);printf("第一次收到子进程的消息:%s\n", buf);// 静静地等待子进程的消息...bzero(buf, 20);read(fd[0], buf, 20);printf("第二次收到子进程的消息:%s\n", buf);}// childif(pid == 0){// 子进程只负责将数据写入管道,所以最好关闭读端close(fd[0]);sleep(2);write(fd[1], "你好", 20);// 暂停
//        pause();}return 0;
}

3.2 有名管道

写者

int main(void)
{// 1,创建有名管道mkfifo("/home/gec/fifo", 0666);/*    // 设置为非阻塞int state = fcntl(fd, F_GETFL);state |= O_NONBLOCK;fcntl(fd, F_SETFL, state);*/// 2,打开管道int fd = open("/home/gec/fifo", O_RDWR);if(fd == -1){perror("打开管道失败");exit(0);}// 3,向对方说一句话write(fd, "abcd", 4);close(fd);return 0;
}

读者

	// 1,创建有名管道mkfifo("/home/gec/fifo", 0666);// 2,打开管道int fd = open("/home/gec/fifo", O_RDONLY);if(fd == -1){perror("打开管道失败");exit(0);}// 3,等待对方的消息char buf[10];bzero(buf, 10);read(fd, buf, 10);printf("对方的消息: %s", buf);close(fd);return 0;

3.3管道的读写特性

在这里插入图片描述

打开文件操作时,不允许管道以单边形式(只有一边,或者只读,或者只写)存在,这样会堵塞然后打不开。
堵塞等待写者:open(“xxx.fifo”,O_RDONLY);

4.信号

4.1sleep()延时函数会被信号的到来打断.

4.2 信号创建和处理(程序模板)

#include "common.h"// f是信号响应函数,注意接口是固定的
void f(int sig)
{// 回收了一个僵尸子进程int status;wait(&status);printf("%d号子进程已被回收\n", WEXITSTATUS(status));
}int main()
{// 关联信号与函数// 即: 将来如果我收到 SIGCHLD,就去执行fsignal(SIGCHLD, f);int n = 0;while(1){n++;pid_t pid = fork();// 父进程,1秒生一个孩子if(pid > 0){pause();sleep(1);continue;}// 子进程,生出来就死掉if(pid == 0){exit(n);}}return 0;
}

4.2.1细节:进程的信号挂起队列中,没有相同的信号(即相同的信号会被丢弃)。

理解:信号阻塞接触后,首先清零信号集中对应的信号位,然后执行信号处理函数

4.2.2进程在响应信号时,不同的信号会相互嵌套。

4.2.3挂起信号不会被子进程继承,但信号阻塞掩码会被继承。

4.2.4 getchar会被信号打断

判断代码

ch = getchar();
if (ch = -1 && errno == EINTR)
{printf("读操作被信号中断了");
}

4.3 信号阻塞

    sigset_t sigs;sigemptyset(&sigs);sigaddset(&sigs, SIGHUP);sigaddset(&sigs, SIGINT);sigprocmask(SIG_BLOCK, &sigs, NULL);

5.flag操作

5.1 置位和清零

对应flag置位(使能)

flag |= O_NONBLOCK

对应flag清零

flag &= ~O_NONBLOCK

5.共享内存

int main()
{// 0,创建一个专属的keykey_t key = ftok(PROJ_PATH, PROJ_ID);// 1,创建/打开共享内存int id = shmget(key, 1024, IPC_CREAT|0666);// 2,将共享内存映射到本进程内存空间// id: 你要映射的共享内存// NULL: 代表让系统帮你决定映射之后的入口地址// 0: 代表映射后对共享内存可读可写char *addr = shmat(id, NULL, 0);// 3,将数据写入共享内存printf("按回车给共享内存写入数据\n");getchar();snprintf(addr, 1024, "%s", "You jump, I jump!\n");// 4,解除共享内存的映射shmdt(addr);return 0;
}

读者

#include "common.h"int main()
{// 0,创建一个专属的keykey_t key = ftok(PROJ_PATH, PROJ_ID);// 1,创建/打开共享内存int id = shmget(key, 1024, IPC_CREAT|0666);// 2,将共享内存映射到本进程内存空间// id: 你要映射的共享内存// NULL: 代表让系统帮你决定映射之后的入口地址// 0: 代表映射后对共享内存可读可写char *addr = shmat(id, NULL, 0);// 3,将数据从共享内存中读出printf("按回车将共享内存中的数据打印出来\n");getchar();printf("%s", addr);// 4,解除共享内存的映射shmdt(addr);// 5,删除共享内存对象shmctl(id, IPC_RMID, NULL);return 0;
}

5.systemV

几个跟 system-V IPC 对象相关的命令:
ipcs -a:查看当前系统中存在的所有的 IPC 对象。
ipcs -q:查看当前系统中存在的 消息队列。
ipcs -m:查看当前系统中存在的 共享内存。
ipcs -s:查看当前系统中存在的 信号量。
删除 IPC 对象
ipcrm -Q key : 删除指定的消息队列
ipcrm -q id : 删除指定的消息队列

ipcrm -M key : 删除指定的共享内存
ipcrm -m id: 删除指定的共享内存

ipcrm -S key : 删除指定的信号量
ipcrm -s id: 删除指定的信号量

5.信号量

在这里插入图片描述

5.1 信号量(组)

需要先定义

union semun
{int val;struct semid_ds *buf;unsigned short *array;struct seminfo *__buf;
};
#include "common.h"// 对信号量semid中的第n个元素,进行P操作
void sem_p(int semid, int n)
{struct sembuf buf;bzero(&buf, sizeof(buf));buf.sem_num = n; // 第n个元素buf.sem_op  = -1;// 减操作buf.sem_flg = 0; // 默认的选项// 此处就是P操作,申请资源,可能会发生阻塞semop(semid, &buf, 1);
}// 对信号量semid中的第n个元素,进行V操作
void sem_v(int semid, int n)
{struct sembuf buf;bzero(&buf, sizeof(buf));buf.sem_num = n; // 第n个元素buf.sem_op  = +1;// 加操作buf.sem_flg = 0; // 默认的选项// 此处就是V操作,增加资源,永不阻塞semop(semid, &buf, 1);
}int main()
{// 0,创建两个keykey_t key1 = ftok(PROJ_PATH, PROJ_SHM);key_t key2 = ftok(PROJ_PATH, PROJ_SEM);// 1,创建共享内存、信号量int shmid = shmget(key1, 256, IPC_CREAT|0666);int semid = semget(key2, 2, IPC_CREAT|0666);// 2,映射共享内存char *addr = shmat(shmid, NULL, 0);// 3,初始化信号量union semun a;// 将第0个信号量元素的值,设置为a.vala.val = 1;semctl(semid, 0, SETVAL, a);// 将第1个信号量元素的值,设置为a.vala.val = 0;semctl(semid, 1, SETVAL, a);// 4,不断往共享内存中写入数据while(1){// 对代表内存的第0个信号量元素进行P操作sem_p(semid, 0);// 直接往共享内存输入数据fgets(addr, 256, stdin);// 对代表数据的第1个信号量元素进程V操作sem_v(semid, 1);}return 0;
}

5.2 有名信号量(进程间)

#include "common.h"int main()
{// 1,创建POSIX有名信号量sem_t *s = sem_open("/abc", O_CREAT, 0666, 1/*初始值*/);if(s == SEM_FAILED)perror("创建有名信号量失败");elseprintf("创建有名信号量成功");// 2,创建两个进程,使之使用以上信号量来进行协同pid_t p1, p2;p1 = fork();if(p1 > 0){p2 = fork();if(p2 > 0)exit(0);// 进程p2:else{while(1){// 进行P操作sem_wait(s);// 疯狂输出字母for(int i=0; i<26; i++){fprintf(stderr, "%c", 'a'+i);usleep(20*1000);}// 进程V操作sem_post(s);}}}// 进程p1:else{while(1){// 进行P操作sem_wait(s);// 疯狂输出数字for(int i=0; i<10; i++){fprintf(stderr, "%d", i);usleep(20*1000);}// 进程V操作sem_post(s);}}return 0;
}

5.3无名信号量(线程间)

#include "common.h"// 定义POSIX无名信号量
sem_t s;void *routine(void *arg)
{char *t = (char *)arg;if(strcmp(t, "t1")==0){while(1){// 进行P操作sem_wait(&s);// 疯狂输出字母for(int i=0; i<26; i++){fprintf(stderr, "%c", 'a'+i);usleep(20*1000);}// 进程V操作sem_post(&s);}}if(strcmp(t, "t2")==0){while(1){// 进行P操作sem_wait(&s);// 疯狂输出数字for(int i=0; i<10; i++){fprintf(stderr, "%d", i);usleep(20*1000);}// 进程V操作sem_post(&s);}}
}int main()
{// 1,初始化sem_init(&s, 0/*作用范围是线程间*/, 1/*初始值*/);// 2,搞两条线程去进程P/V操作pthread_t t1, t2;pthread_create(&t1, NULL, routine, "t1");pthread_create(&t1, NULL, routine, "t2");pthread_exit(NULL);
}

6.消息队列(messageQueue)

// 消息结构体
struct msgbuf
{// 第一个成员是规定long型,是每个消息的标签long type;// 后续成员没有规定,想发什么数据就写什么char data[30];};// 自定义消息标签
#define J2R 1
#define R2J 2

写者

#include "common.h"int main()
{// 0,搞一个keykey_t key = ftok(PROJ_PATH, PROJ_ID);// 1,创建/打开一个消息队列// IPC_CREAT: 如果不存在就创建,如果存在就打开int id = msgget(key, IPC_CREAT | 0666);if(id == -1){perror("创建/打开消息队列失败");exit(0);}// 2,准备消息结构体struct msgbuf buf;bzero(&buf, sizeof(buf));buf.type = J2R;// 3,输入数据并发送fgets(buf.data, 30, stdin);msgsnd(id, &buf, strlen(buf.data)+1, 0/*阻塞型发送*/);return 0;
}

读者

#include "common.h"int main()
{// 0,搞一个keykey_t key = ftok(PROJ_PATH, PROJ_ID);// 1,创建/打开一个消息队列// IPC_CREAT: 如果不存在就创建,如果存在就打开int id = msgget(key, IPC_CREAT | 0666);if(id == -1){perror("创建/打开消息队列失败");exit(0);}// 2,准备消息结构体来接收Jack的消息struct msgbuf buf;bzero(&buf, sizeof(buf));// 3,接收对方的消息并打印int n = msgrcv(id, &buf, 30, J2R, 0/*阻塞型接收*/);if(n == -1){perror("接收消息失败");exit(0);}elseprintf("Jack的消息: %s", buf.data);// 4,收完消息后,删除消息队列msgctl(id, IPC_RMID, NULL);return 0;
}

6共享内存(建立工程模板)

 // 0,创建一个专属的keykey_t key = ftok(PROJ_PATH, PROJ_ID);// 1,创建/打开共享内存int id = shmget(key, 1024, IPC_CREAT|0666);// 2,将共享内存映射到本进程内存空间// id: 你要映射的共享内存// NULL: 代表让系统帮你决定映射之后的入口地址// 0: 代表映射后对共享内存可读可写char *addr = shmat(id, NULL, 0);// 3,将数据从共享内存中读出printf("按回车将共享内存中的数据打印出来\n");getchar();printf("%s", addr);// 4,解除共享内存的映射shmdt(addr);// 5,删除共享内存对象shmctl(id, IPC_RMID, NULL);

7. 创建线程

7.1创建线程工程模板

void *routine(void *arg)
{int a = *(int *)arg;printf("a: %d\n", a);// 打印一些数字for(int i=0; i<10; i++){a++;fprintf(stderr, "%d", a);usleep(200*1000);}// 退出线程pthread_exit("abcdefg");// 线程一旦退出,立即就会变成僵尸线程
}int main()
{// 创建一条线程// 如果创建成功,系统会为这条线程分配一个ID号称为线程ID// tid: 存放线程ID// NULL: 不指定特定的线程属性,创建一条标准线程// routine: 线程的启动函数// &a: 传给线程的参数pthread_t tid;int a = 100;pthread_create(&tid, NULL, routine, (void *)&a);// 静静地等待回收子线程的资源... ...void *val;pthread_join(tid, &val); // 相当于wait()/waitpid()printf("返回值: %s\n", (char *)val);return 0;
}

7.2 子线程分离(系统回收线程资源)

在这里插入图片描述

#include "common.h"
#include <errno.h>void *routine(void *arg)
{//第二种// 将自己分离出去pthread_detach(pthread_self());pthread_exit("abcd");
}int main()
{// 两种方法.第一种// 1,设置线程的属性变量pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 2,根据属性变量来创建线程pthread_t tid;pthread_create(&tid, &attr, routine, NULL);void *val;int err = pthread_join(tid, &val);if(err == 0){printf("子线程返回值: %s\n", (char *)val);}else{printf("获取子线程失败: %s\n", strerror(err));}return 0;
}

7.1 线程等待和唤醒 pthread_cond()

在这里插入图片描述

#include "common.h"// 余额
int balance = 0;// 互斥锁与条件量
pthread_mutex_t m;
pthread_cond_t  v;// 所有的子线程的启动函数
void *routine(void *args)
{// 加锁pthread_mutex_lock(&m);// 判断余额while(balance <= 0){// 进入条件量v的等待房间// 进入以下函数,将自动解锁m// 退出以下函数,将自动加锁mpthread_cond_wait(&v, &m);}// 取钱balance -= 80;printf("我是第%d条线程,取了80元之后的余额是:%d\n", (int)args, balance);// 解锁pthread_mutex_unlock(&m);
}int main(int argc, char **argv) // ./main 10
{// 初始化pthread_mutex_init(&m, NULL);pthread_cond_init(&v, NULL);// 创建一些子女线程pthread_t tid;for(int i=0; i<atoi(argv[1]); i++){pthread_create(&tid, NULL, routine, (void *)i);}// 主线程充当了父母,去充钱pthread_mutex_lock(&m);balance += 500;pthread_mutex_unlock(&m);// 唤醒在条件量房间中睡觉的线程(们)
//    pthread_cond_signal(&v); // 唤醒一个pthread_cond_broadcast(&v); // 唤醒全部// 退出主线程pthread_exit(NULL);
}

注意 : 无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

7.2互斥锁

互斥锁是s=1的二值信号量
同步是s=0的信号量 ,先执行的进行v操作,后执行的进行p操作(s=0进入睡眠状态)

7.2.1程序模板

#include "common.h"// 定义互斥锁
pthread_mutex_t m;void output(const char *s)
{while(*s != '\0'){putc(*s, stderr);usleep(1000);s += 1;}return;
}void *routine(void *arg)
{pthread_mutex_lock(&m);output("info output bu sub-thread.\n");pthread_mutex_unlock(&m);
}int main()
{// 初始化互斥锁pthread_mutex_init(&m, NULL);pthread_t tid;pthread_create(&tid, NULL, routine, NULL);pthread_mutex_lock(&m);output("message delivered by main thread.\n");pthread_mutex_unlock(&m);// 在main函数中return,相当于退出了进程,即所有的线程都被迫退出了// return 0;// 退出当前线程pthread_exit(NULL);
}

7.2 线程理解

7.2.1主线程main()函数pthread_exit(NULL)退出,不影响其他线程继续执行,进程没有结束,保留进程资源,供其他由main创建的线程使用,直至所有线程都结束。

7.2.1 在任何一个线程中调用exit函数都会导致进程结束。进程一旦结束,那么进程中的所有线程都将结束。

7.2.2 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符

7.2.3 不能对一个已经处于detach状态的线程调用pthread_join

7.3线程调度策略

在这里插入图片描述

7.3.1 工程模板


void *routine(void *arg)
{if(*(char *)arg == 'A')nice(0);else if(*(char *)arg == 'B')nice(0);while(1){fprintf(stderr, "%c", *(char *)arg);}
}int main(int argc, char **argv)
{pthread_attr_t attr1;pthread_attr_t attr2;pthread_attr_init(&attr1);pthread_attr_init(&attr2);pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);//inherit 继承pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);//sched 时间表pthread_attr_setschedpolicy(&attr1, SCHED_RR);pthread_attr_setschedpolicy(&attr2, SCHED_RR);struct sched_param param1;struct sched_param param2;param1.sched_priority = 91;param2.sched_priority = 92;pthread_attr_setschedparam(&attr1, &param1);pthread_attr_setschedparam(&attr2, &param2);pthread_t tid1, tid2;pthread_create(&tid1, &attr1, routine, "A");pthread_create(&tid2, &attr2, routine, "B");pause();return 0;
}

7.3.2 sudo才能操作pthread_attr_setschedparam(&attr1 ,SCHED_RR);

属于修改系统层面的优先级

7.3.2SCHED_RR 和SCHED_FIFO的param.sched_priority需要大于0才可以成功

7.3.2 函数nice(10)修改动态优先级

同样需要sudo执行程序

7.4线程取消函数pthread_cancel()(不建议使用,最好自身pthread_exit()😉

个人理解:不建议使用,最好自身pthread_exit();

//主函数发起
pthread_cancel(tid);
void *routine(void *avg)
{int a=0;// 关闭取消状态,即不可被取消// NULL: 代表不保留原来的状态
//    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);// 将取消类型,设置为延迟//不延迟 :PTHREAD_CANCEL_ASYNCHRONOUS// NULL: 代表不保留原来的类型pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);// 拖时间/*  unsigned long long i, j;long double f1, f2;for(i=0; i<10000; i++){for(j=0; j<80000; j++){f1 = f2;}}*/// 循环输出'x';while(1){// 以下函数是取消点,意味着本线程收到取消要求之后,// 在执行完取消点函数fprintf()执行之后之后,就退出。fprintf(stderr, "%c+%d", 'x',a);a++;printf("%d ",a);}
}

(未理解为什么)输出结果:

1.手动创建一个取消点 void pthread_testcancel(void) 检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。
此函数在线程内执行,执行的位置就是线程退出的位置,在执行此函数以前,线程内部的相关资源申请一定要释放掉,他很容易造成内存泄露。

缓冲区溢出
> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx回收成功!

		//修改后(不稳定)fprintf(stderr, "%c+%d\r\n", 'x',a);a++;printf("%d \r\n",a);

在这里插入图片描述

7.4 pthread_kill

pthread_kill与kill有区别,是向线程发送signal。,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。

int pthread_kill(pthread_t thread, int sig);

向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。

pthread_kill(threadid, SIGKILL)杀死整个进程。 如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)。所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。

7.5线程遗言函数pthread_cleanup()(避免发生死互斥锁)

首先发起取消
功能:线程可以通过pthread_cancel来请求取消同一进程中的其它线程
pthread_cancel并不等待线程终止,它仅仅提出请求


pthread_cancel(t1);

void *routine(void *avg)
{
// 关闭取消状态,即不可被取消
// NULL: 代表不保留原来的状态
// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

// 将取消类型,设置为不延迟
// NULL: 代表不保留原来的类型
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);// 拖时间

/* unsigned long long i, j;
long double f1, f2;
for(i=0; i<10000; i++)
{
for(j=0; j<80000; j++)
{
f1 = f2;
}
}*/

// 循环输出'x';
while(1)
{// 以下函数是取消点,意味着本线程收到取消要求之后,// 在执行完取消点函数之后,就退出。fprintf(stderr, "%c", 'x');
}

}
然后响应

void handler(void *arg)
{printf("我[%u]被取消了!\n", (unsigned)pthread_self());pthread_mutex_unlock(&m);
}void *routine(void *arg)
{// 写遗言// 如果将来我死了,请系统帮我执行以下handlerpthread_cleanup_push(handler, NULL);// 加锁之后,如果本线程有可能在解锁之前被杀死(或被取消)// 那么就必须先写好遗言pthread_mutex_lock(&m);printf("[%u]已经锁上了muext!,开始干活...\n", (unsigned)pthread_self());// 干一些事情...sleep(2);printf("[%u]活干完了,准备收工\n", (unsigned)pthread_self());pthread_mutex_unlock(&m);printf("[%u]锁已经解了,下班走人\n", (unsigned)pthread_self());// 如果此刻我还没死,那么后面我再死去也不怕了// 为了节省系统资源,要将刚刚写好的遗言清除// 0: 意味着清除遗言函数,并且不执行pthread_cleanup_pop(0);pthread_exit(NULL);
}

7.6进程间通信方式

管道
无名管道 pipe:适用于亲缘关系进程间的、一对一的通信
有名管道 fifo :适用于任何进程间的一对一、多对一的通信
套接字 socket:适用于跨网络的进程间通信
信号:异步通信方式
system-V IPC对象
共享内存:效率最高的通信方式
消息队列:相当于带标签的增强版管道
信号量
信号量组:参数复杂,功能强大到臃肿
POSIX有名信号量:适用于多进程,参数简单,接口明晰,老少咸宜
POSIX无名信号量:适用于多线程,参数简单,接口明晰,童叟无欺

无名管道
int pipe( int fd[2] )
功能:创建无名管道 pipe
注意:pipe 拥有两个文件描述符,一个专用于读fd[0],一个专用于写fd[1]
创建出来的 pipe 的描述符,只能通过子进程继承的方式传递给别的进程,因此只能用于亲缘进程间的通信,其他非亲缘进程无法获取 pipe 的描述符。
不能有多个进程同时对 pipe 进行写操作,否则数据有可能被覆盖
总结: pipe 适用于一对一的、具有亲缘关系的进程间的通信。

六、程序报错与代码调试

1. perror

2. errno:报告最近的一条系统错误

printf("%s\r\n", strerror(errno) );

3. 代码错误定位(常用)

printf("%d\r\n", __LINE__);
printf("%s:%d\n", __FILE__, __LINE__);

七、linux网络编程

7.1五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O

五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O
2018年10月28日 ⁄ 综合 ⁄ 共 3507字 ⁄ 字号 小 中 大 ⁄ 评论关闭
五种I/O 模式:
【1】 阻塞 I/O (Linux下的I/O操作默认是阻塞I/O,即open和socket创建的I/O都是阻塞I/O)
【2】 非阻塞 I/O (可以通过fcntl或者open时使用O_NONBLOCK参数,将fd设置为非阻塞的I/O)
【3】 I/O 多路复用 (I/O多路复用,通常需要非阻塞I/O配合使用)
【4】 信号驱动 I/O (SIGIO)
【5】 异步 I/O

一般来说,程序进行输入操作有两步:
1.等待有数据可以读
2.将数据从系统内核中拷贝到程序的数据区。

对于sock编程来说:

     第一步:   一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;第二步:   是从内核中把数据拷贝到程序的数据区中。

阻塞I/O模式 //进程处于阻塞模式时,让出CPU,进入休眠状态
阻塞 I/O 模式是最普遍使用的 I/O 模式。是Linux系统下缺省的IO模式。

   大部分程序使用的都是阻塞模式的 I/O 。一个套接字建立后所处于的模式就是阻塞 I/O 模式。(因为Linux系统默认的IO模式是阻塞模式)

对于一个 UDP 套接字来说,数据就绪的标志比较简单:
(1)已经收到了一整个数据报
(2)没有收到。
而 TCP 这个概念就比较复杂,需要附加一些其他的变量。

   一个进程调用 recvfrom  ,然后系统调用并不返回知道有数据报到达本地系统,然后系统将数据拷贝到进程的缓存中。 (如果系统调用收到一个中断信号,则它的调用会被中断)

我们称这个进程在调用recvfrom一直到从recvfrom返回这段时间是阻塞的。当recvfrom正常返回时,我们的进程继续它的操作。
在这里插入图片描述

非阻塞模式I/O //非阻塞模式的使用并不普遍,因为非阻塞模式会浪费大量的CPU资源。
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核: “当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
我们开始对 recvfrom 的三次调用,因为系统还没有接收到网络数据,所以内核马上返回一个 EWOULDBLOCK的错误。

  第四次我们调用 recvfrom 函数,一个数据报已经到达了,内核将它拷贝到我们的应用程序的缓冲区中,然后 recvfrom 正常返回,我们就可以对接收到的数据进行处理了。当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不听的测试是否一个文件描述符有数据可读(称做 polling(轮询))。应用程序不停的 polling 内核来检查是否 I/O操作已经就绪。这将是一个极浪费 CPU资源的操作。这种模式使用中不是很普遍。

在这里插入图片描述

例如:
对管道的操作,最好使用非阻塞方式!
I/O多路复用 //针对批量IP操作时,使用I/O多路复用,非常有好。

   在使用 I/O 多路技术的时候,我们调用 select()函数和 poll()函数或epoll函数(2.6内核开始支持),在调用它们的时候阻塞,而不是我们来调用 recvfrom(或recv)的时候阻塞。当我们调用 select函数阻塞的时候,select 函数等待数据报套接字进入读就绪状态。当select函数返回的时候, 也就是套接字可以读取数据的时候。 这时候我们就可以调用 recvfrom函数来将数据拷贝到我们的程序缓冲区中。对于单个I/O操作,和阻塞模式相比较,select()和poll()或epoll并没有什么高级的地方。而且,在阻塞模式下只需要调用一个函数:读取或发送函数。在使用了多路复用技术后,我们需要调用两个函数了:先调用 select()函数或poll()函数,然后才能进行真正的读写。多路复用的高级之处在于::它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

在这里插入图片描述

IO 多路技术一般在下面这些情况中被使用:
1、当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字),I/O 多路复用技术将会有机会得到使用。
2、当程序需要同时进行多个套接字的操作的时候。
3、如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。
4、如果一个服务器程序同时使用 TCP 和 UDP 协议。
5、如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetd就是这样的)。

异步IO模式有::
1、信号驱动I/O模式
2、异步I/O模式
信号驱动I/O模式 //自己没有用过。
我们可以使用信号,让内核在文件描述符就绪的时候使用 SIGIO 信号来通知我们。我们将这种模式称为信号驱动 I/O 模式。

在这里插入图片描述

为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。
(1)一个和 SIGIO信号的处理函数必须设定。
(2)套接字的拥有者必须被设定。一般来说是使用 fcntl 函数的 F_SETOWN 参数来
进行设定拥有者。
(3)套接字必须被允许使用异步 I/O。一般是通过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。
虽然设定套接字为异步 I/O 非常简单,但是使用起来困难的部分是怎样在程序中断定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。

1.UDP 套接字的 SIGIO 信号 (比较简单)
在 UDP 协议上使用SIGIO 非常简单.这个信号将会在这个时候产生:
1、套接字收到了一个数据报的数据包。
2、套接字发生了异步错误。
当我们在使用 UDP 套接字异步 I/O 的时候,我们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。
2.TCP 套接字的 SIGIO 信号 (因为复杂,实际一般用的是socket的异步IO)
不幸的是,SIGIO 几乎对 TCP 套接字而言没有什么作用。因为对于一个 TCP 套接字来说,SIGIO 信号发生的几率太高了,所以 SIGIO 信号并不能告诉我们究竟发生了什么事情。
在 TCP 连接中, SIGIO 信号将会在这个时候产生:
l 在一个监听某个端口的套接字上成功的建立了一个新连接。
l 一个断线的请求被成功的初始化。
l 一个断线的请求成功的结束。
l 套接字的某一个通道(发送通道或是接收通道)被关闭。
l 套接字接收到新数据。
l 套接字将数据发送出去。
l 发生了一个异步 I/O 的错误。

一个对信号驱动 I/O 比较实用的方面是 NTP(网络时间协议 Network Time Protocol)服务器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,然后再发送请求。对于这个服务器来说,记录下收到每一个数据包的具体时间是很重要的。

因为那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费的时间。图 6-8 表示了怎样建立这样的一个 UDP 服务器。

异步I/O模式 //比如写操作,只需用写,不一定写入磁盘(这就是异步I/O)的好处。异步IO的好处效率高。
当我们运行在异步 I/O 模式下时,我们如果想进行 I/O 操作,只需要告诉内核我们要进行 I/O 操作,然后内核会马上返回。具体的 I/O 和数据的拷贝全部由内核来完成,我们的程序可以继续向下执行。当内核完成所有的 I/O 操作和数据拷贝后,内核将通知我们的程序。
异步 I/O 和 信号驱动I/O的区别是:
1、信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序发送SIGIO 消息。
2、异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。

在这里插入图片描述
原地址:https://www.xuebuyuan.com/3256674.html

7.2udp互发

发送

(实验)bind绑定后是指定ip和端口,不bind则是系统自动分配

#include "common.h"int main(int argc, char **argv) // ./Jack 对方IP 对方端口
{// 1,创建UDP通信端点int sockfd = Socket(AF_INET, SOCK_DGRAM, 0);// 2,准备好对方的地址(IP+PORT)struct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_family = AF_INET;  // 指定协议族,这里是IPv4地址addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址addr.sin_port = htons(atoi(argv[2]));      // PORT端口号// 3,给对方发去消息char buf[50];while(1){bzero(buf, 50);fgets(buf, 50, stdin);sendto(sockfd, buf, strlen(buf), 0,(struct sockaddr *)&addr, len);}return 0;
}

接收

#include "common.h"int main(int argc, char **argv) // ./Rose 自身IP 自身端口
{// 1,创建UDP通信端点int sockfd = Socket(AF_INET, SOCK_DGRAM, 0);// 2,准备好要绑定的自身的地址(IP+PORT)struct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_family = AF_INET;  // 指定协议族,这里是IPv4地址addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址addr.sin_port = htons(atoi(argv[2]));      // PORT端口号// 3,绑定地址Bind(sockfd, (struct sockaddr *)&addr, len);// 4,静静等待对方的数据char buf[50];while(1){bzero(buf, 50);recvfrom(sockfd, buf, 50, 0, NULL, NULL);printf("收到对方消息:%s", buf);}return 0;
}

7.2 udp信号处理 -> 7.1文件状态原理

#include "wrap.h"int sockfd;// 信号处理函数
void readMsg(int sig)
{char buf[50];bzero(buf, 50);recvfrom(sockfd, buf, 50, 0, NULL, NULL);printf("收到信息: %s", buf);
}int main(int argc, char **argv) // ./main IP PORT
{// 1,创建UDP套接字sockfd = Socket(AF_INET, SOCK_DGRAM, 0);// 2,绑定IP、端口struct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(argv[1]);addr.sin_port = htons(atoi(argv[2]));Bind(sockfd, (struct sockaddr *)&addr, len);// 3,准备使用信号驱动的方式来接收UDP数据// 3.1 关联信号及其处理函数signal(SIGIO, readMsg);// 3.2 将套接字的模式设定更为异步模式,从而触发信号int state = fcntl(sockfd, F_GETFL);state |= O_ASYNC;fcntl(sockfd, F_SETFL, state);// 3.3 设定信号的属主fcntl(sockfd, F_SETOWN, getpid());// 服务器现在可以去干别的了for(int i=0; ; i++){printf("%d\n", i);sleep(1);}return 0;
}

7.3 UDP广播

7.3.1 可能需要不同物理网卡才能实现这个功能

#include "wrap.h"// 使用UDP发送广播消息int main(int argc, char **argv) // ./main 192.168.1.100 50001 --> 单播// ./main 192.168.1.255 50001 --> 广播
{// 1,准备好UDP通信端点int sockfd = Socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(argv[1]);addr.sin_port = htons(atoi(argv[2]));// 2,使能广播int on = 1;int a = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,&on, sizeof(on));if(a != 0){perror("使能广播失败");exit(0);}// 3,发送消息int b = sendto(sockfd, "abcd\n", 5, 0, (struct sockaddr *)&addr,len);printf("发送了%d个字节\n", b);return 0;
}

八 shell

shell -eq处理数值 == 处理字符

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 高级图形学之离散技术

    高级图形学之离散技术离散数据最重要的用途之一就是表面绘制。下面我们将从映射方法、纹理映射、OpenGL 中的纹理映射、和纹理生成四个大的部分进行介绍。映射方法图形显示卡可以每秒钟显示多达一千万个多边形,但这个速度仍不能满足模拟许多自然现象和天然物质的要求:如云、草…...

    2024/3/28 16:24:38
  2. 搞它!!!Linux构建远程YUM仓库与NFS共享存储服务

    文章目录一、构建远程YUM仓库1、YUM仓库概述(1)、yum仓库简介(2)yum仓库的三种提供方式(3)yum配置文件(4)配置远程ftp YUM源(5)软件包查询(6)安装、升级软件二、NFS共享存储服务1、NFS 网络文件系统(Network File System)2、NFS挂载原理3、配置NFS服务(1)第一步…...

    2024/3/8 8:01:12
  3. 实训第五次总结

    任务:(1).创建服务接口, (2).创建服务接口实现类 (3)创建测试类 1.创建服务接口 在net.hw.student包里创建service子包;并在内创建学校,状态,学生,用户服务接口:CollegeService,StatusService,StudentService,UserService(此板块里面的代码的敲写较少)2.创…...

    2024/3/8 8:01:07
  4. C语言 读取文件内容

    要读取的目标文件:要读取的目标内容:运行前请将代码文件和要读取的文件放在同一目录下。 #include <stdio.h>int main(void){int ch;FILE *fp;char fname[FILENAME_MAX];printf("文件名:");scanf("%s", fname);if((fp = fopen(fname, "r&quo…...

    2024/3/10 22:17:20
  5. org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z

    MapReduce刚启动会产生的问题 org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z 导致的原因可能是access里面的return出了问题 重新创建org.apache.hadoop.io.nativeio.NativeIO 这个类 将源码拷贝过来 修改public static boolean access(String…...

    2024/3/4 5:09:58
  6. day46:64. 字符流中第一个只出现一次的字符(队列的巧妙使用)

    问题描述: 请实现一个函数用来找出字符流中第一个只出现一次的字符。 例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是’g’。当从该字符流中读出前六个字符”google”时,第一个只出现一次的字符是’l’。如果当前字符流没有存在出现一次的字符,返回…...

    2024/3/7 16:26:52
  7. 基于Keras的卷积神经网络(CNN)可视化

    链接:https://www.cnblogs.com/bnuvincent/p/9612686.html 卷积层的主要目的是滤波。 注意事项: 卷积运算后的输出无论在宽度上还是高度上都比原来的小 核和图片窗口之间进行的是线性的运算 滤波器中的权重是通过许多图片学习的 池化: 池化层和卷积层很类似,也是用一个卷积…...

    2024/3/4 5:09:56
  8. VMware Workstation Pro 15.5 的安装(安装VMware报错:此安装程序要求您重新启动系统以完成Microsoft VC Redistributable安装)

    目录VMware Workstation Pro 15.5 的安装(安装VMware报错:此安装程序要求您重新启动系统以完成Microsoft VC Redistributable安装)一、前言二、安装过程 VMware Workstation Pro 15.5 的安装(安装VMware报错:此安装程序要求您重新启动系统以完成Microsoft VC Redistributa…...

    2024/3/13 22:40:52
  9. 点评:“理想与现实的努力平衡:OpenAI开启测试API试水商业化道路”道翰天琼认知智能机器人大脑API接口平台。

    摘要:为了平衡使命和现实,OpenAI开始尝试将研发出的高性能算法GPT-3构建成API以实现商业化……基于这一强大的自然语言模型,OpenAI发布了一款通用的文本处理接口,与先前为特定目标设计的AI系统不同的是,此API可以允许用户任务尝试任意英文语言任务。……目前已经有十多个公…...

    2024/3/8 8:00:59
  10. Roundgod and Milk Tea (HTU-6667)

    Roundgod and Milk Tea (HTU-6667)题目样例输入样例输出题目大意题目解析代码 HTU-6667 题目 Roundgod is a famous milk tea lover at Nanjing University second to none. This year, he plans to conduct a milk tea festival. There will be n classes participating …...

    2024/3/28 15:46:07
  11. 第8章通用程序设计

    本章主要讨论:局部变量的处理、控制结构类库的用法、各种数据类型的用法和不是由语言本身提供的机制(反射和本地方法)、优化和命名惯例。45. 将局部变量的作用域最小化将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。要使局部变量的作用域…...

    2024/3/28 16:11:17
  12. 初次学习Java注意事项

    注意事项Dos页面切换 时使用 F: 可以切换每个单词的大小不能出现问题,Java是大小写敏感的尽量使用英文;文件名和类名必须保证一致,并且首字母大写符号使用的了中文,需使用英文使用DOS运行java脚本时需先使用 javac java脚本 ;然后再使用java java 脚本即可。Hello word 语…...

    2024/3/28 19:30:12
  13. 智能编曲软件含音色库-Band in a Box 2017 macOS | 100GB

    PG Music Band-in-a-Box 2017 MAC | 884M + 96GB Mac版Band-in-a-Box2017具有80多个令人兴奋的新功能和增强功能!对主屏幕GUI进行了重新设计,进行了许多请求的改进,包括新的,较小的占用空间,可配置的工具栏等。RealTrack音质比以往任何时候都更好,具有更好的拉伸和移调质…...

    2024/3/29 8:09:36
  14. 单例模式的设计思路与具体实现

    文章内容输出来源:拉勾教育大数据训练营; 单例模式的设计思路与具体实现 Java中的一个类,理论上可以创建无数个对象。 假如我们只允许一个类创建一个对象,就需要会使用单例模式来设计该类。单例模式的应用场景 1. Windows的任务管理器、回收站等; 2. 网站的计数器; 3. 数…...

    2024/3/27 11:36:16
  15. 前端——Vue(1)

    1 MVC和MVVM 1.1 MVC 概述:后端分层开发思想M(Model)模型:需要给view传递的所有数据。 V(View)视图:负责将model中的数据进行展示 C(Controller)控制器:负责接收页面http请求,进行业务逻辑调用或处理,返回正确的模型、视图或内容1.2 MVVM 概述:前端视图层分层开发思想 &l…...

    2024/3/28 11:05:59
  16. 2020最新测试面试基础(一)

    为什么要在一个团队中开展软件测试工作? 为了验证软件是否可用,是否可上线,同时避免此问题线上发生,避免公司发生损失 一份测试计划应该包括哪些内容? 项目背景、项目简介、项目目的、测试范围、人员分工、资源配置要求、进度计划制定、参考文档、常用术语、提交文档、风险…...

    2024/3/28 18:03:51
  17. CSS核心知识点浅谈

    CSS简介:css:Cascading Style Sheet 层叠样式表,简称样式表css的目的是实现网页布局,对网页进行美化 学习CSS其实就是学习一堆的选择器和属性。CSS语法:引入CSS 行内样式直接在标签里添加style属性,如:<p style="color: red;"></p>内部样式写在头…...

    2024/3/28 17:24:14
  18. 安卓XML解析获取后台json返回的html标签数据

    //本例只是获取第一个数据,当然你可以存放在list中获取所有的数据。 public static String getTagFirst(String html){XmlPullParser xmlPullParser = Xml.newPullParser();try {InputStream inputStream = new ByteArrayInputStream(html.getBytes("utf-8"));xmlPu…...

    2024/3/28 18:03:49
  19. 音乐制作软件-Ableton Live Suite v10.1.0 WiN-MAC

    Ableton Live Suite v10.1.0 WiN&MacOSX | 3GB 使用Live的新设备创建更大胆的声音。通过大量的工作流程改进来保持发展。使用Push,可以使笔记本计算机与计算机的距离更大。使用精选库来建立声音。并获得无缝内置的Max for Live的无限潜力。 使用新设备进行创建 满足Waveta…...

    2024/3/28 18:03:49
  20. python Unable to import ‘xxx‘ pylint(import-error); No module named xxx的解决办法

    问题描述: 下面以PyQt5这个包为例说明安装PyQt5,没有问题,显示成功安装pip install PyQt5调用的时候出现解决办法 在前面加上路径(pip 安装的包的路径)路径信息查询pip show PyQt5这样vscode还是会显示红线,可以不管这个线,直接运行没有任何问题。(有强迫症的同学可以看看…...

    2024/3/8 8:00:11

最新文章

  1. C# OpenCvSharp MatchTemplate 多目标匹配

    目录 效果 项目 代码 下载 效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp; using O…...

    2024/3/29 15:59:32
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 【机器学习300问】55、介绍推荐系统中的矩阵分解算法是什么、有什么用、怎么用?

    本来这篇文章我想先讲矩阵分解算法是什么东西的&#xff0c;但这样会陷入枯燥的定义中去&#xff0c;让原本非常有趣技术在业务场景中直观的使用路径被切断。所以我觉得先通过一个具体的推荐算法的例子&#xff0c;来为大家感性的介绍矩阵分解有什么用会更加合理。 如果你还不知…...

    2024/3/29 10:00:40
  4. 如何在Linux系统使用Docker本地部署Halo网站并实现无公网IP远程访问

    最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可…...

    2024/3/29 0:30:10
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/3/27 10:21:24
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/3/24 20:11:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/3/29 2:45:46
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/3/24 20:11:23
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/3/29 5:19:52
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/3/28 17:01:12
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/3/29 11:11:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/3/29 1:13:26
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/3/29 8:28:16
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/3/29 7:41:19
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/3/24 20:11:18
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/3/29 9:57:23
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/3/29 0:49:46
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/3/24 20:11:15
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/3/27 7:12:50
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/3/24 20:11:13
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/3/26 11:21:23
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/3/28 18:26:34
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/3/28 12:42:28
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/3/28 20:09:10
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57