目录
使用g++编译C++代码
直接编译链接一步到位
输入下面命令在命令行
g++ hello.cpp -o hello
注意这个-o参数,直接把文件编译链接好了,前面的hello.cpp是你的源文件,后面的hello是你想要生成可执行文件的名字,这样就生成一个hello的可执行文件,在输入 ./hello就可以运行了,以下是图片展示
g++单独编译、单独链接
g++ -c hello.cpp 这是编译,生成一个hello.o的文件
g++ hello.o -o hello 这是链接,生成一个hello的可执行文件
makefile
刚学libevent网络库,刚配好文件,附上makefile代码
export LD_LIBRARY_PATH=/usr/local/lib/
firstlibevent:first_libevent.cpp
g++ $^ -o $@ -levent
./$@
clean:
rm -rf firstlibevent
rm -rf *.o
make
make main //直接编译了一个cpp文件,根据你的makefile来进行编译
在可执行文件后面加一个&表示新建一个shell执行(后台执行),比如我这个signal写的就是个死循环,加上&依然可以在当前的命令行输入命令
僵尸进程和孤儿进程的区别使用signal函数解决僵尸进程
僵尸进程
是父进程里有一个死循环,已经用不到子进程了,但是系统依然保存着子进程的信息。尽管保留的信息占用系统很少的资源,但是积少成多,也是会消耗一部分的资源的。
孤儿进程
父进程异常挂掉了,子进程还再运行,这个时候init进程(PID(进程ID号) 1的进程)来接管这个进程,所以叫这个子进程孤儿进程。init收养了他,init帮他进行善后
信号
也称为软件中断,可以由一个进程发送给另一个进程,也可以由内核发给某个进程。
孤儿进程有init帮他善后,僵尸进程要怎么处理呢?通过下面两个函数进行处理signal 和wait。推荐使用waitpid来处理僵尸进程,下面有讲,先介绍wait最简单的使用方法。代码如下
#include <sys/types.h>
#include <unistd.h>
#include<iostream>
#include<sys/wait.h>
#include <signal.h>
using namespace std;
static void sig_cld(int signo){
int pid;
int status;
cout<<"wait前进程ID"<<getpid()<<endl;
if((pid=wait(&status)) < 0){
cout<<"wait error"<<endl;
exit(0);
}
cout<<"wait后进程ID:"<<getpid()<<endl;
// exit(0);//子进程已经被杀死了,在杀就是杀死父进程了
}
int main(){
int data = 0;
cout<<"父进程 data = "<< data << " pid: "<< getpid() <<endl;
if(signal(SIGCHLD,sig_cld) == SIG_ERR){
cout<<"signal error"<<endl;
}
int pid = fork();
if(pid == 0){//pid=0才是子进程
cout<<"son run"<<endl;
cout<<"子进程 data = "<< ++data << " pid: " << getpid() <<endl;
exit(0);
}
cout<<"fork after"<<endl;//代码这里是后来加的
exit(0);
}
ps下面并没有出现子进程的ID。我们把wait注释掉再来看一下会发生什么事情?右边出现了defunct状态,这就是一个僵尸进程。这也证明了wait函数可以杀死僵尸进程,int wait(int* stat_loc),stat_loc把子进程退出进程状态信息存在这里面
当要杀的进程较多的时候,采用异步的wait函数——waitpid(pid_t pid,int* stat_loc,int options),第一个参数指的是等待pid的一个子进程,如果设置为-1,意思是等待任意的子进程,第二个参数和wait一样,第三个参数设置为WNOHANG,把此函数变成非阻塞函数(这个函数我并没有去测试,只是把书的知识总结了下来)
信号处理在内核没有一个缓冲区的,就是没有一个数组啊、队列呀记录信号发生,处理了就是处理了,没有处理就不再触发了,怎么解决呢?使用waitpid函数,wait的高级版。多了一个while循环处理,并设置为WNOHANG(告诉waitpid在有未终止的子进程在运行时不要设置为阻塞诶的),那为什么wait不能也加个while循环?因为wait函数无法设置WNOHANG这一参数
void child_ctl(int sign){
int pid ,stat;
while(pid = waitpid(-1,&stat,WNOHANG) >0 ){
cout<<"杀死该子进程"<< pid <<endl;
}
return ;
}
这里还需要补充一些知识,慢系统调用:适用那些可能永远阻塞的系统调用。当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可以会返回一个EINTR错误。比如我们在编写多进程的服务器的时候捕捉到了SIGCHLD信号,我们的accept函数就能出错,但这并不是错误,我们只需要continue;限于篇幅,只放伪代码了,注errno是系统定义好了的,不需要自己定义,关于accept还需要注意的是ECONNABORTED这个错误,暂时还没看到,UNP提了一嘴,看到了在更新
int client = accept(server_socket,NULL,NULL);
if(client < 0){
if(errno == EINTR){
continue;
}
}
open函数
使用open去打开一个文件,如果没有这个文件,第二个参数设置一下O_CREAT就可以帮你去创建了,关于后面的权限要说一下两种表示777的权限,但是实际创建出来的时候却不是777(见下图)
#include<string>
#include<iostream>
#include<fcntl.h>//read、close、open等等都在这个头文件下面
#include<unistd.h>//write在这个头文件下面
using namespace std;
int main()
{
//int open_fd = open("first_create.txt",O_RDWR | O_CREAT,0777);
int open_fd = open("first_create.txt",O_RDWR | O_CREAT,S_IRWXU | S_IRWXG | S_IRWXO);
if(open_fd == -1){
cout<<"文件打开失败"<<endl;
exit(0);
}
string s = "hello world";
int write_fd = write(open_fd,s.c_str(),s.size());
if(write_fd == -1){
cout<<"文件写入失败"<<endl;
exit(0);
}
exit(0);
}
为什么会是这个样子的呢?为什么不是777?这时候就跟umask有关了,查看一下umask,发现我们系统不希望其他人有读的权限,open创建的文件权限在跟系统的umask作一下比较再生成文件的权限!
[dxgzg@study IO]$ umask
0002
权限的说明
S_IRWXU,00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。
S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。
S_IXUSR 或S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。
S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。
S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。
S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。
S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。
S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。
S_IROTH 00004 权限,代表其他用户具有可读的权限
S_IWOTH 00002权限,代表其他用户具有可写入的权限。
S_IXOTH 00001 权限,代表其他用户具有可执行的权限。
权限的意义是从这篇博客拷贝过来的
fstat配合open函数
int fstat(int fd, struct stat *statbuf)函数原型,第一个参数是接受一个文件描述符,第二个参数是把这个fd的信息都保存在这里。如下代码,只用到了stat结构体一个参数,最近在写web服务器demo,计算发送文件的数据,fstat配合open函数感觉很棒!
string filepath = "/home/dxgzg/test_c++_code/XTcp/www
/index.html";//为了博客格式才换行的
int fd = open(filepath.c_str(),O_RDONLY);//以只读的方式打开
if(fd == -1){//这个可以加个404
cout<<"未找到该文件"<<endl;
return -1;
}
cout<<"打开了该文件"<<endl;
struct stat buf;
if(fstat(fd,&buf) == -1){//fstat函数
cout<<"fstat出错"<<endl;
return -1;
}
cout<<"文件大小为:"<<buf.st_size<<endl;
write函数和lseek函数
函数原型
ssize_t write(int fd, const void *buf, size_t count);
记一下自己犯的小错误,在学习libevent网络库,其中有个监控文件,我发现我手动打上去别不能监测到(视频教程给的是监测root登录的日志,但我在用普通用户权限登陆,没法监测),一开始我以为我的代码写错了?拿到root环境试试没有错误,那我就猜想,可能这个监测文件必须要用系统来打入,而不是手打进入(原凉我也不知道这里应该用什么专业术语),第一个想到的就是使用write来进行写入,刚接触linux1个月的我还没太熟悉各个API,导致出现了write乱码的错误
很简单的写入一个小程序,打开文件却发现乱码,一开始我以为是不是\0没有自动加?加上也不对,那是不是第三个参数传进去的值太小了?再加个1,还不对。最后发现问题了,const char* buff = “dxgzg”,第三个参数传进去的值却是sizeof(buff),传进去指针的字节大小去了,发现之后立马用strlen(buff)修改。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <thread>
#include <iostream>
#include <string.h>
using namespace std;
int main(){
int fd = open("listened.log",O_WRONLY,0);
if(fd <= 0){
cout<<"open failed"<<endl;
return -1;
}
lseek(fd,0,SEEK_END);//偏移到末尾
const char* buff = "dxgzg";
for(int i = 0;i < 5;++i){
int flag = write(fd,buff,strlen(buff));
//this_thread::sleep_for(2000ms);
}
return 0;
}
初识execl函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
函数原型,path是写可执行文件的路径(注意是可执行的路径),arg给可执行文件的参数(比如int main(int argc,char** argv)就是为了给这里传递参数)。返回值:成功了不返回,出错了返回-1。
举个简单的例子,看了好几篇博客,感觉很多人的例子不是摘自APUE(UNIX环境高级编程)要么就是一带而过,向我这样初识这个函数的小白,真没看懂他们写的。
如下代码,execl函数有一个路径(这里我写的是相对路径,在你的可执行文件下,使用pwd可以得到绝对路径),hello就是一个存放在他上一个目录下的Hello目录下的可执行的文件。
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main(){
printf("before execl\n");
cout<<"进程ID:"<<getpid()<<endl;
if(execl("../Hello/hello",nullptr) == -1)//execl函数
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
hello 只打印hello和他的进程ID
发现并没有输出”after execl“。引用APUE对exec系列函数的介绍
当进程调用一种函数时,该进程执行的程序被完全替换为新程序,而新程序则从其main函数开始执行,并没有创建新的进程,所以进程ID号是一样的(这个我们上诉的例子是证明了的),新的程序替换进程的正文段、数据段、堆段和栈段
sleep和usleep函数有区别的哦
sleep睡的是N秒,usleep睡的是N微秒,今天写程序sleep(200),等了好久,我说这等个200ms(我以为和windows一样是毫秒为单位),一直不知道怎么要这么久呀,检查代码发现没有问题,百度了一下,发现sleep是以秒为单位的,usleep是以微秒为单位的
sigalrm定时器
alarm进行定时,利用pause()暂停一下程序,然后到了时间触发sigalrm信号。alarm(0)后面跟pause()的时候,进程会唤不醒了,一直被挂起了。
#include <signal.h>
#include <unistd.h>
#include <iostream>
#include <setjmp.h>
using namespace std;
static void sig_alrm(int);
static jmp_buf env_alrm;
void sleep2(int seconds){
/*if(signal(SIGALRM,sig_alrm) == SIG_ERR){
cout<<"信号捕捉出错"<<endl;
return ;
}*/
alarm(seconds);
//alarm(0);
// pause();
}
static void sig_int(int);
int main(){
/*if(signal(SIGINT,sig_int) == SIG_ERR){
cout<<"sig_int error"<<endl;
return -1;
}*/
if(signal(SIGALRM,sig_alrm) == SIG_ERR){
cout<<"信号捕捉出错"<<endl;
return -1;
}
for(int i = 2;i < 10;++i){
sleep2(2);
}
return 0;
}
static void sig_alrm(int){
cout<<"信号被捕捉"<<endl;
//sleep2(5);
}
static void sig_int(int){
cout<<"中断"<<endl;
}
获取时间API
time函数
这个函数精度是到秒数。
time_t time_val = time(nullptr);//1970-01-01 00:00:00至今的秒数
cout<<time_val<<endl;
利用typeid来获取time_t类型
if(typeid(long) == typeid(time_val)){
cout<<"time_val is long"<<endl;
}
cout<<typeid(time_val).name()<<endl;
gettimeofday
这个函数比time精度高一点,这个可以精确到微秒,使用方法如下,muduo网络库使用的是这个函数
struct timeval tv,tv2;
struct timezone tz,tz2;
gettimeofday(&tv,&tz);//可以精确到微秒
cout<<tv2.tv_sec+(tv2.tv_usec)/1000000<<"s"<<endl;
stat配合sendfile函数
这个函数是零拷贝的函数,我还没有深入了解,这是我再搭建HTTP服务器用到的一个函数,对这个函数用法、理解不深刻,使用这个函数拿我的HTTP服务器来举例子把。
函数原型如下第一个参数是传个谁(HTTP服务器的话就是客户端的套接字),第二个参数可以是一个文件打开的fd,第三个参数我没有测试过,通过读man手册大概了解到这是值的你的偏移量,0也就是(nullptr)就是从头开始。否则就是从指定的偏移量发送数据,第四个参数是
ssize_t sendfile(int out_fd, int in_fd, off_t *offset,
size_t count);
伪代码如下,打开一个文件,利用fstat函数让stat对象来保存这个文件的属性(大小、偏移量、打开方式阿等等)。然后先把协议内容发送给客户套接字,然后再像客户套接字发送这个文件的描述符,大小就是用stat对象保存下来的文件大小。
int fd = open(filepath.c_str(), O_RDONLY);
if(fd < 0){
cout<<"fd error"<<endl;
break;
}
struct stat file_size;
fstat(fd,&file_size);
string msg = "HTTP/1.1 200 OK\r\n";
msg += "Content-Type: text/html\r\n";
msg += "Content-Length:";
msg += to_string(file_size.st_size);//注意别直接加
cout<< file_size.st_size<<endl;
msg += "\r\n\r\n";
send(client,msg.c_str(),msg.size(),0);
sendfile(client, fd, NULL, file_size.st_size);
dup函数复制文件描述符
fflush
dup函数复制现有的文件描述符,他们共享同一文件的状态标志。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
using namespace std;
int main(){
int file_fd = open("1.txt",O_CREAT | O_RDWR,0775);
cout<<"old file fd"<<file_fd<<endl;
//返回最小可用的文件描述符,一般是new_file + 1
int new_file_fd = dup(file_fd);
//注:尽管文件描述符是对的,但是这个描述符不可以写入文件。
//int new_file_fd = file_fd + 1;
cout<<"new file fd"<<new_file_fd<<endl;
int n = 0;
char buff[1024] = {0};
lseek(new_file_fd,0,SEEK_END);//每次都把输入偏移到末尾
while( (n=read(STDIN_FILENO,buff,sizeof(buff) ) ) ){ //STDIN_FILENO是标准输入,从键盘输入
if(write(new_file_fd,buff,n) < n){
cout<<"写入错误"<<endl;
}
}
return 0;
}
标准输出重定向到一个网络连接,在linux高性能服务器编程这本书看到的。
标准输出重定向到一个网络连接原理就是关闭服务器端的输出,这样printf的数据话就自动给客户端(telnet)发送过去了
不过书上的例子没有for循环,我一开始加上了for循环,telnet连接的话,并不会显示出hello语句,我感到很奇怪,后来猜测是因为数据在缓冲区没有发出来,使用fflush(stdout)让数据刷新一下即可解决该问题。数据缓冲区刷新,我参考的一篇博客
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <iostream>
using namespace std;
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( sock, 5 );
assert( ret != -1 );
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
cout<<"启动标准输出重定向到一个网络连接"<<endl;
while(1){
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
close( STDOUT_FILENO );
dup( connfd );
printf( "hello\n" );
fflush(stdout);
// close( connfd );
}
}
// close( sock );
return 0;
}
writev
iovec,把分散的缓冲数据聚合到一起,这样的好处是不用多次的read/write。
#include <iostream>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
using namespace std;
int main(){
int fd = open("1.txt",O_CREAT | O_RDWR,0775);
if(fd < 0){
cout<<"文件打开失败"<<endl;
return -1;
}
iovec vec[2];
vec[0].iov_base = const_cast<char*>("hello");
vec[0].iov_len = strlen("hello");
cout<<"strlen"<< strlen("hello") <<endl;
vec[1].iov_base = const_cast<char*>(" dxgzg!");
vec[1].iov_len = strlen(" dxgzg!");
int res = writev(fd,vec,2);
if(res == -1){
cout<<"errono: "<<errno<<endl;
return -1;
}
cout<<"写入文件成功"<<endl;
return 0;
}
umask(0)
这个函数是屏蔽文件权限用的,用open或者creat函数的时候别忘了添加权限,否则创造出来的文件会稀奇古怪的
sigaction函数
在一个demo中看到的这个函数,这个函数是signal的升级版。目前只局限于会怎么用,但是对他封装的struct的部分成员还不太了解
#include<signal.h>
#include<iostream>
#include <unistd.h>
using namespace std;
//constexpr SIGUSR1 = 1;
//constexpr SIGUSR2
void sig_usr(int signo){
if(signo == SIGUSR1){
cout<<"收到了usr1的信号"<<endl;
}
else if(signo == SIGUSR2){
cout<<"收到usr2的信号"<<endl;
}
else{
cout<<"啥东西,发错了把?"<<endl;
}
}
int main(){
// 用&在后台执行,然后通过PID,再另一个终端使用kill -usr1 PID即可
if(signal(SIGUSR1,sig_usr) == SIG_ERR){
cout<<"do not catch usr1"<<endl;
}
struct sigaction sigAction;
// 这个就相当于注册回调函数,或者说捕获到这个信号要怎么处理的函数。
sigAction.sa_handler = sig_usr;
// # define sa_handler __sigaction_handler.sa_handler
//sigAction.sa_handler = SIG_IGN;
// 这一步APUE说是必要的,加不加没看出了区别
sigemptyset(&sigAction.sa_mask);
//sigfillset(&sigAction.sa_mask); // 同上
// 这个标志可加可不加,设置一个标志
sigAction.sa_flags = SA_RESTART; // 设置一下中断自动重启
// 这个相当于signal函数
if( sigaction(SIGUSR2,&sigAction,nullptr) < 0 ){
cout << "error" << endl;
}
for(;;){
//pause();
}
return 0;
}
用&在后台执行,为了得到PID进程号
strpbrk
这个是用来判断是否含有其中某一个字符,有的话直接返回第一个匹配的字符的下标一直到为末尾的char或者const char,根据传进来的是const char还是not const决定的
#include <iostream>
#include <string.h>
int main(){
char* str = "GET / HTTP/1.1";
// 这个是用来判断是否含有其中某一个字符,有的话直接返回第一个匹配的字符的下标一直到为末尾的char*或者const char*,根据传进来的是const char还是not const决定的
char* ans = strpbrk(str," \t");
if(ans == nullptr){
cout << "ans is null" << endl;
}
else{
cout << str << endl;
}
return 0;
}
找不到新增加的库?
sudo ldconfig使用这个命令来刷新一下把!
sudo ldconfig