常用服务器构建
- 关于Ubuntu中文本编辑器vim的一个小问题:解决Ubuntu中vi命令的编辑模式下不能正常使用方向键和退格键的问题
- 关于虚拟机中的客户机操作系统的网络适配器设置选项,应选择桥接模式,这样该虚拟机中的客户机操作系统的ip就和主机Windows系统的ip在同一网段中但不相同,从而可使虚拟机操作系统和主机操作系统之间进行通信。而选择NAT模式使虚拟机和主机操作系统共享同一个ip。
ftp
服务器搭建步骤:
- 实名用户登录ftp的方式:
1. 安装 sudo apt-get install vsftpd //ubuntu中有很多种ftp服务器,此处选择安装vsftpd服务器
2. 配置 sudo vi /etc/vsftpd.conf
尽量不要手写内容到文件中。通过打开、关闭注释的方式调整该文件。
3. 启动 (重启——如果修改过配置必须重启)
sudo service vsftpd restart
4. 客户端发起链接
ftp 127.0.0.1(自己充当服务器、自己充当客户端)
ftp 192.168.36.xxx(对方IP)
5. 数据传递(上传、下载)
下载:get 非目录
上传:put 非目录
6. 关闭连接
quit/exit/bye
- 上述第四步客户端发起链接时需要输入服务器的用户名和登录密码,连上服务器之后,客户端可以自由地在服务器的文件系统中自由切换访问。这种服务器贡献密码并开放权限的做法不值得提倡。因此引出以下匿名登录ftp服务器的方式。
- 允许上传、下载,但仅开放指定服务器目录,并且不贡献服务器的登录密码。系统默认指定了一个服务器目录给匿名登录用户使用,在
/etc/passwd
中可查到默认ftp服务器的开放目录。 - 匿名用户登录ftp的方式:
1. 创建匿名用户登录位置。
cat /etc/passwd | grep ftp 查找本机默认指定 匿名用户登录位置。——/srv/ftp
2. sudo mkdir /srv/ftp/incoming
//此处之所以要在默认登录文件夹位置里再创建一个子文件夹,
//是因为默认位置文件夹的权限限制用户ftp对该目录进行写操作,后果是不能使用put上传文件到该服务器目录
//默认文件夹的权限见下图
//虽然简单粗暴的方法是修改这个默认登录文件夹的权限对同组用户开放写权限,
//不过更好的方式是在该目录下新建一个为用户ftp所有的子文件夹,
//以后每次登录上ftp服务器后都对应地进入默认登录文件夹下的指定子文件夹进行上传和下载操作。
//这样,当多个用户连接上同一个ftp服务器时各自对应操作不同的文件夹,更有条理。子文件夹权限设置见以下第三步。
3. sudo chown ftp:nogroup /srv/ftp/incoming ——所有者:ftp 所属组:nogroup
//此处是修改incoming文件夹的权限为用户ftp所有,见下图
4. 修改配置文件,给匿名用户开放各种权限。/etc/vasftpd.conf
5. sudo service vsftpd restart
6. ps aux | grep ftp ——查询结果应为2+ 才表示启动成功
7. ftp 127.0.0.1 ——用户名:anonymous 密码:回车
8. 默认匿名用户登录上以后在/srv/ftp(默认)
9. cd incoming (注意目录权限)
10 put 上传 get 下载。
其中,ftp用户拥有第二组(同一组内)权限。
其中,ftp用户拥有第一组(所有者)权限。
- 但是。ftp服务器只能上传和下载文件而不能操作不能文件夹,因此引出 lftp 服务器。
lftp
- lftp服务器搭建和使用步骤:
lftp使用:
1. sudo apt-get install lftp 安装lftp客户端
2. 配置vsftpd.conf(与ftp共享同一个配置文件)
3. restart
4. 登录lftp 127.1
5. login itcat/anonymous (匿名用户可省略该步)
6. mirror -R 上传目录
7. mirror 目录下载
8. bye
另外,lftp 下的中文默认使用UTF-8,可正确显示中文;而ftp 下的中文为乱码。因此,推荐使用 lftp。
- 上传位置:ftp或lftp登录后,cd到哪个目录执行put/mirror -R 就上传至哪个目录。
- 下载位置:默认情况从哪个目录执行了ftp 127.0.0.1 就get到哪个目录,
可以使用lcd 改变本地机的工作目录位置。
lcd …可以修改至上级目录
!ls 列出本地机列表项;
!pwd 查看本地机工作目录位置。
nfs
nfs使用流程:
1.安装:sudo apt-get install nfs-kernel-server
2.创建一个欲共享出去的目录 如:/home/itcast/itcast
3.打开配置文件:sudo vi /etc/exports
4.写入共享目录的绝对路径,及对应权限。如:
/home/itcast/itcast *(ro,sync,no_root_squash)
5.重启nfs
sudo /etc/init.d/nfs-kernel-server restart
-------------------------
6.客户端访问共享目录:
sudo mount 192.168.43.75:/home/itcast/itcast/ /mnt
7. cd 到本机的/mnt 目录中就可以使用cp命令得到目录中的所有文件。
【常见错误】:1. 虚拟机下出现“不能mount实体”错误时,需在mount之前使用
install nfs-common 即可解决。
2. 出现“超级坏块儿”错误无法挂载,多是因为没有安装nfs-kernel-server导致。
ssh
scp
- 其实不是一种服务器,而是一条远程拷贝命令。
对比语法格式:
nfs:sudo mount 192.168.43.75:/home/itcast/itcast/ /mnt
ssh: ssh itcast@192.168.43.75 ——>yes 密码
scp -r itcast@192.168.43.75:/home/itcast/aa ../
- nfs 和 ssh 服务器的使用需要知道对方主机的登录密码。而scp命令不需要,而且也不需要搭建服务器即可传数据。
telnet
- 明文传输二进制数据流,不安全,不推荐使用。
开发工具
gcc(编译器)
在这四个过程中,编译过程耗时最长,因为编译要逐条检查语法错误,然后和标准语法比对。
若程序包括自己写的头文件但该头文件不在程序所在的同一个文件夹下,就使用 -I 参数后接这个自定义头文件所在的文件夹。
objdump -dS后接目标文件(.o)或可执行文件(.out)可反汇编二进制文件为汇编程序。
- 可执行程序(.out)运行起来后被加载到内存中,那么可执行程序在内存中的布局是怎么的呢?
上图是32位内存空间的布局:
最上面为内核区(kernel),下面为用户区(user)。用户无权访问内核区。对于用户区,从低地址开始向上:
最下面为ELF header,记录ELF文件的属性信息;
上面为.text区,即代码段,存放代码(只读);
上面为ordata区,存放常量(只读);
上面为.data区,即数据段,存放全局变量;
上面为.bss区,存放未初始化的全局变量和静态局部变量;
上面为堆区,生长方向为从低地址到高地址;
用户区最上面为栈区,生长方向为从高地址向低地址。
当栈和堆生长到彼此接触时,则表明发生了内存溢出!
栈中存放的是一个个栈帧,即函数调用的存储子空间,栈帧中具体存放的是被调用函数的局部变量和临时值,临时值保存的是被调函数的返回地址,被调函数的形参(若有)和其内部的局部变量等价。
静态库
- 函数库:一组功能相近或者操作同一个数据结构的函数的结合。发布形式分为源码形式(开源)和二进制文件形式,后者更常见。
- 二进制库的优点是免去编译过程,而且可隐藏内部实现细节。分静态库和动态库(共享库)。
- 机制:复制静态库作为程序代码段的一部分。
- 优点:将函数库中的函数本地化。寻址方便,速度快。(库函数调用效率 == 自定义函数使用效率,达到直接跳转)
- 缺点:消耗系统资源较大。每个进程使用静态库都要复制一份。无端浪费内存
- 使用场景:多应用于核心程序,保证时效性,可以忽视空间。
- 静态库发布形式为二进制文件(.o),并且静态库的命名规范为:lib开头,.a结尾
- 制作静态库:
1. gcc add.c sub.c mul.c -c ——>得到*.o
2. ar rs libmymath.a add.o sub.o mul.o ——>得到静态库
ar工具不包含在gcc中
r更新、c创建、s建立索引
file libmymath.a ——>查看库信息
3. gcc main.c -L ./ -l mymath -I ./ -o app ——>使用静态库
使用:
L:指定静态库所在目录位置;
l:指定静态库名字(此处为去掉lib前缀和.a后缀后的名字)
I:指定头文件所在目录位置
- 比如有三个子文件add.c、sub.c、mul.c,和main.c联合编译生成一个可执行程序:
gcc add.c sub.c mul.c main.c -o app
而把前三个子文件制作成一个静态库以后就可以借助这个静态库和main.c一起联合编译了。
动态库(共享库)
- 机制:共享代码(不共享数据,比如在动态库文件中存在变量和宏的时候)
- 优点:节省内存(共享)、易于更新(动态链接)
多个程序共享同一份动态库;当可执行文件a.out开始运行时才加载动态库。
注意:动态库加载到内存中的位置是在栈和堆之间的一块内存区域中。 - 缺点:相较于静态库函数调用速度略慢(延迟绑定机制)
- 使用场景:
(1)对程序执行速度要求不是很强烈,而相对于系统资源有一定要求的场景
(2)对于更新比较频繁的程序
当可执行文件在运行时,它所加载的动态库发生修改时:
1)停止运行程序
2)使用新库覆盖旧库(保证新旧库名称一致,接口一致) “接口”
3)重新启动程序。 - 注意:
(1)动态库是否加载到内存,取决于程序是否运行
(2)动态库每次加载到内存的位置不固定(地址是相对地址)----动态链接器(工作在程序运行期间,区别于链接器,它工作在程序编译期间) - 制作动态库:
1. gcc -fPIC add.c mul.c sub.c -c (-fPIC:生成“与位置无关”的目标文件*.o)
2. gcc -shared -o libmymath.so add.o mul.o sub.o
3. gcc main.c -L库路径 -l库名 -I头文件名 -o app
4. ./app --> 出错
原因:动态连接器 ld-linux.so.2 搜寻动态库的路径未指定,
执行ldd app发现动态链接器找不到 libmymath 库。
指定方法(推荐第3、4种方法):
1). 环境变量法:export LD_LIBRARY_PATH=./ 将当前目录加入环境变量,但是终端退出了就无效了。
2). 配置文件法:将上条写入家目录下.bashrc文件中 (永久生效,设置到~/.bashrc)
3). 拷贝法:直接将libmymath.so文件拷贝到/usr/lib/目录下。
(受libc库的启发,因为动态链接器在搜索标准C库时会去这个目录下找,而且也说明标准C库也是一个动态库)
4). 缓存文件法:将libmymath.so所在绝对路径加入到/etc/ld.so.conf文件(保存动态链接器默认搜索动态库的路径),
使用sudo ldconfig -v 动态更新/etc/ld.so.cache文件(2进制文件,
它是实际真正影响动态库加载路径的文件,但由于是二进制文件,不方便直接修改它,因此通过修改上面这个.conf文件来影响它。)
动态库的规范使用方法:
5. 指定动态库的soname
gcc -shared -Wl,-soname,libmymath.so.1 -o libmymath.so.1.10 add.o mul.o sub.o
soname的作用是隐藏库的实现细节,不让用户知晓版本调整细节信息。
6.创建动态库的LinkerName
ln -s libmymath.so.1.10 libmymath.so
也即创建动态库的软链接,在使用这个动态库联合编译时就是用的是它的软链接名。
- 动、静态库共存时:
1)编译器默认使用.so的动态库,找不到才使用静态库。
2)-static可以直接指定使用静态库
3)注意比较加了static和没有加static的可执行文件大小,及内部printf的地址。显然是加了static(使用静态库)的可执行文件更大。
gdb(调试工具)
- 使用场景:程序编译无误,但是有逻辑错误,即程序无语法错误但运行结果不正确。
- 使用参数:
1)首先在前一步gcc编译时要加-g 参数,这样得到的可执行程序文件包含调试信息,才能把生成的可执行程序xxx用于gdb调试 —> gdb xxx
2)list或简写为l : 列出程序源码
3)b 行号:设置断点所在行号
4)info b : 查看断点信息
5)disable/enable 断点号 : 设置断点非使能/使能
6)delete 断点号: 删除断点
7)b 行号 if i=5 : 设置条件断点,只有i=5时断点生效
8)run:全速执行程序,若程序无错误,会很快结束,但若有错,则会停在出错处。比如,编译时出现段错误(segmentation fault),执行run命令,会很方便地知道出现段错误的地方。
9)start:从首行执行,单步调试
10)p 变量名——查看变量值
11)display——跟踪变量
12)undisplay——取消跟踪
13)ptype 变量名——查看变量类型
14)bt : 查看当前程序函数栈帧使用基本情况
函数的栈帧保存局部变量和临时值,形参与局部变量等价。
15)info locals: 查看当前栈帧上变量的存储值
16)frame 栈帧编号: 查看对应栈帧中变量的值
17)s——step,进入到调用子函数内部继续跟踪
18)n——next,跳过调用子函数继续调试
19)finish : 终止当前函数
20)continue 结束当前断点调试,跳到下一个断点(若有)
21)quit:退出gdb调试 - 总结:
命令 | 简写 | 作用 |
---|---|---|
help | h | 按模块列出命令类 |
help class | 查看某一类型的具体命令 | |
list | l | 查看代码,可跟行号和函数名 |
quit | q | 退出gdb |
run | r | 全速运行程序 |
start | 单步执行,运行程序,停在第一行执行语句 | |
next | n | 逐过程执行 |
step | s | 逐语句执行,遇到函数,跳到函数内执行 |
backtrace | bt | 查看函数的调用的栈帧和层级关系 |
info | i | 查看GDB内部局部变量的数值,info breakpoints |
frame | f | 切换函数的栈帧 |
finish | 结束当前函数,返回到函数调用点 | |
set | 设置变量的值set var n=100 | |
run argv[1] argv[2] | 调试时命令行传参 | |
p | 打印变量和地址 | |
break | b | 设置断点,可根据行号和函数名 |
delete | d | 删除断点d breakpoints NUM |
display | 设置观察变量 | |
undisplay | 取消观察变量 | |
continue | c | 继续全速运行剩下的代码 |
enable breakpoints | 启用断点 | |
disable breakpoints | 禁用断点 | |
x | 查看内存x /20xw 显示20个单元,16进制,4字节每单元 | |
watch | 被设置观察点的变量发生修改时,打印显示 | |
i watch | 显示观察点 | |
core文件 | ulimit -c 1024 开启core文件,调试时gdb a.out core |
makefile
- 它是一个脚本文件,其中包含程序编译期间使用到的一系列可执行命令集合,批量执行以提高程序编译效率。作用是项目编译管理、提高编译效率、降低编译出错概率。这个文件必须命名为
makefile
或Makefile
。里面都是一组一组的规则:目标、依赖条件、命令。Makefile文件的目标是在不改变该脚本文件的前提下,当源文件发生变化时,如修改内容、增加源文件、删除某些源文件等,也可以成功编译运行,这依赖于以下几个关键点:
vim编辑Makefile出现missing separator的解决方法 - 一个规则:
目标:依赖条件
(一个tab缩进)命令
// 1)目标的更新时间必须晚于依赖条件的更新时间,否则更新目标;
// 2)依赖条件如果不存在,则找寻新的规则去生成这个依赖条件。
//例如:
a.out:hello.o
gcc hello.o -o a.out
hello.o:hello.c
gcc -c hello.c -o hello.o
make
命令的默认规则是把Makefile文件中的第一个规则的目标作为整个文件的终极目标。不过可以使用ALL命令打破这个默认规则,用它来制定终止目标:
ALL
:后跟makefile文件的终极目标
- 两个函数:
wildcard
函数:src = $(wildcard *.c)
: 找到当前目录下所有后缀为.c 的文件,赋值给src
patsubst
函数:obj = $(patsubst %.c, %.o, $(src))
:把src变量里所有后缀为.c 的文件替换为.o(更一般的说明是,将参数3中所包含的参数1的部分替换为参数2)
另:clean
命令:详见下文
例如:
//在当前目录下有文件:add.c sub.c div.c arith.c,将它们编译生成一个可执行程序:
src = $(wildcard *.c) #src = add.c sub.c div.c arith.c
obj = $(patsubst %.c, %.o, $(src)) #obj = add.o sub.o div.o arith.o
a.out:$(obj)
gcc $(obj) -o a.out
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div.o:div.c
gcc -c add.c -o add.o
arith.o:arith.c
gcc -c arith.c -o arith.o
- 三个自动变量:
$@:在规则的命令中,表示规则中的目标
$<:在规则的命令中,表示规则中的第一个依赖条件。
若应用在模式规则中,它可将依赖条件列表中的各个依赖依次取出,套用模式规则。
$^:在规则的命令中,表示规则中的所有依赖条件,组成一个列表,以空格隔开。
如果这个列表中有重复的项则消除重复项。
//即,这三个自动变量不能出现在规则中的目标和依赖条件的位置,只能出现在命令中。
例如:
//在上例中的几个规则可以改为:
a.out:$(obj)
gcc $^ -o $@
add.o:add.c
gcc -c $< -o $@ #$<也可换为$^;但在模式规则中,$<更好。
sub.o:sub.c
gcc -c $< -o $@
div.o:div.c
gcc -c $< -o $@
arith.o:arith.c
gcc -c $< -o $@
1)makefile文件若采用硬编码方式编写,则灵活性较差,应采用软编码方式,即使用变量和函数。其中,变量分为手动变量和自动变量。
2)手动变量
在Makefile中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单。
//变量定义和使用:
//1、变量定义直接用'='
//2、使用变量值用$(变量名)
foo = abc
bar = $(foo) //索引变量值
//手动定义了两个变量:foo、bar,其中bar的值是foo变量值的引用。
- 模式规则:
至少在规则的目标定义中要包含%
:它表示一个或多个。
例如:在上例中,对后四条一样的规则可改写为如下模式:
%.o:%.c
gcc -c $< -o $@
在依赖条件中同样可以使用%
,依赖条件中的%
的取值,取决于其目标。比如:
//以下列形式代替多组相同形式的规则
%.o:%.c
%:%.c
$(target):%:%.c //静态模式规则,针对某个集合来使用该组模式,比如:
$(obj):%.o:%.c
$(CC) -c $< -o $@ #其中CC变量的值为 :gcc
//上句静态模式规则的含义是,把变量obj中的每个成员都用这条模式规则来生成
$(CC) –c $(CFLAGS) $(CPPFLAGS) $< -o $@
//其中,“$@”表示依次取出目标值,$<表示依次取出依赖条件。
//注意:只有写成模式规则的时候,$<才表示了所有依赖条件的依次取值
//否则只是取依赖条件中的第一个。
- clean:没有依赖,只有命令
- 用途:清除编译生成的中间.o文件和最终目标文件
用法:例如,在makefile文件中插入这样一行:
clean:
-rm -rf $(obj) a.out
则,只需在命令行输入:make clean
即可运行这条命令,删除当前目录中所有已生成的.o文件和a.out文件(这里$(obj)即上例中表示几个.o文件的自动变量)
- make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令
- 伪目标声明:
.PHONY:clean
所谓伪目标,是makefile中无论其依赖条件满足或不满足,该规则都会执行,这样就可以克服同名clean文件的干扰造成不能执行makefile中的clean命令的问题。.PHONY:
后一般跟clean
和ALL
。 - clean命令中的特殊符号
1)“-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o
”
2)“@”不显示命令本身,只显示结果。如:“@echo”clean done
”
-
其它
- ALL:指定最终要生成的目标
- make -n:只打印要执行的命令,即模拟命令的执行,不会真正执行命令
- make -f:执行一个名称为非
makefile/Makefile
的文件 - make 默认执行第一个出现的目标,可通过make dest指定要执行的目标
- distclean目标
- install目标
- make -C 指定目录进入指定目录,调用里面的Makefile
-
常见C项目结构:
第三方库
-
libevent
:在网络编程中最常见的一种跨平台的第三方开源库。
1)开源库使用方式:- 执行命令:
./configure
,检查当前主机环境是否适合安装,同时会自动生成一个makefile
文件; make
:编译当前这个库sudo make install
:把这个动态库和所需的一些指定文件拷贝到指定目录下。(应用级程序可能对某些目录没有访问权限,因此要加sudo
)
注意:打开一个库的源码文件的第一件事是先看README
文件。- 有一个名为
sample
的文件夹,其中是例子程序,可先从这些例子程序入手,遇到陌生函数时再看该函数源码;ls *.c
可得到所有的源码文件;ls -a
找到名为.libs
的隐藏目录,其中包括编译好的.o
库文件。 - 使用库文件:
gcc *.c -L 库路径 -l 库名 -I 头文件名
2)开源库学习方法:
读源码,学习其设计思想、编码风格。先看README
和sample
文件夹中的例子程序。 - 执行命令:
-
libev
:弥补了libevent
库对多线程的支持。 -
nginx
:web服务器 -
webbench
:压力测试库