linux系统编程(全)

linux系统编程

一、文件编程

1、文件的打开及创建

1.1open函数头文件

       #include 
       #include 
       #include 
1.2open函数原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);	
1.3函数参数说明
pathname:要打开的文件名(含路径)

flags : 

 O_RDONLY 只读打开,O_WRONLY 只写打开,O_RDWR 可读可写打开

当我们附带了权限后,打开的文件就只能按照这种权限来操作。

以上这三个参数中应当只指定一个。下列参数是可选择的:

1.O-CREAT    :若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode.用其说明该新文件的存取许可权限。
2.O_EXCL      :以这种属性去打开文件时,如果同时指定了O_CREAT,而文件已经存在,则打开文件失败(也可以说返回值是-1)。
3.O_APPEND :以这种属性去打开文件时,每次写时都加到文件的尾端。(不想写入时文件被覆盖,用这个flag,正常写入没有加其他条件的话,原来文件中的数据内容会被覆盖,注意是覆盖,覆盖本身的字节长度,没有覆盖的会保留,不是删除)
4.O_TRUNC   :以这种属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0,通俗点的意思就是把原来的文件中的内容干掉,写入你自己要的数据内容
Mode:一定是在flags中使用了 O-CREAT 标志,mode 记录待创建的文件的访问权限,(比如:给的是0600时,则文件所有者给的权限是 可读可写
1.4open函数概述
DESCRIPTION
Given  a  pathname  for  a  file,  open() returns a file descriptor, a small, nonnegative integer for use in subsequent system calls(read(2), write(2), lseek(2), fcntl(2), etc.).  The file descriptor returned by a successful call will be the  lowest-numbered  file descriptor not currently open for the process.

给定一个文件的路径名,open()返回一个文件描述符,一个小的非负整数,用于后续的系统调用(read(2), write(2), Iseek(2), fcntl(2),等等)。成功调用返回的文件描述符将是进程当前未打开的编号最低的文件描述符。失败:返回 -1
1.5打开一个文件(文件存在时)

#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
 
        fd = open("./file1",O_RDWR);
 
        printf("fd = %d \n",fd);
 
        return 0;
}
1.6打开一个文件,如果需要打开的文件不存在,那么创建该文件。
【在参数flags中使用了O_CREAT标志,参数mode中要给出文件的访问权限0600(或者其他),这两个是配着使用的】
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
 
        fd = open("./file1",O_RDWR);
 
        if(fd == -1){
                printf("open file1 fail \n");
                                                   
                fd = open("./file1",O_RDWR|O_CREAT,0600);
                
                if(fd > 0){
                        printf("creat file1 success \n");
                }
        }
        
        printf("open file1 success:fd = %d \n",fd);
    
        return 0;
}
参数mode为0600的意思

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jApwXxxg-1688607361410)(…/…/…/…/…/…/Pictures/Camera Roll/0600.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiUuY8i8-1688607361411)(…/…/…/…/…/…/Pictures/Camera Roll/1.jpg)]

对应两个图看:即可知道0600是让我们以文件所有者的身份给了file1文件可读可写的权限。

开头的第一个 - 是普通文件的意思

1.7O_EXCL的使用

以这种属性去打开文件时,如果同时指定了O_CREAT,而文件已经存在,则打开文件失败(也可以说返回值是-1)。

#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
 
        fd = open("./file",O_RDWR|O_CREAT|O_EXCL,0600);
 
        if(fd == -1){
                printf("file1 exist \n");
        }
 
        return 0;
}
1.8O_APPED的使用

以这种属性去打开文件时,每次写时都加到文件的尾端。(不想写入时文件被覆盖,用这个flag,正常写入没有加其他条件的话,原来文件中的数据内容会被覆盖,注意是覆盖,覆盖本身的字节长度,没有覆盖的会保留,不是删除)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "wenjian chu ru men !";
 
        fd = open("./file1",O_RDWR|O_APPEND);   //在open添加
 
        //ssize_t write(int fd, const void *buf, size_t count);
        int n_write =  write(fd,buf,strlen(buf));
        printf("write %d byte to file1 \n",n_write);
 
                                                                                                                    
        close(fd);
 
        return 0;
}
     
1.9 O_TRUNC的使用

以这种属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0,通俗点的意思就是把原来的文件中的内容干掉,写入你自己要的数据内容

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "test!";
 
        fd = open("./file1",O_RDWR|O_TRUNC);   //在open添加
 
        //ssize_t write(int fd, const void *buf, size_t count);
        int n_write =  write(fd,buf,strlen(buf));
        printf("write %d byte to file1 \n",n_write);
 
                                                                                                                    
        close(fd);
 
        return 0;
}     
1.10creat函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUaj4TpR-1688607361412)(…/…/…/…/…/…/Pictures/Camera Roll/2.jpg)]

#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
 
        fd = creat("./wenjian",S_IRWXU);
 
 
        return 0;
}

2、文件的写入操作

2.1.包含的头文件
 #include 
2.2函数原型
ssize_t write(int fd, const void *buf, size_t count);
2.3函数参数说明
int fd :文件描述符
const void *buf :一个无类型的指针buf,是一个缓冲区
size_t count:你要写入文件的大小

【整一个函数的意思是:将缓冲区 buf 这个指针指向的位置的内存中的数据,写多少个字节,写到刚打开的文件 fd 里面去。】

2.4write函数描述
DESCRIPTION
       write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.
       write()从指向文件描述符fd引用的文件的缓冲区buf写入字节数。
2.5函数返回值
RETURN VALUE
       On  success,  the  number of bytes written is returned (zero indicates nothing was written).  On error, -1 is returned, and errno is set appropriately.
       如果成功,将返回写入的字节数 (0表示没有写入任何内容)。出现错误时,返回-1,并适当地设置errno
2.6实现demo
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "wenjian chu ru men !";
        
        fd = open("./file1",O_RDWR);   //打开一个file1的文件
 
        if(fd == -1){
                printf("open file1 fail \n");           
                fd = open("./file1",O_RDWR|O_CREAT,0600);  //如果file1不存在,就创建它 
                if(fd > 0){
                        printf("creat file1 success \n");
                }
        }
 
        printf("open file1 success: fd = %d \n",fd);
 
        write(fd,buf,strlen(buf));    //将buf里面的内容写入到fd里面去。计算字符串大小用strlen
 
        close(fd);      //用open打开一个文件,操作结束了就要关闭close
                                                                                         
        return 0;
}

3、文件读取操作

3.1包含的头文件
    #include 
3.2函数原型
ssize_t read(int fd, void *buf, size_t count);
3.3函数参数说明
int fd :文件描述符
const void *buf :一个无类型的指针buf,是一个缓冲区
size_t count:你要读取文件的大小

【整个函数的意思是:从 fd 指向的文件里面的数据,读取多少个字节数据到缓存区 buf 里面去。】

3.4实现demo
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "wenjian chu ru men !";
 
        fd = open("./file1",O_RDWR);        //打开文件
 
        if(fd == -1){
                printf("open file1 fail \n");
                fd = open("./file1",O_RDWR|O_CREAT,0600);        //如果没有文件,创建文件
                if(fd > 0){
                        printf("creat file1 success \n");
               }
        }
 
        printf("open file1 success: fd = %d \n",fd);
 
        //write函数原型:ssize_t write(int fd, const void *buf, size_t count);
        int n_write =  write(fd,buf,strlen(buf));
        printf("write %d byte to file1 \n",n_write);
 
 
        char *readbuf;
        readbuf = (char *)malloc(strlen(buf)+1);
        //read函数原型:ssize_t read(int fd, void *buf, size_t count);
        int n_read = read(fd,readbuf,n_write);
        printf("read %d byte,context:%s\n",n_read,readbuf);
 
 
        close(fd);
      
        return 0;
}

注意:上述代码是无法读取数据的,你们知道是什么原因吗?

原因是在我们write写操作之后,光标已经移动到了数据最末尾的位置,(就像你用word文档敲完一句话之后,那个光标总是停留在最后的位置),这里是同样的道理。所以当我们read读操作的时候,总是在末尾读数据,单数尾巴没有数据,所以啥也读不到。

解决办法:

1.将光标移到头

2.关闭文件,重新打开再读取(这种方法不符合人类的常规操作,一般不用!当然你要用也可以,但是不建议)

下面介绍第二种方法:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "wenjian chu ru men !";
 
        fd = open("./file1",O_RDWR);            //打开文件
 
        if(fd == -1){
                printf("open file1 fail \n");
                fd = open("./file1",O_RDWR|O_CREAT,0600);        //如果没有文件,创建文件
                if(fd > 0){
                        printf("creat file1 success \n");
               }
        }
 
        printf("open file1 success: fd = %d \n",fd);
 
        //write函数原型:ssize_t write(int fd, const void *buf, size_t count);
        int n_write =  write(fd,buf,strlen(buf));
        printf("write %d byte to file1 \n",n_write);
 
 
        close(fd);                        //关闭文件
 
        fd = open("./file1",O_RDWR);      //打开文件,这两步解决write写操作光标移动到末尾的问题(土方法,不常用)
 
 
        char *readbuf;
        readbuf = (char *)malloc(strlen(buf)+1);
        //read函数原型:ssize_t read(int fd, void *buf, size_t count);
        int n_read = read(fd,readbuf,n_write);
        printf("read %d byte,context:%s\n",n_read,readbuf);
 
 
        close(fd);
      
        return 0;
}

4.文件光标移动操作

4.1包含的头文件
 #include 
 #include 
4.2函数原型
off_t lseek(int fd, off_t offset, int whence);
4.3函数参数说明
int fd :文件描述符
off_t offset:偏移多少个字节
int whence:光标偏移位置

【整个函数的意思是:将文件读写指针相对whence移动offset个字节位置】

4.4lseek函数描述
给whence参数设定偏移位置:

SEEK_SET:光标偏移到头

SEEK_CUR:光标为当前位置

SEEK_END:光标偏移到末尾
4.5函数返回值
成功完成后,Iseek()返回从文件开始的偏移位置(以字节为单位)【就是返回偏移了多少个字节】。发生错误时,返回值(off_t) -1,并设置errno来指示错误
4.6运用lseek移动光标(就是上节课的第一种解决方法,推荐用这个,因为这个才符合人类的思维)

open打开文件:此时的光标是在头位置。

write写入操作:写入操作后,光标的位置在尾巴。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
        char *buf = "wenjian chu ru men !";
 
        fd = open("./file1",O_RDWR);        //打开文件
 
        if(fd == -1){
                printf("open file1 fail \n");
                fd = open("./file1",O_RDWR|O_CREAT,0600);        //如果没有文件,创建文件
                if(fd > 0){
                        printf("creat file1 success \n");
               }
        }
 
        printf("open file1 success: fd = %d \n",fd);
 
        //write函数原型:ssize_t write(int fd, const void *buf, size_t count);
        int n_write =  write(fd,buf,strlen(buf));
        printf("write %d byte to file1 \n",n_write);
 
 
        lseek(fd,0,SEEK_SET);     //将光标移动到头后,相对头偏移0个字节位置
 
        char *readbuf;
        readbuf = (char *)malloc(strlen(buf)+1);
        //read函数原型:ssize_t read(int fd, void *buf, size_t count);
        int n_read = read(fd,readbuf,n_write);
        printf("read %d byte,context:%s\n",n_read,readbuf);
 
 
        close(fd);
      
        return 0;
}
4.7几种lseek移动光标的操作方法
lseek(fd,0,SEEK_SET);                   //将光标移动到头后,相对头偏移0个字节位置(常用一种就好啦)
lseek(fd,0,SEEK_END;                   //常用于计算文件大小
lseek(fd,-20,SEEK_END);              //将光标移动到尾巴后,相对尾巴向头偏移,也就是往前偏移20个字节位置
lseek(fd,-n_write,SEEK_END);      //将光标移动到尾巴后,相对尾巴向头偏移,也就是往前偏移写入操作write之后的(返回值,实际就是20)个字节位置
lseek(fd,-20,SEEK_CUR);             //将光标移动到当前位置(上面代码也就是尾巴),相对当前位置(尾巴)向头偏移,也就是往前偏移20个字节位置
lseek(fd,-n_write,SEEK_CUR);    //将光标移动到当前位置(上面代码也就是尾巴),也就是往前偏移写入操作write之后的(返回值,实际就是20)个字节
4.8利用lseek函数的返回值,计算文件的大小:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        int fd;
 
        fd = open("./file1",O_RDWR);
 
        // off_t lseek(int fd, off_t offset, int whence);
        int n_lseek = lseek(fd,0,SEEK_END);
 
        printf("file1's size is %d \n",n_lseek);
 
        close(fd);
                                                                                              
        return 0;
}

5、文件操作原理

5.1文件描述符

1、对于内核而言,所有打开的文件都由文件描述符引用。文件描述符是一个非负整数,当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用 open 和 creat 返回的文件描述符标识该文件,将其作为参数传递给 read 和 write。

按照惯例,UNIX shell 使用文件描述符0与进程的标准输入相结合、文件描述符1与标准输出相结合、文件描述符2与标准错误输出相结合。STDIN_FILENO、STDOUT_FILENO、 STDERR_FILENO这几个宏代替了 0、1、2 这几个数。

2、文件描述符,这个数字在一个进程中表示一个特定的含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们的应用程序如果想要操作这个动态文件,它只需要用这个文件描述符区分。

3、文件描述符的作用域是当前进程,超出当前进程,文件描述符就没有意义了。

open函数打开一个文件,打开成功返回一个文件描述符,打开失败返回-1。

5.2对于标准输入0和标准输出1的加深理解

标准输入:从键盘输入

标准输出:输出从键盘输入的内容

#include 
#include 
#include 
 
int main()
{
        
        char readBuf[128];
 
        read(0,readBuf,5);                    0 也可以写成宏 表示从键盘输入(这句代码的意思是:读取从键盘输入的数据,不超5个字节,到readBuf这个缓冲区里面去)
    
        write(1,readBuf,strlen(readBuf));     1 也可以写成宏 表示输出到界面(这句代码的意思是:从键盘输入的数据readBuf中,写入数据到输出界面去)  
         
 
        return 0;
}
5.3linux文件操作步骤

1、在Linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。

2、强调一点:我们对文件进行操作时,一定要先打开文件,只有操作成功后才能操作,如果打开失败,就不用进行后面的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。

3、文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,Linux内核做的操作包括:内核在进程中建立一个打开的文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定的地址管理存放(叫动态文件)。

4、打开文件后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而不是针对静态文件的。当我们对动态文件进行读写以后,此时内存中的动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)的块设备中的静态文件。

5、为什么是这样设计,不能直接对块设备直接操作。

块设备本身读写非常不灵活,是按块读写的。而内存是按字节单位操作的,并且可以随机操作,很灵活

6、文件操作小应用之实现cp指令(了解main函数参数的意义)

6.1.mian函数参数的用法
#include 
 
int main(int argc,char **argv)
{
        printf("total params:%d\n",argc);
        printf("No.1 params :%s\n",argv[0]);
        printf("No.2 params :%s\n",argv[1]);
        printf("No.3 params :%s\n",argv[2]);
 
        return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTbbxiO2-1688607361413)(…/…/…/…/…/…/Pictures/Camera Roll/3.jpg)]

从结果看出:

argc :代表的是 ./a.out argc argv 这三个参数的个数

argv[0] :代表第一个参数./a.out

argv[1] :代表第二个参数 argc

argv[2] :代表第二个参数 argv

由此可见argv是数组的数组。

6.2根据main函数参数的意义:拷贝文件编程思路及代码:

我把源文件定为src.c,目标文件定位des.c

思路:1.打开源文件src.c

​ 2.读src到buf

​ 3.打开/创建目标文件des.c

​ 4.将buf写入des.c

​ 5.close两个文件

运行的时候就是这样拷贝啦: ./a.out src.c des.c

实现代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int fdSrc;
        int fdDes;
        char *readbuf;                           //如果直接写char readbuf[1024];不初始化会造成目标文件有脏,就是可能会多出一些不明所以的符号。
 
        if(argc != 3){
            printf("param error\n");
            exit(-1);
        }
 
        fdSrc = open(argv[1],O_RDWR);            //1.打开要拷贝的源文件
 
        int size = lseek(fdSrc,0,SEEK_END);      //利用lseek返回值计算文件的大小  
        lseek(fdSrc,0,SEEK_SET);                 //移动光标到头   
 
        readbuf = (char *)malloc(sizeof(char)*size + 8);
       
        
        read(fdSrc,readbuf,size);                //2.读源文件到readbuf缓冲区
  
 
        fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600); //3.打开/创建你要拷贝到的目标文件,如果目标文件已存在有内容,O_TRUNC清除掉内容
 
      
        write(fdDes,readbuf,strlen(readbuf));      //4.将readbuf里面的内容写入目标文件
 
        
        close(fdSrc);                              //5.关闭打开的文件                      
        close(fdDes);
 
        return 0;
}

7.文件编程小应用(修改程序的配置文件)

#include
#include
#include
#include
#include
#include
#include

void findFun(char *str1,char *str2)
{
     char *p=strstr(str1,str2);
     if(p==NULL)
     {
         printf("no found\n");
         exit(-1);
     }

     p=p+strlen(str2);

     *p='5';
}

int main(int argc,char **argv)
{
    int fdSrc;
    int size;

    char *readBuf=NULL;

    if(argc!=2)
    {
        printf("the params no ok\n");
        exit(-1);
    } 

    fdSrc=open(argv[1],O_RDWR);
    
    size=lseek(fdSrc,0,SEEK_END);
    
    lseek(fdSrc,0,SEEK_SET);    
    
    readBuf=(char*)malloc(sizeof(char)*size+1);
    
    read(fdSrc,readBuf,size);

    findFun(readBuf,"LENG=");    

    lseek(fdSrc,0,SEEK_SET);    

    write(fdSrc,readBuf,strlen(readBuf));  
    
    close(fdSrc);
}

8.写整数,结构体,结构体数组,链表到文件

8.1写结构体数组到文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
struct Test
{
        int a;
        char c;
};
 
int main(int argc,char **argv)
{
        int fd;
 
        struct Test data[2] = {{100,'a'},{101,'b'}};
        struct Test data2[2];
 
        fd = open("./file",O_RDWR);
 
        int n_write = write(fd,&data,sizeof(struct Test)*2);
 
        lseek(fd,0,SEEK_SET);
 
        int n_read = read(fd,&data2,sizeof(struct Test)*2);
 
        printf("read %d,%c \n",data2[0].a,data2[0].c);
        printf("read %d,%c \n",data2[1].a,data2[1].c);
        close(fd);
 
        return 0;
}
                                                                                   
8.2写一个整数到文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int fd;
        int data = 100;
        int data2 = 0;
 
        fd = open("./file",O_RDWR);
 
        int n_write = write(fd,&data,sizeof(int));
 
        lseek(fd,0,SEEK_SET);
 
        int n_read = read(fd,&data2,sizeof(int));
 
        printf("read %d \n",data2);
        close(fd);
 
        return 0;
}
8.3写一个结构体到文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
struct Test
{
        int a;
        char c;
};
 
int main(int argc,char **argv)
{
        int fd;
 
        struct Test data = {100,'a'};
        struct Test data2;
 
        fd = open("./file",O_RDWR);
 
        int n_write = write(fd,&data,sizeof(struct Test));
 
        lseek(fd,0,SEEK_SET);
 
        int n_read = read(fd,&data2,sizeof(struct Test));
 
        printf("read %d,%c \n",data2.a,data2.c);
        close(fd);
 
        return 0;
}                   
8.4写一个链表到文件
#include
#include
#include 
#include 
#include 
#include 

struct Test
{
	int data;
	struct Test *next;
};

struct Test* insertFormHead(struct Test *head,struct Test *new)
{
	struct Test* point=head;

	if(point==NULL)
	{
		head=new;
		return new;
	}

	while(point->next!=NULL)

	{

		point=point->next;
	}

	point->next=new;



	return head;
}

struct Test* creatLink(struct Test *head)
{
	struct Test *new=NULL;
	int i;
	int num;    

	printf("please input link data\n");
	scanf("%d",&num); 

	for(i=0;inext=NULL;
		printf("please input NO %d link data\n",i+1);
		scanf("%d",&(new->data));
		head=insertFormHead(head,new);
	} 

	return head;

}


void printLink(struct Test *head)
{
	struct Test *point=head;

	while(point!=NULL)
	{

		printf("%d ",point->data);
		point=point->next;
	}


	putchar('\n');
} 

void saveLink(struct Test *head)
{
	struct Test *point=head;
	int fd;

	fd=open("./file2",O_RDWR);
	if(fd==-1)
	{
		printf("open file failed\n");
	}   

	while(point!=NULL)
	{
		if((write(fd,&point->data,sizeof(struct Test)))==-1)
		{
			printf("write failed\n");
		} 


		point=point->next;
	}

	close(fd);
}


void readLink(struct Test *head)
{
	struct Test *point=head;
	int fd;
	int n_read;
	int n_write;
	int fd1;
	struct Test *buf=NULL;

	fd=open("./file2",O_RDWR);
	if(fd==-1)
	{
		printf("open file failed\n");
	}

	int size=lseek(fd,0,SEEK_END);

	lseek(fd,0,SEEK_SET);

	buf=(struct Test*)malloc(sizeof(struct Test)*size+8);

	printf("open file2 success\n");

	while(point!=NULL)
	{
		n_read=read(fd,buf,sizeof(struct Test));
		printf("buf:%d ",point->data);              
		if(n_read==-1)
		{
			printf("read failed\n");
		} 

		fd1=open("./file3",O_RDWR);
		if(fd1==-1)
		{
			printf("open failed\n");
		}

		n_write=write(fd1,buf,sizeof(struct Test));
		if(n_write==-1)
		{
			printf("write failed\n");
		}

		point=point->next;
	}


	close(fd);
	close(fd1);
}

int main()
{
	struct Test *head=NULL;    

	head=creatLink(head);   

	printLink(head);

	saveLink(head);        

	readLink(head);

	return 0;
}

9.open和fopen的区别

9.1.来源

从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别:

  • open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
  • fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。
PS:从来源来看,两者是有千丝万缕的联系的,毕竟C语言的库函数还是需要调用系统API实现的。
9.2移植性

这一点从上面的来源就可以推断出来,fopen是C标准函数,因此拥有良好的移植性;而open是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile

9.3适用范围
  • open返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
  • fopen是用来操纵普通正规文件(Regular File)的。
9.4文件IO层次

如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

9.5缓冲
  1. 缓冲文件系统
    缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。
  2. 非缓冲文件系统
    缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar等。

一句话总结一下,就是open无缓冲,fopen有缓冲。前者与read, write等配合使用, 后者与fread,fwrite等配合使用。

10.标准C库打开/创建文件读写文件光标移动

10.1实现demo1
#include 
#include 
 
int main()
{
        FILE *fp;
        char *str = "wenjian chu ru men !";
        char readbuf[128] = {0};
 
        //FILE *fopen(const char *path, const char *mode);
        fp = fopen("./yy.text","w+");      //“w+” 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件
 
        //size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
        //const void *ptr  :缓冲区buf
        //size_t size      : 一个字符的大小  
        //size_t nmemb     :个数
        //FILE *stream     :哪个文件
        fwrite(str,sizeof(char),strlen(str),fp);        //第二个参数:写一个字符的大小,第三个参数:写strlen计算的字符串的个数这么多次
 
        // int fseek(FILE *stream, long offset, int whence);
        fseek(fp,0,SEEK_SET);
 
        //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
        fread(readbuf,sizeof(char),strlen(str),fp);
        
        printf("read data: %s \n",readbuf);
 
        fclose(fp);
 
        return 0;
}
10.2实现demo2
#include 
#include 
 
int main()
{
        FILE *fp;
        char *str = "wenjian chu ru men !";
        char readbuf[128] = {0};
 
        //FILE *fopen(const char *path, const char *mode);
        fp = fopen("./yy.text","w+");        //“w+” 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件
 
        //size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
        //const void *ptr  :缓冲区buf
        //size_t size      : 一个字符的大小  
        //size_t nmemb     :个数
        //FILE *stream     :哪个文件
        fwrite(str,sizeof(char)*strlen(str),1,fp);        //第二个参数:把整个字符串直接写完,第三个参数:写一次
 
        // int fseek(FILE *stream, long offset, int whence);
        fseek(fp,0,SEEK_SET);
 
        //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
        fread(readbuf,sizeof(char)*strlen(str),1,fp);
        
        printf("read data: %s \n",readbuf);
 
        fclose(fp);
 
        return 0;
}

这两个demo的区别是一次写入和读取的字节数的差别

11.标准C库写入结构体到文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
struct Test
{
        int a;
        char c;
};
 
int main(int argc,char **argv)
{
        FILE *fp;
 
        struct Test data = {100,'a'};
        struct Test data2;
      
        fp = fopen("./file","w+");       //“w+” 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
 
        int n_write = fwrite(&data,sizeof(struct Test),1,fp);
 
        fseek(fp,0,SEEK_SET);
 
        int n_read = fread(&data2,sizeof(struct Test),1,fp);
 
        printf("read %d,%c \n",data2.a,data2.c);
 
        fclose(fp);
 
        return 0;
}

12.标准c库:fputc,fgetc,feof

12.1fputc写一个字符到文件
#include 
 
int main()
{
        FILE *fp;
 
        fp = fopen("./test.txt","w+");
 
        //int fputc(int c, FILE *stream);
        fputc('a',fp);
 
        fclose(fp);
 
        return 0;
}
12.2fputc写一个字符串到文件
#include 
#include 
 
int main()
{
        FILE *fp;
        char *str = "wenjian chu ru men !";
        int i = 0;
 
        int len = strlen(str);
 
        fp = fopen("./test.txt","w+");
 
        for(i=0;i
12.3fgetc,feof

getc :意为从文件指针stream指向的文件中读取一个字符,读取一个字节后,光标位置后移一个字节。

返回值,是返回所读取的一个字节。如果读到文件末尾或者读取出错时返回EOF。虽然返回一个字节,但返回值不为unsigned char的原因为,返回值要能表示-1(即为EOF)。

feof :其功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0

注意:feof 判断文件结束是通过读取函数fread/fscanf等返回错误来识别的,故而判断文件是否结束应该是在读取函数之后进行判断。比如,在while循环读取一个文件时,如果是在读取函数之前进行判断,则如果文件最后一行是空白行,可能会造成内存错误。

#include 
#include 
 
int main()
{
        FILE *fp;
        char c;
 
        fp = fopen("./test.txt","r"); //以只读方式打开文件,该文件必须存在。
 
        c = fgetc(fp);
        while(!feof(fp)){
                printf("%c",c);
                c = fgetc(fp);
        }
 
        fclose(fp);
 
        printf("\n");
 
        return 0 ;
}

二、进程

1.进程相关概念

1.1什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c -o pro

磁盘中生成的文件,叫做程序。

进程是程序的一次运行活动,通俗点的意思就是程序跑起来了,系统中就多了一个进程。
1.2如何查看系统中有那些进程
1.2.1使用ps指令查看
实际工作中,配合grep来查找程序中是否存在某一个进程
ps -aux|grep xxx;
1.2.2使用top指令查看
类似windows的任务管理器
1.3什么是进程标识符
每一个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证

pid = 0 ;称为交换进程(swapper)

作用——进程的调度

pid = 1 ;init 进程

作用——系统的初始化

编程调用 getpid 函数获取滋生的进程标识符,getppid 获取父进程的进程标识符
1.4什么叫父进程,什么叫子进程
进程 A 创建了进程 B 

那么 A 叫做父进程,B 叫做子进程,父子进程是相对的概念,理解为人类中的父子关系
1.5c程序的存储空间是如何分配的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDaf9jsV-1688607361414)(…/…/…/…/…/…/Pictures/Camera Roll/4.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qaSdg5p6-1688607361414)(…/…/…/…/…/…/Pictures/Camera Roll/5.jpg)]

2.创建进程函数fork的使用,与vfork的区别

2.1fork函数简介
使用 fork 函数创建一个进程

pid_t  fork(void);

fork 函数调用成功,返回两次

返回值为0,代表当前进程是子进程

返回值是非负数,代表当前进程为父进程

调用失败,返回 -1
2.2代码实例研究fork函数之后父子进程的关系
#include 
#include 
 
int main()
{
        pid_t pid;
        pid_t pid2;
 
        pid = getpid();
        printf("before fork: pid = %d \n",pid);
 
        fork();
 
        pid2 = getpid();
        printf("after fork: pid = %d \n",pid2);
 
        if(pid == pid2){
                printf("this id father print , pid = %d \n",getpid());
        }
        else{
                printf("this id child print , pid = %d \n",getpid());
        }
 
        return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1OcuQL3-1688607361414)(…/…/…/…/…/…/Pictures/Camera Roll/7.jpg)]

结论:

一个函数在没有创建fork函数之前都是父进程在走

一旦到了fork进程之后就会出现两个进程(父进程和子进程)

fork之后具体哪个进程进程在跑不知道,由系统调度决定。但可以确定的是,父子进程都会从fork之后,走代码一遍,所以打印了两次 after fork

直到在选择语句的时候,根据条件,父子进程才根据条件各自跑

2.3fork返回值具体情况
#include 
#include 
 
int main()
{
        pid_t pid;
        pid_t pid2;
        pid_t retpid;
 
 
        pid = getpid();
        printf("before fork: pid = %d \n",pid);
 
        retpid = fork();
 
        pid2 = getpid();
        printf("after fork: pid = %d \n",pid2);
 
        if(pid == pid2){
                printf("this id father print , retpid = %d ,pid = %d\n",retpid,getpid());
        }
        else{
                printf("this id child print ,retpid = %d , pid = %d \n",retpid,getpid());
        }
 
        return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vk1tr9FP-1688607361415)(…/…/…/…/…/…/Pictures/Camera Roll/8.png)]

结论:

fork的返回值,大于0 的时候,代表是父进程。此时的返回值,刚好是子进程的pid号

而fork的返回值,等于0 的时候,代表的是子进程。此时的子进程的pid号其实是复制了fork大于0 的返回值。

其实意思就是:两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的ID

有点拗口,具体看代码吧。

2.4vfork函数

关键区别一:

vfork直接使用父进程的存储空间,不拷贝

关键区别二:

vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行

通过代码验证区别,既然vfork是直接使用父进程的储存空间,那么子进程修改的数据,父进程那边也是被修改了。

#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
 
        pid = vfork();
 
        if(pid > 0){
                while(1){
                        printf("this is father print: pid = %d \n",getpid());
                        printf("cnt = %d\n",cnt);
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d \n",getpid());
                        cnt++;
                        if(cnt == 3){
                                exit(0);
                                //break;  不能使用,原因请看下面4.1点
                        }
                        sleep(1);
                }
        }
 
        return 0;
}

结论:

1、如果子进程不是按 exit 这个规则退出,换了其他退出,比如break,则父进程的打印的 cnt 不是 3,是一串数字

2、vfork是直接使用父进程的储存空间,那么子进程修改的数据,父进程那边也是被修改了。从代码结果可以看出来,子进程将cnt 改为了3,父进程打印 cnt 就为 3。

而 fork 则是,旧的说法是拷贝了一份空间(新的说法是共享,用的时候就直接调用),父子进程各不影响,各行各事。

3.进程的正常退出和异常退出

3.1正常退出
1.Main函数调用return
2.进程调用 exit( ) ,标准C库,一般写 exit(0);
3.进程调用 _exit( ) 或者 _Exit( ),属于系统调用
进程最后一个线程返回
最后一个线程调用pthread_exit
3.2异常退出
调用 abort
当进程收到某些信号时,如 ctrl + c
最后一个线程对取消(cancellation)请求做出响应

总之,不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

对上述任意一种终止清醒,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数( exit、_exit、 _Exit ),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination stastus)。在任意一种情况下,该终止进程的父进程都能用 wait 或 waitpid 函数取得其终止状态。

同时值得注意的是,在编程的时候,进程的退出最好是用三个终止函数( exit、_exit、 _Exit )其中的一个,当然建议 exit 即可。不能用 break 等其他的终止,因为会导致数据被破坏

4.父进程等待子进程退出

4.1僵尸进程(子进程退出状态不被父进程收集)
#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
 
        pid = vfork();
 
        if(pid > 0){
                while(1){
                        printf("this is father print: pid = %d \n",getpid());
                        printf("cnt = %d\n",cnt);
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d \n",getpid());
                        cnt++;
                        if(cnt == 3){
                                exit(0);
                        }
                        sleep(1);
                }
        }
 
        return 0;
}
4.2子进程退出状态被父进程收集,调用wait

NAME
wait, waitpid, waitid - wait for process to change state

SYNOPSIS
#include
#include

   pid_t wait(int *status);

   pid_t waitpid(pid_t pid, int *status, int options);

   int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

在等待的过程中:

如果其所有子进程都还在运行,则阻塞。
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
如果它没有任何子进程,则立即出错返回。
status参数:

是一个整型数指针

非空:

子进程退出状态存放在它指向的地址中

空:

不关心退出状态

4.2.1wait函数status参数为空
#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
 
        pid = fork();
 
        if(pid > 0){
    
                wait(NULL);
 
                while(1){
                        printf("this is father print: pid = %d \n",getpid());
                        printf("cnt = %d\n",cnt);
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d \n",getpid());
                        cnt++;
                        if(cnt == 5){
                                exit(0);
                        }
                        sleep(1);
                }
        }
 
        return 0;
}
4.2.2wait函数status参数非空。

当wait函数status参数非空,并且我们要查看返回的是哪一个子进程的终止状态的状态码的时候

我们就需要下面这个表:检查wait和waitpid所返回的终止状态的宏,来解析状态码。

说明

     WIFEXITED(status)	    若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送给exit、_exit或_Exit参数的低8位
     WIFSIGNALED(status)	   若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使子进程终止的信号编号。另外,有些实现定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真
    WIFSTOPPED(status)	   若为当前暂停子进程的返回状态,则为真。对于这种情况,可执行WSTOPSIG(status),取使子进程暂停的信号编号
    WIFCONTINUED(status)	   若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.1的XSI扩展;仅用于waitpid。)

下面代码调用的是第一个宏 WEXITSTATUS(status) , 正常终止子进程返回的状态,为真的情况:

#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
        int status = 10;
 
        pid = fork();
 
        if(pid > 0){
 
                wait(&status);
                printf("child quit,child status = %d\n",WEXITSTATUS(status));
 
                while(1){
                        printf("cnt = %d\n",cnt);
                        printf("this is father print: pid = %d \n",getpid());
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d \n",getpid());
                        cnt++;
                        if(cnt == 5){
                                exit(3);
                        }
                        sleep(1);
                }
        }
 
 
        return 0;
}
4.3waitpidwait 使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞。

们用 waitpid的options常量:第二种WNOHANG非阻塞,写个代码demo如下:

根据运行结果看到:**当父进程用waitpid等待不阻塞的时候,子进程是(僵尸进程)。**两个进程都在跑

#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
        int status = 10;
 
        pid = fork();
 
        if(pid > 0){
 
                //wait(&status);
                waitpid(pid,&status,WNOHANG);
                printf("child quit,child status = %d\n",WEXITSTATUS(status));
 
                while(1){
                        printf("cnt = %d\n",cnt);
                        printf("this is father print: pid = %d \n",getpid());
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d \n",getpid());
                        cnt++;
                        if(cnt == 5){
                                exit(3);
                        }
                        sleep(1);
                }
        }
 
 
        return 0;
}
4.4孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时的子进程叫做孤儿进程

Linux避免系统存在过多的孤儿进程,init进程(系统的一个初始化进程,它的pid号为1)收留孤儿进程,变成孤儿进程的父进程

#include 
#include 
#include 
 
int main()
{
        pid_t pid;
 
        int cnt = 0;
        int status = 10;
 
        pid = fork();
 
        if(pid > 0){
                        printf("this is father print: pid = %d \n",getpid());
        }
        else if(pid == 0){
                while(1){
                        printf("this is child print: pid = %d,my father pid = %d \n",getpid(),getppid());
                        cnt++;
                        if(cnt == 5){
                                exit(3);
                        }
                        sleep(1);
                }
        }
        return 0;
}

5exec族函数

5.1exec族函数的作用

我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变

5.2exec族函数功能

在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

5.3函数族

exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

5.4函数原型
#include 
extern char **environ;
 
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
5.5返回值

*exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。*
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

5.6以execl函数为例子来编写代码说明:

带l的一类exac函数(l表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。

//文件execl.c
#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);
 
int main(void)
{
    printf("before execl\n");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!\n"); 
 
        perror("why");     //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 
    }
    printf("after execl\n");
    return 0;
}
//文件echoarg.c
#include 
 
int main(int argc,char *argv[])
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]); 
    }
    return 0;
}

实验结果:

ubuntu:~/test/exec_test$ ./execl
before execl****
argv[0]: echoarg
argv[1]: abc

实验说明:

我们先用gcc编译echoarg.c,生成可执行文件echoarg并放在当前路径目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg,将当前进程main替换掉,所以”after execl” 没有在终端被打印出来。

5.7活用execl族函数来查找系统时间

1首先用命令 whereis date ,找到系统时间 date 的绝对路径:

2

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);
 
int main(void)
{
    printf("this pro get system data\n");
    if(execl("/bin/date","date",NULL,NULL) == -1)
    {
        printf("execl failed!\n");
 
        perror("why");
   }
    printf("after execl\n");
    return 0;
}
5.8execlp函数
clude
#include

int main(void)
{
   printf("before execl\n");

   if(execlp("ps","ps",NULL,NULL)==-1)
   {
        printf("execl failed\n");
        perror("why");
   } 

   printf("afther execl\n");

   return 0;
}
5.9execvp
#include
#include

int main(void)
{
   printf("before execl\n");
   
   char *argv[]={"ps",NULL,NULL};

   if(execvp("ps",argv)==-1)
   {
        printf("execl failed\n");
        perror("why");
   } 

   printf("afther execl\n");

   return 0;
}

6linux 下修改环境变量配置绝对路径

修改环境变量的好处就是,可以直接将要加路径如 ./ 才能运行的可执行文件,直接就可以用名字就能运行。

用命令 pwd 找 找出当前路径
用命令 echo P A T H 找出环境变量(按图中的方法,这一步可省略,直接第 3 步即可)。用命令 e x p o r t P A T H = PATH 找出环境变量(按图中的方法,这一步可省略,直接第3步即可)。 用命令 export PATH= PATH找出环境变量(按图中的方法,这一步可省略,直接第3步即可)。用命令exportPATH=PATH:当前路径 ,将环境变量和当前路径连在一起 。这样修改环境变量就完成了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MmNy1dG-1688607361415)(…/…/…/…/…/…/Pictures/Camera Roll/9.png)]

7Linux下exec配合fork使用

实现功能,当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。

被修改的字段的配置文件config.txt

//config.txt
 
SPEED=5
LENG=9
SCORE=90
LEVEL=95

修改字段的文件 changData.c

//changData.c
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int fdSrc;
        char *readBuf = NULL;
 
        if(argc != 2){
                printf("parames error");
                exit(-1);
        }
 
        fdSrc = open(argv[1],O_RDWR);            /1.打开文件
        int size = lseek(fdSrc,0,SEEK_END);
        lseek(fdSrc,0,SEEK_SET);
 
        readBuf =(char *)malloc(sizeof(char)*size +8);
        int n_read = read(fdSrc,readBuf,size);    //2.读文件
 
        char *p = strstr(readBuf,"LENG=");        //3.找到要修改的地方       
        if(p==NULL){                        
                printf("not found\n");
                exit(-1);
        }
        p = p + strlen("LENG=");
        *p = '5';
 
        lseek(fdSrc,0,SEEK_SET);
        int n_write =  write(fdSrc,readBuf,strlen(readBuf));    4.改了之后写入文件
 
        close(fdSrc);
 
        return 0;
}

将修改字段的文件changData.c ,( gcc changData.c -o changData ) ,生成可执行文件 changData

由下面 execl( ) 函数配合 fork( ) 函数使用的代码,让 exexl( ) 函数调用可执行文件 changData,来修改配置文件

//demo.c
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main()
{
        pid_t pid;
        int data = 10;
 
        while(1){
                printf("please input your data:\n");
                scanf("%d",&data);
                if(data == 1){
                        pid = fork();
 
                        if(pid > 0){
                                wait(NULL);
                        }
 
                        if(pid == 0){
                                while(1){
 
                                        execl("./changData","changData","TEST.config",NULL);    //execl 调用 修改文件changData
 
                                }
                        }
 
                }
                else{
                        printf("wait , do nothing!\n");
                }
        }
        return 0;
}

配置文件被修改后:将 LENG=9 改成了 LENG=5

//config.txt
 
SPEED=5
LENG=5
SCORE=90
LEVEL=95

8linux下system函数

8.1system()函数原型

NAME
system - execute a shell command

SYNOPSIS
#include

​ int system(const char *command);

8.2ststem()函数返回值

成功,则返回进程的状态值;

当sh不能执行时,返回127;

失败返回-1;

8.3system()函数源码
 
int system(const char * cmdstring)
{
    pid_t pid;
    int status;
    if(cmdstring == NULL)
    {
        return (1); //如果cmdstring为空,返回非零值,一般为1
    }
    if((pid = fork())<0)
    {
        status = -1; //fork失败,返回-1
    }
    else if(pid == 0)
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); /* exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的
        进程就不存在啦8*/
    }
    else //父进程
    {
        while(waitpid(pid, &status, 0) < 0)
        {
            if(errno != EINTR)
            {
                status = -1; //如果waitpid被信号中断,则返回-1
                break;
            }
        }
    }
    return status; //如果waitpid成功,则返回子进程的返回状态
}
8.4system()函数小应用代码demo

实现小功能,执行 vim 中的 ps - l 命令

#include 
#include 
#include 
//函数原型:int system(const char *command);
 
int main(void)
{
    printf("this pro get system date:\n");
    if(system("ps -l") == -1)
    {
        printf("system failed!\n");
 
        perror("why");
   }
    printf("after system!!!\n");
    
    return 0;
}

通过运行结果可以看出:system( ) 函数调用完之后,代码还会往下走。 而exec族函数则不会往下走。

system( ) 函数的参数书写的规律是,可执行文件怎么执行,就怎么写:比如 system(“./a.out aa bb”);

9linux下popen函数

9.1popen原型
SYNOPSIS
       #include 
 
       FILE *popen(const char *command, const char *type);
 
       int pclose(FILE *stream);
9.2函数说明

popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。

type参数只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。

command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。

popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。

9.3返回值

如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。

9.4比system()函数在应用中的好处:可以获取运行的结果
#include 
#include 
#include 
 
//FILE *popen(const char *command, const char *type);
 
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
 
int main(void)
{
        FILE *fp;
        char ret[1024]={0};
 
        fp = popen("ps","r");   
 
        int nread = fread(ret,1,1024,fp);
 
        printf("read ret %d byte,ret = %s \n",nread,ret);
 
        return 0;
}

代码popen中的 ps 是linux中的 ps 指令。

三、进程间通信

1.进程间通信(IPC)介绍

进程间通信(IPC,InterProcess Communication)是指在不同的进程之间传播或交换信息。

IPC的方式有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程间通信。

2.管道

2.1无名管道
2.1.1特点:
  1. 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  2. 它只能用于父子进程之间的通信。
  3. 管道是创建在内存中,进程结束空间释放,管道不复存在。对于它的读写可以使用普通的read、write等函数
2.1.2函数原型
SYNOPSIS
       #include 
 
       int pipe(int pipefd[2]);
2.1.3返回值
返回值:成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。
2.1.4无名管道代码demo
#include 
#include 
#include 
#include 
 
//int pipe(int pipefd[2]);
//ssize_t write(int fd, const void *buf, size_t count);
//ssize_t read(int fd, void *buf, size_t count);
 
int main()
{
        int fd[2];
        int pid;
        char buf[128];
 
        if(pipe(fd) == -1){
                printf("creat pipe fail\n");
        }
 
        pid = fork();
 
        if(pid < 0 ){
                printf("creat child fail\n");
        }
        else if(pid > 0){
                sleep(3);
 
                printf("this is father\n");
                close(fd[0]);
                write(fd[1],"hello from father",strlen("hello from father"));
 
                wait();
        }
        else{
                printf("this is child\n");
                close(fd[1]);
                read(fd[0],buf,128);
                printf("read = %s \n",buf);
 
                exit(0);
        }
 
        return 0;
}

注意:管道的特性:管道里没有数据会阻塞

2.2命名管道
2.2.1特点
  1. 命名管道可以在无关的进程之间交换数据,与无名管道不同。
  2. 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2.2.2函数原型
#include 
#include 
 
int mkfifo(const char *pathname, mode_t mode);
 

其中mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般的文件I/O函数来操作。

当open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开FIFO。类似的,只写open要阻塞到某个其他进程为读而打开它。(一般选择默认)
若是指定O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1,如果没有进程已经为读而打开该FIFO,其errno为ENXIO。
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清楚数据,并且”先进先出“。

2.2.3命名管道代码demo

read.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
//ssize_t read(int fd, void *buf, size_t count);
//int mkfifo(const char *pathname, mode_t mode);
//int open(const char *pathname, int flags);
 
int main()
{
        int fd;
        char buf[128] = {0};
 
        if(mkfifo("./file",0600) == -1 && errno != EEXIST){
                printf("mkfifo fail\n");
                perror("why");
        }
 
        fd = open("./file",O_RDONLY);
        printf("read open success\n");
 
        while(1){
                int n_read = read(fd,buf,128);
                printf("read %d byte,contxt = %s \n",n_read,buf);
        }
 
        close(fd);
 
        return 0;
}

write.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
//ssize_t write(int fd, const void *buf, size_t count);
//int mkfifo(const char *pathname, mode_t mode);
//int open(const char *pathname, int flags);
 
int main()
{
        int fd;
        char *str = "message from fifo";
        int cnt = 0;
 
        fd = open("./file",O_WRONLY);
        printf("write open success\n");
 
        while(1){
                write(fd,str,strlen(str));
 
                sleep(1);
        }
        close(fd);
 
        return 0;
}

3.消息队列

消息队列,是信息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。

3.1特点
  1. 消息队列是面向记录的,其中的消息具有特地那个的格式以及特定的优先级。
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及内容并不会删除。
  3. 消息队列可以实现信息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
3.2函数原型
#include 
#include 
#include 
 
//创建或打开消息队列:成功返回队列 ID ,失败返回 -1    
int msgget(key_t key, int msgflg);
//添加消息:成功返回 0 ,失败返回 -1 
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取信息:成功返回消息数据的长度,失败返回 -1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//控制消息队列:成功返回 0 ,失败返回 -1 
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

key值很关键,相当于一个索引。进程通过这个键值,在Linux内核中找到相应的队列(确保了队列是同一个)

在以下两种情况中,msgget 将创建一个新的消息队列:

如果没有与键值相对应的消息队列,并且flag中包含了 IPC_CREAT 标志位。
key 参数为 IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况。

type == 0 ,返回队列中的第一个消息;
type > 0 ,返回队列中消息类型为 type 的第一个消息;
type < 0 ,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

3.3demo

send.c

#include
#include
#include
#include
#include

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
	char mtext[128];    /* message data */
};

int main()
{
	struct msgbuf sendBuf={888,"this is msgged from que"};
        struct msgbuf readBuf;

        key_t key;

        key=ftok(".",1);
         
        printf("key=%d\n",key);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId==-1)
	{
		perror("why:");
	}


	while(1)
	{
		msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
                sleep(1);
		printf("write success\n");

                msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
                sleep(1);
                printf("read from que:%s\n",readBuf.mtext);

	}

        msgctl(msgId,IPC_RMID,NULL);        

	return 0;
}

read.c

#include
#include
#include
#include
#include

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
	char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf readBuf;
        struct msgbuf sendBuf={988,"this is msgged father"};

        key_t key;

        key=ftok(".",1);
        
        printf("key=%d\n",key);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId==-1)
	{
		perror("why:");
	}


	while(1)
	{
		msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
                sleep(1);
		printf("read from que:%s\n",readBuf.mtext);
                
                msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
                sleep(1);
                printf("write success\n");
                  
	}


        msgctl(msgId,IPC_RMID,NULL);
                 

	return 0;
}

4.信号

4.1什么是信号?

对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

4.2信号概述
4.2.1信号的名字和编号

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMYA5uVk-1688607361415)(…/…/…/…/…/…/Pictures/Camera Roll/10.png)]

4.2.2 信号的处理:
  1. 信号的处理有三种方法,分别是:忽略、捕捉 和 默认动作
  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
    具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明
4.2.3信号如何使用

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCdo3F74-1688607361416)(…/…/…/…/…/…/Pictures/Camera Roll/11.jpg)]

4.3信号处理函数的注册

信号处理函数的注册不只一种方法,分为入门版和高级版

  1. 入门版:函数 signal
  2. 高级版:函数 sigaction
4.4信号处理发送函数

信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:kill
2.高级版:sigqueue

4.5信号注册函数——入门版
4.5.1signal函数
#include 
 
typedef void (*sighandler_t)(int);
 
sighandler_t signal(int signum, sighandler_t handler);

根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
对于**sighandler_t signal(int signum, sighandler_t handler)**;函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
同样,**typedef void (\*sighandler_t)(int**);中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。我们先来看看简单一个信号注册的代码示例吧。(signal函数的入门理解参考:https://blog.csdn.net/jinchi_boke/article/details/116081840)

4.5.2信号注册的代码demo
#include 
#include 
 
//       typedef void (*sighandler_t)(int);
 
//       sighandler_t signal(int signum, sighandler_t handler);
 
void handler(int signum)
{
        printf("get signum = %d\n",signum);
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
 
int main()
{
        signal(SIGINT,handler);
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
 
        while(1);
        return 0;
}
4.5.3 信号的处理还有两种状态,分别是默认处理和忽略,这两种设置很简单,只需要将 handler 设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认动作)即可
#include 
#include 
 
//       typedef void (*sighandler_t)(int);
//       sighandler_t signal(int signum, sighandler_t handler);
 
 
int main()
{
        signal(SIGINT,SIG_IGN);    //参数1:注册(接收)crtl+c这个信号,参数2:宏(忽略)
        signal(SIGKILL,SIG_IGN);
        signal(SIGUSR1,SIG_IGN);
 
        while(1);
        return 0;
}
4.6kill函数
4.6.1函数原型
#include 
#include 
 
int kill(pid_t pid, int sig);

信号的处理需要有接受者,显然发送者必须要知道发给谁,根据 kill 函数的远行可以看到,pid 就是接受者的 pid,sig 则是发送的信号的类型。从原型来看,发送信号要比接受信号还要简单些

./pro

#include 
#include 
 
//       typedef void (*sighandler_t)(int);
 
//       sighandler_t signal(int signum, sighandler_t handler);
 
void handler(int signum)
{
        printf("get signum = %d\n",signum);
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
 
int main()
{
        signal(SIGINT,handler);    //参数1:注册(接收)crtl+c这个信号,参数2:调用handler函数处理信号
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
 
        while(1);
        return 0;
}

./a,out

#include 
#include 
#include 
#include 
 
//int kill(pid_t pid, int sig);
 
int main(int argc,char **argv)
{
        int signum;
        int pid;
 
        signum = atoi(argv[1]);    //atoi() 将argv[i]的字符串,转换为 整型数 
        pid = atoi(argv[2]);
 
        printf("signum = %d , pid = %d \n",signum,pid);
 
        //int kill(pid_t pid, int sig);
        kill(pid,signum);
 
        printf("send siganal ok\n");
 
        return 0;
}
4.6.2除了kill函数还可以用sprintf()配合system()函数来做到同样的信号处理发送
#include 
#include 
#include 
#include 
 
//int kill(pid_t pid, int sig);
 
int main(int argc,char **argv)
{
        int signum;
        int pid;
 
        char cmd[128] = {0};
 
        signum = atoi(argv[1]);    //atoi() 将argv[i]的字符串,转换为 整型数
        pid = atoi(argv[2]);
 
        printf("signum = %d , pid = %d \n",signum,pid);
 
//      kill(pid,signum);       
        sprintf(cmd,"kill -%d %d",signum,pid);
 
        system(cmd);
 
        printf("send siganal ok\n");
        return 0;
}
4.7信号注册函数——高级版
4.7.1信号注册函数:sigaction

函数原型:

#include 
 
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
 
struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一
 

这个函数的原版帮助信息,可以通过**man sigaction**来查看。

sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

在这里额外说一下struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。

sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。

关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。

关于void (*sa_sigaction)(int, siginfo_t *, void *);处理函数来说还需要有一些说明。void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。

 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

那么,kill 函数发送的信号是无法携带数据的,我们现在还无法验证发送收的部分,那么,我们先来看看发送信号的高级用法后,我们再来看看如何通过信号来携带数据吧。

4.7.2信号发送函数:sigqueue
#include 
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };
  1. 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
  2. sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。

sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

接收端demo:

#include 
#include 
 
//       int sigaction(int signum, const struct sigaction *act,
//              struct sigaction *oldact);
 
//void     (*sa_sigaction)(int, siginfo_t *, void *);
 
void handler(int signum,siginfo_t *info,void *context)    //参数1:信号值,参数2:内容,参数3:是否为NULL决定有没有内容
{
        printf("get signum %d\n",signum);
 
        if(context != NULL){                              //非空,代表有内容
                printf("get data = %d\n",info->si_int);
                printf("get data = %d\n",info->si_value.sival_int);
                printf("from = %d\n",info->si_pid);
        }
}
 
int main()
{
        struct sigaction act;
        act.sa_sigaction = handler;      //调用handler函数处理信号
        act.sa_flags = SA_SIGINFO;      //要想接收信号数据,必须指定这个flag 是 SA_SIGINFO  
 
        printf("%d\n",getpid());
 
        sigaction(SIGUSR1,&act,NULL);    //参数1:接收哪个信号,参数2:收到信号后想怎么处理它,参数3:备份原信号的操作。一般设置为NULL,不关心
 
        while(1);
        return 0;
}

发送端demo:

#include 
#include 
 
//int  sigqueue(pid_t  pid,  int  sig,  const  union  sigval value);
 
int main(int argc,char **argv)
{
        int signum;
        int pid;
 
        printf("%d\n",getpid());
 
        signum = atoi(argv[1]);    //atoi() 将argv[i]的字符串,转换为 整型数
        pid = atoi(argv[2]);
 
        union  sigval value;
        value.sival_int = 100;
    
        sigqueue(pid,signum,value);    //参数1:进程pid,参数2:信号值,参数3:你要发送的信息
 
        printf("done\n");
        return 0;
}

5信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

5.1特点
  1. 信号量用于进程间同步,若要在进程间传递胡数据需要结合共享内存。
  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。(P操作:拿锁。V操作:放回锁)
  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或 减1 ,而且可以加加减任意正整数。
  4. 支持信号量组。
5.2函数原型

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

#include 
#include 
#include 
 
//创建或获取一个信号量组,成功会返回信号量集 ID ,失败返回 -1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值,成功返回 0,失败返回 -1 (用于 PV 操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);    
//控制信号量的相关信息 (用于给信号量初始化)
int semctl(int semid, int semnum, int cmd, ...);
 
5.3semget

原型 : int semget(key_t key, int nsems, int semflg);

参数:

key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。

nsems:信号集中信号量的个数

semflg: 由九个权限标志构成,它们的用法和创建文件时使⽤的mode模式标志是一样的

返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1

5.4semop

原型 : int semop(int semid, struct sembuf *sops, unsigned nsops);

参数 :

**semid:**是该信号量的标识码,也就是semget函数的返回值

**sops:**是个指向一个结构数值的指针

指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
 
struct sembuf{
   short sem_num; //信号量编号,除非使用一组信号量,否则它的取值为0
   short sem_op;  //信号量在一次操作中需要改变的数值。通常用到两个值,-1,也就是p操作,表示拿锁;+1,也就是V操作,表示放回锁。
   short sem_flg; //通过被设置为SEM_UNDO。表示操作系统会跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,防止其他进程一直处于等待状态。 
};  

**nsops:**信号量的个数

返回值:成功返回0;失败返回-1

5.5semctl

系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。

semid: 信号量的标志码(ID),也就是semget()函数的返回值;

semnum: 操作信号在信号集中的编号。从0开始。

cmd: 命令,表示将要进行的操作。

参数cmd中可以使用的命令如下:

·IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
·IPC_RMID 将信号量集从内存中删除。
·GETALL 用于读取信号量集中的所有信号量的值。
·GETNCNT 返回正在等待资源的进程数目。
·GETPID 返回最后一个执行semop操作的进程的PID。
·GETVAL 返回信号量集中的一个单个的信号量的值。
·GETZCNT 返回正在等待完全空闲的资源的进程数目。
·SETALL 设置信号量集中的所有的信号量的值。
·SETVAL 设置信号量集中的一个单独的信号量的值。【一般用这个】

此函数具有三个或四个参数,具体取决于cmd。当
有四个时,第四个具有union semun类型。

第四个参数:可选。是否使用取决于所请求的命令。如果使用该参数,则其类型是semun。
union semun{
  int val;                   SETVAL的值
  struct semid_ds *buf;       IPC_STAT,IPC_SET的缓冲区 
  unsigned short *array;      GETALL,SETALL的数组
    struct seminfo __buf;       IPC_INFO的缓冲区(特定于Linux)
};
一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。
5.6信号量代码demo
#include 
#include 
#include 
#include 
 
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
 
//联合体,用于semctl初始化
union semun {
        int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                    (Linux-specific) */
};
 
void pGetKey(int id)
{
        struct sembuf set;
        set.sem_num = 0;            //一般默认是 0
        set.sem_op = -1;            //因为我信号量的初始值设置为0,所以这里-1就是取走锁
        set.sem_flg = SEM_UNDO;     //一般是 SEM_UNDO ,大概是表示阻塞等待
 
        semop(id,&set,1);           //参数2:是个指向⼀一个结构数值的指针
                                    //参数3:信号量的个数
 
        printf("get key\n");
}
 
void pPutBackKey(int id)
{
        struct sembuf set;
        set.sem_num = 0;           //一般默认是 0
        set.sem_op = 1;            //因为我信号量的初始值设置为0,所以这里+1就是有锁
        set.sem_flg = SEM_UNDO;    //一般是 SEM_UNDO ,大概是表示阻塞等待
 
        semop(id,&set,1);
 
        printf("put back the key\n");
}
 
int main()
{
        int semid;
 
        key_t key;
        key = ftok(".",2);
 
        //1.获取或创建信号量
        semid =  semget(key,1,IPC_CREAT|0666);  //参数2:信号量集合中有 1 个信号量 
                                                //参数3:0666 是信号量的权限
 
        union semun initsem;
        initsem.val = 0;           //一般只使用val这个成员,来为信号量赋初值。为0时,没有钥匙
 
        //2.初始化信号量    
        semctl(semid,0,SETVAL,initsem);    //参数2:操作第0个信号量    
                                           //参数3:SETVAL 设置信号量的初值,此时决定有第四个参数,设置为initsem.
                                           //参数4:是一个联合体。初始化信号量
 
        int pid = fork();
        if(pid > 0 ){
                //4.去拿锁
                pGetKey(semid);
                printf("this is father\n");
               
                //5.锁返回去
                pPutBackKey(semid);
               
                //6.销毁锁
                semctl(semid,0,IPC_RMID);
        }
        else if(pid == 0){
                printf("this is child\n");
                //3.放锁
                pPutBackKey(semid);
        }
        else{
                printf("fork error\n");
        }
 
        return 0;
}
5.7demo优化
#include 
#include 
#include 
#include 
#include 
 
//联合体,用于semctl初始化
union semun
{
	int              val;
	struct semid_ds *buf;
	unsigned short *array;
};	
 
//初始化信号量
int init_sem(int sem_id,int value)
{
	union semun tmp;
	tmp.val = value;
 
	if(semctl(sem_id,0,SETVAL,tmp) == -1){
		perror("init semaphore error");
		return -1;
	}
	return 0;
}
 
// P 操作
// 若信号量值为 1,获取资源并将信号量值 -1
// 若信号量值为 0,进程挂起等待
int sem_p(int sem_id)
{
        struct sembuf sbuf;
        sbuf.sem_num = 0;
        sbuf.sem_op = -1;
        sbuf.sem_flg = SEM_UNDO;
 
        if(semop(sem_id,&sbuf,1) == -1){
                perror("P operation error");
                return -1;
        }
        
	return 0;
}
 
// V 操作
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒他们
int sem_v(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0;	//序号
	sbuf.sem_op = 1;	// V 操作
	sbuf.sem_flg = SEM_UNDO;
 
	if(semop(sem_id,&sbuf,1) == -1){
		perror("V operation error");
		return -1;
	}
	
	return 0;
}
 
//删除信号量集
int del_sem(int sem_id)
{
	union semun tmp;
 
	if(semctl(sem_id,0,IPC_RMID,tmp) == -1){
		perror("delete semaphore erroe");
		return -1;
	}
	return 0;
}
 
int main()
{
	int   sem_id; //信号量集 ID
	key_t key;
	pid_t pid;
 
	//获取key值
	if((key = ftok(".",'z')) < 0){
		perror("ftok error");
		exit(1);
	}
 
	//创建信号量集,其中只有一个信号量
	if(sem_id = semget(key,1,IPC_CREAT|0666) == -1){
		perror("semget error");
		exit(1);
	}
 
	//初始化:初值设为 0 资源被占用
	init_sem(sem_id,0);
 
	if(pid = fork() == -1){
		perror("Fork error");		
	}
	else if(pid == 0){	//子进程
		sleep(2);
		printf("process child: pid = %d\n",getpid());
		sem_v(sem_id);	//释放资源
	}
	else{ //父进程
		sem_p(sem_id);	//等待资源
		printf("process father: pid = %d\n",getpid());
		sem_v(sem_id);	//释放资源
		del_sem(sem_id);//删除信号量集
	}
 
	return 0;
}

6共享内存(一般来说需要搭配 信号量 来使用)

共享内存(shared memory),指两个或多个进程共享一个给定的存储区。

查看共享内存的命令:ipcs -m (在共享内存创建之后,并在断开连接之前,可以加exit(0)退出程序,此时可以用查看命令看到创建的共享内存有哪些)

删除共享内存的命令:ipcs -m id号

6.1特点

共享内存是最快的一种IPC,因为进程是直接对内存进行存储,而不需要任何数据的拷贝。

它有一个特性:只能单独一个进程写或读,如果A和B进程同时写,会造成数据的混乱,(所以需要搭配信号量来使用,比较好)

6.2函数原型
//创建或获取一个共享内存:成功返回共享内存ID,失败返回 -1
int shmget(key_t key, size_t size, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回 -1
void *shmat(int shmid, const void *shmaddr, int shmflg);
//断开与共享内存的连接:成功返回0,失败返回 -1
int shmdt(const void *shmaddr);
//控制共享内存的相关信息:成功返回0,失败返回 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  1. 当 shmget函数创建一段共享内存的时候,必须指定其size,而且必须是以兆对齐的(一兆空间是:1024);而如果引用一个已存在的共享内存,则将size指定为0。

shmget ,参数1:是一个key 。参数2:共享内存的大小,必须以兆对齐(一兆空间是:1024)。 参数3:一般是,创建+权限(可读可写),IPC_CREAT|0600

2.当一段共享内存被闯将以后,它并不能被任何进程访问。必须使用 shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区映射到调用进程的地址空间,随后可像本地空间一样访问。

shmat ,参数1:共享内存id ,参数2:一般写0,默认是由linux内核自行安排共享内存, 参数3:一般写0,代表映射的共享内存是可读可写。若是指定了 SHM_RDONLY ,则是以只读的方式。

3.shmdt函数函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能访问该共享内存而已。

4.shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

shmctl ,参数2:删除指令,一般写IPC_RMID ,参数3:一般写0,因为我们一般不关心共享内存中的数据信息。

编程思路:

  1. 创建/打开共享内存
  2. 连接映射共享内存
  3. 写入数据 strcpy
  4. 断开释放共享内存
  5. 干掉共享内存
6.3代码demo

shm_write.c

#include 
#include 
#include 
#include 
#include 
 
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
 
int main()
{
        int shmid;
        char *shmaddr;
 
        key_t key;
        key = ftok(".",1);    //“.” 代表当前路径 ,第二个参数随意数字
        
        //创建共享内存
        shmid = shmget(key,1024*4,IPC_CREAT|0600);
        if(shmid == -1){
                printf("creat shm fail\n");
                exit(-1);
        }
 
        //连接映射共享内存
        shmaddr = shmat(shmid,0,0);
 
        printf("shmat OK\n");
 
        //将数据拷贝到共享内存
        strcpy(shmaddr,"hello world\n");
 
        sleep(5);            //等待5秒,避免一下子断开连接。等待另外一个进程读完。
 
        //断开共享内存连接
        shmdt(shmaddr);
        //删除共享内存
        shmctl(shmid,IPC_RMID,0);
 
        printf("quit\n");
 
        return 0;
}

shm_get.c

#include 
#include 
#include 
#include 
#include 
 
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
 
int main()
{
        int shmid;
        char *shmaddr;
 
        key_t key;
        key = ftok(".",1);    //“.” 代表当前路径 ,第二个参数随意数字
 
        //打开共享内存    
        shmid = shmget(key,1024*4,0);    //以打开方式的时候,第三个参数写0,直接获取,不创建
        if(shmid == -1){
                printf("creat shm fail\n");
                exit(-1);
        }
 
        //连接并映射共享内存
        shmaddr = shmat(shmid,0,0);
 
        printf("get from shm_write message is : %s",shmaddr);
 
        //断开共享内存连接
        shmdt(shmaddr);
 
        //删除共享内存
        shmctl(shmid,IPC_RMID,0);
 
        printf("quit\n");
 
        return 0;
}

7. 对比总结简图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iALLbQvM-1688607361416)(…/…/…/…/…/…/Pictures/Camera Roll/11jpg.jpg)]

8.结合消息队列,共享内存,信号量三个知识的代码demo

8.1代码

get.c

#include
#include
#include  // shared memory
#include  // semaphore
#include  // message queue
#include   // memcpy
 
// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};
 
// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};
 
// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}
 
// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}
 
// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}
 
// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}
 
// 创建一个信号量集
int creat_sem(key_t key)
{
    int sem_id;
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  /*初值设为1资源未占用*/
    return sem_id;
}
 
 
int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    char data[] = "this is server";
    struct shmid_ds buf1;  /*用于删除共享内存*/
    struct msqid_ds buf2;  /*用于删除消息队列*/
    struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/
 
    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }
 
    // 创建共享内存
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
    {
        perror("Create Shared Memory Error");
        exit(1);
    }
 
    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }
 
 
    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }
 
    // 创建信号量
    semid = creat_sem(key);
    
    // 读数据
    while(1)
    {
        msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
        if(msg.mtext == 'q')  /*quit - 跳出循环*/ 
            break;
        if(msg.mtext == 'r')  /*read - 读共享内存*/
        {
            sem_p(semid);
            printf("%s\n",shm);
            sem_v(semid);
        }
    }
 
    // 断开连接
    shmdt(shm);
 
    /*删除共享内存、消息队列、信号量*/
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);
    return 0;
}

send.c

#include
#include
#include  // shared memory
#include  // semaphore
#include  // message queue
#include   // memcpy
 
// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};
 
// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};
 
// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}
 
// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;
 
    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}
 
 
int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1; /*while循环条件*/
 
    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }
 
    // 获取共享内存
    if((shmid = shmget(key, 1024, 0)) == -1)
    {
        perror("shmget error");
        exit(1);
    }
 
    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }
 
    // 创建消息队列
    if ((msqid = msgget(key, 0)) == -1)
    {
        perror("msgget error");
        exit(1);
    }
 
    // 获取信号量
    if((semid = semget(key, 0, 0)) == -1)
    {
        perror("semget error");
        exit(1);
    }
    
    // 写数据
    printf("***************************************\n");
    printf("*                 IPC                 *\n");
    printf("*    Input r to send data to server.  *\n");
    printf("*    Input q to quit.                 *\n");
    printf("***************************************\n");
    
    while(flag)
    {
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch(c)
        {
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  /*访问资源*/
                scanf("%s", shm);
                sem_v(semid);  /*释放资源*/
                /*清空标准输入缓冲区*/
                while((c=getchar())!='\n' && c!=EOF);
                msg.mtype = 888;  
                msg.mtext = 'r';  /*发送消息通知服务器读数据*/
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                /*清空标准输入缓冲区*/
                while((c=getchar())!='\n' && c!=EOF);
        }
    }
 
    // 断开连接
    shmdt(shm);
 
    return 0;
}

四、Linux多线程

1,linux多线程介绍

1.1进程与线程

典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。

进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

"进程——资源分配的最小单位,线程——程序执行的最小单位"

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些

1.2使用线程的理由

从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。

使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

1.3Linux上线程开发API概要

多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkhU8W0U-1688607361416)(…/…/…/…/…/…/Pictures/Camera Roll/12.png)]

2.线程创建和线程等待

2.1线程创建函数原型
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
void *func1(void *arg)
{
        printf("%ld thread is create\n",(unsigned long)pthread_self());    //打印线程的pid
        printf("param is %d\n",*((int *)arg));    
}
 
int main()
{
        int ret;
        pthread_t t1;     /*不使用指针是以免空指针异常*/
        int arg = 100;
 
        //创建 t1 线程
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);    //参数2:线程属性,一般设置为NULL,参数3:线程干活的函数,参数4:往t1线程里面传送数据。
        //ret = pthread_create(&t1,NULL,func1,NULL);   
 
        if(ret == 0){
                printf("create t1 success\n");
        }
 
        return 0;
}

t1 线程还没有执行完,主线程就已经运行完毕。

所以要看到 t1 线程的运行,要么加在主线程加一个 while(1) 死循环。要么用到线程的等待函数 pthread_join( )。请看下面。

2.2线程等待函数api
int pthread_join(pthread_t thread, void **rval_ptr);

// 返回:若成功返回0,否则返回错误编号

第 2 个参数,可以设置为NULL不关心线程收回的退出状态 
2.2.1第2个参数设为NULL,不关心线程收回的退出状态
#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
//nt pthread_join(pthread_t thread, void **rval_ptr);
 
void *func1(void *arg)
{
        printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t1:param is %d\n",*((int *)arg));
}
 
int main()
{
        int ret;
        pthread_t t1;
        int arg = 100;
 
        //创建线程
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);
 
        if(ret == 0){
                printf("main:create t1 success\n");
        }
 
        printf("main:%ld \n",(unsigned long)pthread_self());
 
        //线程等待/阻塞
        pthread_join(t1,NULL);
 
        return 0;
}
2.2.2关心线程收回的退出状态。用到----线程退出 pthread_exit( )

​ int pthread_exit(void *rval_ptr);

#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
//nt pthread_join(pthread_t thread, void **rval_ptr);
 
void *func1(void *arg)
{
        static char *p = "t1 is run out";    //变量必须定义前加 static ,否则二级指针指向它的时候数据会出错
 
        printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t1:param is %d\n",*((int *)arg));
 
        pthread_exit((void *)p); //线程退出,并且返回 p 指向的字符串
}
 
int main()
{
        int ret;
        pthread_t t1;
        int arg = 100;
 
        char *pret = NULL;    //不可以直接定义为二级指针
 
        //创建线程
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);
 
        if(ret == 0){
                printf("main:create t1 success\n");
        }
 
        printf("main:%ld \n",(unsigned long)pthread_self());
 
        //线程的等待/阻塞
        pthread_join(t1,(void **)&pret);  //参数2:将指针pret 指向 t1线程的p    
 
        printf("main: t1 quit :%s\n",pret);
 
        return 0;
}

3.线程同步之互斥量加锁和解锁

3.1互斥锁的作用是用来控制线程同步的,但是只能控制一个线程执行完才到下一个线程,不能保证线程的运行顺序。
3.2创建及销毁互斥锁
#include 
 
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //初始化互斥量,默认属性attr参数可以设置为NULL。
 
int pthread_mutex_destroy(pthread_mutex_t mutex);  //释放互斥量                             
 
// 返回:若成功返回0,否则返回错误编号
 
要用默认的属性初始化互斥量,只需把attr设置为NULL。
3.3加锁及解锁
#include 
 
int pthread_mutex_lock(pthread_mutex_t *mutex);    //加锁
 
int pthread_mutex_trylock(pthread_mutex_t *mutex);
 
int pthread_mutex_unlock(pthread_mutex_t *mutex);  //解锁
 
// 返回:若成功返回0,否则返回错误编号

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。

3.4代码demo
#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
pthread_mutex_t mutex;  //定义互斥量
 
void *func1(void *arg)
{
        int i;
 
        pthread_mutex_lock(&mutex);    //加锁
        for(i=0;i<5;i++){
                printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
                printf("t1:param is %d\n",*((int *)arg));
                sleep(1);
        }
        pthread_mutex_unlock(&mutex);    //解锁
}
 
void *func2(void *arg)
{
        pthread_mutex_lock(&mutex);
        printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t2:param is %d\n",*((int *)arg));
        pthread_mutex_unlock(&mutex);
}
 
void *func3(void *arg)
{
        pthread_mutex_lock(&mutex);
        printf("t3:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t3:param is %d\n",*((int *)arg));
        pthread_mutex_unlock(&mutex);
}
 
int main()
{
        int ret;
        int arg = 100;
        pthread_t t1;
        pthread_t t2;
        pthread_t t3;
 
        //初始化互斥量
        pthread_mutex_init(&mutex,NULL);
 
        //创建 t1 线程 
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);
        if(ret == 0){
                printf("main:create t1 success\n");
        }
 
        ret = pthread_create(&t2,NULL,func2,(void *)&arg);
        if(ret == 0){
                printf("main:create t2 success\n");
        }
 
        ret = pthread_create(&t3,NULL,func3,(void *)&arg);
        if(ret == 0){
                printf("main:create t3 success\n");
        }
 
        printf("main:%ld\n",(unsigned long)pthread_self());
 
        //线程等待
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        pthread_join(t3,NULL);
 
        //销毁互斥量
        pthread_mutex_destroy(&mutex);
 
        return 0;
}

不管t1、t2、t3 哪个线程先抢到锁运行 ,当t1 线程抢到锁之后都会执行完整个for循环,才会到 t2 和 t3 线程争夺运行

至于为什么main 也会在t1 运行过程中穿插进来? 那是因为main 函数并没有加锁。它不需要像t2 或 t3 那样需要等待 t1 运行完解锁之后,才能运行

4.线程 条件控制实现线程的同步

条件变量使用之前必须首先初始化,pthread_cond_t 数据类型代表的条件变量可以用两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化

4.1创建及销毁条件变量
#include 
 
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
 
int pthread_cond_destroy(pthread_cond_t cond);
 
// 返回:若成功返回0,否则返回错误编号

除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。

4.2等待
#include 
 
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
 
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
 
// 返回:若成功返回0,否则返回错误编号

pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。

pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

4.3触发
#include 
 
int pthread_cond_signal(pthread_cond_t cond);       //触发
 
int pthread_cond_broadcast(pthread_cond_t cond);    //广播
 
// 返回:若成功返回0,否则返回错误编号

这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。

注意一定要在改变条件状态以后再给线程发信号

4.4demo
#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
int g_data = 0;
 
pthread_mutex_t mutex;    //锁
pthread_cond_t  cond;     //条件   
 
void *func1(void *arg)
{
        printf("t1: %ld thread is create\n",(unsigned long)pthread_self());
        printf("t1: param is %d\n",*((int *)arg));
 
        while(1){
                pthread_cond_wait(&cond,&mutex);    //条件的等待
                printf("t1 run =========================\n");
 
                printf("t1: %d\n",g_data);
                g_data = 0;
 
                sleep(1);
        }
}
 
void *func2(void *arg)
{
        printf("t2: %ld thread is create\n",(unsigned long)pthread_self());
        printf("t2: param is %d\n",*((int *)arg));
 
        while(1){
                printf("t2: %d\n",g_data);
 
                pthread_mutex_lock(&mutex);            //加锁
                g_data++;
                if(g_data == 3){
                        pthread_cond_signal(&cond);    //条件的信号
                }
                pthread_mutex_unlock(&mutex);          //解锁
                sleep(1);
        }
}
 
int main()
{
        int ret;
        int arg = 100;
        pthread_t t1;
        pthread_t t2;
 
        pthread_mutex_init(&mutex,NULL);    //锁的创建(动态初始化)
        pthread_cond_init(&cond,NULL);      //条件的创建(动态初始化)
 
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);
        if(ret == 0){
//              printf("main:create t1 success\n");
        }
 
        ret = pthread_create(&t2,NULL,func2,(void *)&arg);
        if(ret == 0){
//              printf("main:create t2 success\n");
        }
 
//      printf("main:%ld\n",(unsigned long)pthread_self());
 
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
 
        pthread_mutex_destroy(&mutex);    //销毁锁
        pthread_cond_destroy(&cond);        //条件的销毁
 
        return 0;
}

整个代码,主要就是让线程2中的 data++ 到 3 的时候,就触发发出一个信号signal,

线程1等待接收到信号signal,之后就会开始运行。

4.5测试脚本
int main()
{
        int i;
 
        for(i=0;i<10;i++){
                system("./pthread");
        }
 
        return 0;
}

命令:chmod +x test.sh (*chmod +x的意思就是给执行权限*

./test.sh (运行)

将测试结果保存到文档 : ./test.sh >>ret.txt & (加个&有点类似于后台运行)

4.6测试代码,将测试结果放进文档
demo5.c
#include 
#include 
#include 
 
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
//       void *(*start_rtn)(void *), void *restrict arg);
 
int g_data = 0;
 
pthread_mutex_t mutex;
pthread_cond_t  cond;
 
void *func1(void *arg)
{
        printf("t1: %ld thread is create\n",(unsigned long)pthread_self());
        printf("t1: param is %d\n",*((int *)arg));
 
        static int cnt = 0;
 
        while(1){
                pthread_cond_wait(&cond,&mutex);
                printf("t1 run =========================\n");
 
                printf("t1: %d\n",g_data);
                g_data = 0;
                sleep(1);
                if(cnt++ == 10){
                        exit(1);
                }
        }
}
 
void *func2(void *arg)
{
        printf("t2: %ld thread is create\n",(unsigned long)pthread_self());
        printf("t2: param is %d\n",*((int *)arg));
 
        while(1){
                printf("t2: %d\n",g_data);
 
                pthread_mutex_lock(&mutex);
                g_data++;
                if(g_data == 3){
                        pthread_cond_signal(&cond);
                }
                pthread_mutex_unlock(&mutex);
                sleep(1);
        }
}
 
int main()
{
        int ret;
        int arg = 100;
        pthread_t t1;
        pthread_t t2;
 
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);
 
        ret = pthread_create(&t1,NULL,func1,(void *)&arg);
        if(ret == 0){
//              printf("main:create t1 success\n");
        }
 
        ret = pthread_create(&t2,NULL,func2,(void *)&arg);
        if(ret == 0){
//              printf("main:create t2 success\n");
        }
 
//      printf("main:%ld\n",(unsigned long)pthread_self());
 
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
 
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);
 
        return 0;
}
test.c
int main(int argc,char **argv)
{
        int i;
 
        int time = atoi(argv[1]);
 
        for(i=0;i

测试代码并记录进文档:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNlqezUj-1688607361417)(…/…/…/…/…/…/Pictures/Camera Roll/13.jpg)]

test.ret.txt 文档:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dt7Qel8w-1688607361417)(…/…/…/…/…/…/Pictures/Camera Roll/14.png)]

5.死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PV5jRPYC-1688607361417)(…/…/…/…/…/…/Pictures/Camera Roll/15.png)]

当线程t1有第一把锁的时候,它想获得第二把锁
而线程t2有第二把的时候,它想获得第一把锁
此时线程 t1 和 t2 都想获得对方手中的那把锁
谁都不往下进行 解锁
结果就会造成 死锁

6.生产者和消费者

#iinclude 
#include 
#include 
#include 
#include 
    
struct Msg                //共享数据
{
        int num;
        struct Msg *next;
};
 
struct Msg *head;
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    //定义并初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;    //定义并初始化一个条件变量
 
void err_thread(int ret,char *str)  //如果线程创建失败,则返回失败的原因,并退出线程....
{
        if(ret != 0){
                fprintf(stderr,"%s:%s\n",str,strerror(ret));
                pthread_exit(NULL);
        }
}
 
void *produser(void *arg)    //生产者
{
        while(1){
                struct Msg *food = (struct Msg *)malloc(sizeof(struct Msg));    //生产一个食物
                food->num = rand() % 1000 + 1;        //初始化食物,模拟生产了一个数据
                printf("--produser %d\n",food->num);
 
                pthread_mutex_lock(&mutex);          //加锁
                food->next = head;                  //写公共区域      
                head = food;
                pthread_mutex_unlock(&mutex);       //解锁
 
                pthread_cond_signal(&has_data);    //唤醒阻塞在条件变量 has_data上的线程
 
                sleep(rand() % 3);
        }
}
 
void *consumer(void *arg)    //消费者
{
        struct Msg *food;
 
        while(1){
                pthread_mutex_lock(&mutex);                    //加锁 互斥量
                if(head == NULL){    
                        pthread_cond_wait(&has_data,&mutex);    //阻塞等待条件变量,解锁
                }                                               //pthread_cond_wait 返回时,重新加锁                         
 
                food = head;
                head = food->next;
 
                pthread_mutex_unlock(&mutex);                   //解锁
                printf("-------------------consumer:%d\n",food->num);
 
                free(food);
                sleep(rand() % 3);
        }
}
 
int main()
{
        int ret;
        pthread_t pid,cid;
 
        srand(time(NULL));
 
        ret = pthread_create(&pid,NULL,produser,NULL);    //生产者线程
        if(ret != 0){
                err_thread(ret,"pthread_creat error");
        }
 
        ret = pthread_create(&cid,NULL,consumer,NULL);    //消费者线程
        if(ret != 0){
                err_thread(ret,"pthread_creat error");
        }
 
        pthread_join(pid,NULL);
        pthread_join(cid,NULL);
 
        return 0;
}

五、网络编程

1.TCP/UDP的区别,端口号的作用
1.1TCP/UDP的区别
  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前,不需要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接发送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCO连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. TCP首部开销20字节,UDP首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
2.1端口的作用

一台拥有IP地址的主机可以提供许多服务,比如 Web服务、FTP服务、SMTP服务等

这些服务完全可以通过1个IP地址来实现。那么,之际是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IPI地址与网络服务的关系是一对多的关系。

实际上通过“IP地址+端口号”来区分不同的服务的。

端口提供了一种访问通道。

服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69.

2.字节序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKjAVEzw-1688607361418)(…/…/…/…/…/…/Pictures/Camera Roll/16.jpg)]

x86 系列 CPU 都是 little - endian 的字节序
网络字节序 = 大端字节序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4EOuNGW-1688607361418)(…/…/…/…/…/…/Pictures/Camera Roll/17.jpg)]

3. socket 服务器实现双方消息发送

3.1socket 服务器实现不断接收客户端信息

做法:用一个while(1) 死循环,使得 socket 服务器不断接收accept ( ) 客户端的数据。

当客户端有数据接入,调用fork( ) 函数创建子进程去处理数据。

server.c

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
 
        char *msg = "I got your message";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
 
        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));
 
        if(argc != 3){                                //判断一下参数,否则运行参数没写对的时候,出现段错误不知道是哪里搞错。
                printf("param isn't right\n");
        }
 
        //1.socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socked");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr = "127.0.0.1";  (error)
        inet_aton(argv[1],&s_addr.sin_addr);
 
        //2.bind
        bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
 
        //3.listen
        listen(s_fd,10);
 
        //4.accept
        int clen = sizeof(struct sockaddr_in);
 
        while(1){
                c_fd = accept(s_fd,(struct sockaddr *)&c_addr, &clen);
                if(c_fd == -1){
                        perror("accept");
                }
                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
 
                if(fork() == 0){
                        //5.read
                        n_read = read(c_fd,readBuf,128);
 
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("get message: %d ,%s\n",n_read,readBuf);
                        }
 
                        //6.write
                        write(c_fd,msg,strlen(msg));
 
                        break;    //子进程做完一次就退出,父进程继续接收数据
                }
        }
        return 0;
}
~

client.c

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        char readBuf[128];
 
        char *msg = "from client msg";
        struct sockaddr_in c_addr;
 
        memset(&c_addr,0,sizeof(struct sockaddr_in));
 
        if(argc != 3){                            //判断一下参数,否则参数没写对的时候,出现段错误不知道是哪里搞错。
                printf("param isn't right\n");
        }
 
        //1.socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }
 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr = "127.0.0.1";  (error)
        inet_aton(argv[1],&c_addr.sin_addr);
 
//      memset(&c_addr,0,sizeof(struct sockaddr_in));   (error:清空位置不对)                    
 
        //2.connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
                perror("connect");
                exit(-1);
        }
 
        //3.send
        int n_write = write(c_fd,msg,strlen(msg));
        if(n_write == -1){
                perror("write:");
        }
 
        //4.read
        n_read = read(c_fd,readBuf,128);
        if(n_read == -1){
                perror("read");
        }else{
                printf("get message from server: %d ,%s\n",n_read,readBuf);
        }
        return 0;
}

4.一个服务器对应一个客户端作双方交互信息

服务端创建子进程不断接收客户端数据,再创建一个子进程发送数据,做不同的事情;客户端创建子进程发送数据,父进程读取数据;一个进程一个while(1);

server.c

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
 
        int mark = 0;
 
        char msg[128] = {0};
        //      char *msg = "I got your message";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
 
        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));
 
        if(argc != 3){
                printf("param isn't right\n");
        }
 
        //1.socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socked");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr = "127.0.0.1";  (error)
        inet_aton(argv[1],&s_addr.sin_addr);
 
        //2.bind
        bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
 
        //3.listen
        listen(s_fd,10);
 
        //4.accept
        int clen = sizeof(struct sockaddr_in);
 
        while(1){
                c_fd = accept(s_fd,(struct sockaddr *)&c_addr, &clen);
                if(c_fd == -1){
                        perror("accept");
                }
                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
 
                mark++;
 
                if(fork() == 0){
 
                        if(fork() == 0){
                                while(1){
                                        //6.write
                                        memset(msg,0,sizeof(msg));
                                        sprintf(msg,"welcome %d client",mark);    //将回复的信息直接放到msg 回复
                                        write(c_fd,msg,strlen(msg));
                                        sleep(3);
                                }
                        }
 
                        //5.read
                        while(1){
                                memset(readBuf,0,sizeof(readBuf));
 
                                n_read = read(c_fd,readBuf,128);
                                if(n_read == -1){
                                        perror("read");
                                }else{
                                        printf("\nget:%s\n",readBuf);
                                }
 
                        }
                break;
                }
        }
        return 0;
}

client.c

#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        char readBuf[128];
 
 
        char msg[128] = {0};
        //      char *msg = "from client msg";
        struct sockaddr_in c_addr;
 
        memset(&c_addr,0,sizeof(struct sockaddr_in));
 
        if(argc != 3){
                printf("param isn't right\n");
        }
 
        //1.socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }
 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr = "127.0.0.1";  (error)
        inet_aton(argv[1],&c_addr.sin_addr);
 
        //      memset(&c_addr,0,sizeof(struct sockaddr_in));   (error:清空位置不对)                    
 
        //2.connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
                perror("connect");
                exit(-1);
        }
 
 
        while(1){
                if(fork() == 0){
                        while(1){
                                //3.send
                                memset(msg,0,sizeof(msg));
                                printf("input: ");
                                gets(msg);
                                write(c_fd,msg,strlen(msg));
                        }
                }
 
                while(1){
                        //4.read
                        memset(readBuf,0,sizeof(readBuf));
                        n_read = read(c_fd,readBuf,128);
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("\nget: %s\n",readBuf);
                        }
                }
        }
 
        return 0;
}

5.服务器与多个客户端聊天,但是服务器自动回复

server.c

#include
#include          /* See NOTES */
#include
#include
#include 
#include 
#include 

int main(int argc,char **argv)
{
	int s_fd;
	int c_fd;
	int n_read;
        int mark=0;
	char buf[128]={0};
	char msg[128]={0};
	//	char *msg="I get the msg";

	struct sockaddr_in s_addr; 
	struct sockaddr_in c_addr; 

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc!=3)
	{
		printf("params no ok\n");
		exit(-1);
	}

	//1.socket
	s_fd=socket(AF_INET,SOCK_STREAM,0);
	if(s_fd==-1)
	{
		perror("socket");
		exit(-1);
	}  
	//2.bind 
	s_addr.sin_family=AF_INET;
	s_addr.sin_port=htons(atoi(argv[2]));    
	inet_aton(argv[1],&s_addr.sin_addr);        

	bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in)); 

	//3.listen
	listen(s_fd,10);


	int len=sizeof(struct sockaddr_in);
	while(1)
	{
		//4.accept
		c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&len);    
		if(c_fd==-1)
		{
			perror("accept");
		}    

                mark++;
		printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

		if(fork()==0)
		{

			if(fork()==0)
			{
				//5.write
				while(1)
				{
                                        memset(msg,0,sizeof(msg));
                                        sprintf(msg,"welcome No %d client",mark);
					write(c_fd,msg,strlen(msg));
                                        sleep(2);
				}
			}

			//6.read
			while(1)
			{
                                memset(buf,0,sizeof(buf));
				n_read=read(c_fd,buf,sizeof(buf));  
				if(n_read==-1)
				{
					perror("read");
				}
				else
				{
					printf("read client:%s\n",buf);
				}
			}  
		}
	}
	//7.close
	close(s_fd);

	return 0;
}

client.c

#include
#include          /* See NOTES */
#include
#include
#include
#include
#include

int main(int argc,char **argv)
{
	int c_fd;
	int n_read;
	char buf[128]={0};
	char msg[128]={0};
	//	char *msg="msg from client";


	struct sockaddr_in c_addr; 

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc!=3)
	{
		printf("params is no ok\n");
		exit(-1);
	}

	//1.socket
	c_fd=socket(AF_INET,SOCK_STREAM,0);
	if(c_fd==-1)
	{
		perror("socket");
		exit(-1);
	}  
	//2.bind 
	c_addr.sin_family=AF_INET;
	c_addr.sin_port=htons(atoi(argv[2]));    
	inet_aton(argv[1],&c_addr.sin_addr);        

	//3.connect
	if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in))==-1)
	{
		perror("connect");
		exit(-1);
	}   
        
      
        while(1)
        { 
		//4.send
		if(fork()==0)
		{
			while(1)
			{
				memset(msg,0,sizeof(msg));
				printf("input: ");
				gets(msg);
				write(c_fd,msg,strlen(msg));
			}
		}
		//5.read
		while(1)
		{
			memset(buf,0,sizeof(buf));
			n_read=read(c_fd,buf,sizeof(buf));  
			if(n_read==-1)
			{
				perror("read");
			}
			else
			{
				printf("read sever:%s\n",buf);
			}
		}
        }

	//6.close
	close(c_fd);

	return 0;
}

6.FTP云盘:

功能盘点:
lls:列出本地的文件
lcd:进入本地的文件路径
cd:进入服务端的路径
ls:列出服务端的文件
quit:退出客户端
get:获取服务端的文件
put:上传客户端的文件

server.c

#include
#include          /* See NOTES */
#include
#include
#include
#include
#include
#include 
#include 
#include"config.h"

int get_cmd_type(char *cmd)
{
	if(strcmp("ls",cmd)==0)      return LS;
	if(strcmp("pwd",cmd)==0)     return PWD;
	if(strcmp("quit",cmd)==0)    return QUIT;     

	if(strstr(cmd,"cd"))         return CD;
	if(strstr(cmd,"get"))        return GET;
        if(strstr(cmd,"put"))        return PUT;

	return -1;
}


char* getDir(char *cmd)
{
	char *p=NULL;

	p=strtok(cmd," ");
	p=strtok(NULL," ");

	return p;
}


void cmdHandler(struct MSG msg,int c_fd)
{
	int ret;
	int fd;
        int p_fd;
	FILE *fp;
	char *filename=NULL;
	char *file=NULL;
        char *filename2=NULL;
	char dataBuf[1024]={0};

	printf("%s\n",msg.cmd);
	ret=get_cmd_type(msg.cmd); 

	printf("ret=%d\n",ret);
	switch(ret)
	{
		case LS:
		case PWD:
			fp=popen(msg.cmd,"r");     
			fread(msg.cmd,sizeof(msg.cmd),1,fp);
			write(c_fd,&msg,sizeof(msg));
			break;
		case CD:
			filename=getDir(msg.cmd);
			printf("filename:%s\n",filename);
			chdir(filename);
			break;
		case GET:
			file=getDir(msg.cmd);                       
			if(access(file,F_OK)==-1)
			{
				strcpy(msg.cmd,"NO this file");
				write(c_fd,&msg,sizeof(msg));
			}
			else
    		        {  
                                msg.type=8;
            			fd=open(file,O_RDWR);            
				read(fd,dataBuf,sizeof(dataBuf));
				close(fd);

				strcpy(msg.cmd,dataBuf);
				write(c_fd,&msg,sizeof(msg));
                                printf("success\n"); 
			} 
			break;
                case PUT:
                        filename2=getDir(msg.cmd);
                        printf("filename1:%s\n",filename2);
                        p_fd=open(filename2,O_RDWR|O_CREAT,0666);
                        write(p_fd,msg.dataBuf,strlen(msg.dataBuf));
                        printf("write success\n");
                        close(p_fd);                        
                        break;
		case QUIT:
			printf("client out\n");
			exit(-1);
			break;             
	}


}



int main(int argc,char **argv)
{
	int s_fd;
	int c_fd;
	int n_read;        

	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;

	struct MSG msg;

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc!=3)
	{
		printf("params is no ok\n");
		exit(-1);
	}
	//1.socket  
	s_fd=socket(AF_INET,SOCK_STREAM,0);        
	if(s_fd==-1)
	{
		perror("socket");
		exit(-1);
	}

	//2.bind
	s_addr.sin_family=AF_INET;
	s_addr.sin_port=htons(atoi(argv[2]));   
	inet_aton(argv[1],&s_addr.sin_addr);    

	bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));    

	//3.listen
	listen(s_fd,10);

	int len=sizeof(struct sockaddr_in);

	while(1)
	{ 
		//4.accept 
		c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&len);    
		if(c_fd==-1)
		{
			perror("accept:");
		}

		printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

		if(fork()==0)
		{
			while(1)
			{
				memset(&msg.cmd,0,sizeof(msg));      
				n_read=read(c_fd,&msg,sizeof(msg));
				if(n_read==0)
				{
					printf("clinet out");
					break;
				}
				else if(n_read>0)
				{
					cmdHandler(msg,c_fd);
				}
			} 
		}  

	}
	close(s_fd);
	close(c_fd);
}

client.c

#include
#include          /* See NOTES */
#include
#include
#include
#include
#include
#include
#include 
#include 
#include"config.h"

int get_cmd_type(char *cmd)
{
	if(strcmp(cmd,"ls")==0)    return LS;
	if(strcmp(cmd,"pwd")==0)   return PWD;
	if(strcmp(cmd,"quit")==0)  return QUIT;   

	if(strstr(cmd,"lcd"))      return LCD;
	if(strstr(cmd,"cd"))       return CD;
	if(strstr(cmd,"lls"))      return LLS;  
	if(strstr(cmd,"get"))      return GET;         ;   
    if(strstr(cmd,"put"))      return PUT;   

	return -1;
}

char* getDir(char *cmd)
{
	char *p=NULL;
	p=strtok(cmd," ");
	p=strtok(NULL," ");

	return p;
}


int cmd_handler(struct MSG msg,int c_fd)
{
	int ret;
    int fd;
	char *filename=NULL;
    char *dir=NULL;
    char buf[32]={0};

	ret=get_cmd_type(msg.cmd);

	switch(ret)
	{
		case LS:
		case PWD:
		case CD:
		case GET:
			write(c_fd,&msg,sizeof(msg));
			break;
		case LLS:
			system("ls");
			break; 
		case LCD:
			filename=getDir(msg.cmd);
                      //  if(access(filename,F_OK)==-1)
                       // { 
                         //    printf("\nno this file\n");
                       // } 
			printf("filename:%s\n",filename);
			chdir(filename);
			break;
                case PUT:
                        strcpy(buf,msg.cmd);
                        dir=getDir(buf);
                        printf("dir:%s\n",dir);
                        if(access(dir,F_OK)==-1)
                        {
                            printf("%s no exsit\n",dir);
                        }
                        else
                        {
                            fd=open(dir,O_RDWR);
                            read(fd,msg.dataBuf,strlen(msg.dataBuf));
                            close(fd);

                            write(c_fd,&msg,sizeof(msg));
                            printf("ok\n");             
                        }
                        break;
		case QUIT:
			write(c_fd,&msg,sizeof(msg));
			close(c_fd);
			exit(-1);
			break;       
	}

	return ret;
}

void cmd_serverMsg_handler(struct MSG msg,int c_fd)
{
	int n_read;
	int fd;
	char *file=NULL;
	struct MSG getmsg;

	n_read=read(c_fd,&getmsg,sizeof(getmsg));
	if(n_read==0)
	{
		printf("sever quit\n");
	}

	if(getmsg.type==8)
	{
		file=getDir(msg.cmd);
		fd=open(file,O_RDWR|O_CREAT,0600);
                printf("open success\n");
		write(fd,getmsg.cmd,strlen(getmsg.cmd));
	}
	else
	{
		printf("-------------------------------------\n");
		printf("%s\n",getmsg.cmd);
		printf("-------------------------------------\n");
	}
}


int main(int argc,char **argv)
{
	int c_fd;
	int ret;

	struct sockaddr_in c_addr;

	struct MSG msg;

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc!=3)
	{
		printf("params is no good\n");
		exit(-1);
	} 

	//1.socket
	c_fd=socket(AF_INET,SOCK_STREAM,0);
	if(c_fd==-1)
	{
		perror("socket");
		exit(-1);
	}   

	//2.connect
	c_addr.sin_family=AF_INET; 
	c_addr.sin_port=htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);   

	if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in))==-1)
	{
		perror("connect");
		exit(-1);
	}  

	printf("connect....\n");

	//3.
	while(1)
	{
		memset(msg.cmd,0,sizeof(msg));      

		gets(msg.cmd);

		ret=cmd_handler(msg,c_fd);
		if(ret==CD||ret==LLS||ret==LCD||ret==QUIT||ret==PUT)
		{
			continue;

		}
		if(ret==-1)
		{
			printf("this is command no exist\n");
			continue;
		}

		cmd_serverMsg_handler(msg,c_fd);

	}

	close(c_fd);

	return 0;
}

config.h

#define LS   0
#define PWD  1

#define CD   2
#define GET  3
#define PUT  4

#define LLS  5
#define LCD  6

#define QUIT 7


struct MSG 
{
    int type;
    char cmd[1024];
    char dataBuf[1024];
};

it(-1);
break;
}

}

int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int n_read;

struct sockaddr_in s_addr;
struct sockaddr_in c_addr;

struct MSG msg;

memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));

if(argc!=3)
{
	printf("params is no ok\n");
	exit(-1);
}
//1.socket  
s_fd=socket(AF_INET,SOCK_STREAM,0);        
if(s_fd==-1)
{
	perror("socket");
	exit(-1);
}

//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));   
inet_aton(argv[1],&s_addr.sin_addr);    

bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));    

//3.listen
listen(s_fd,10);

int len=sizeof(struct sockaddr_in);

while(1)
{ 
	//4.accept 
	c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&len);    
	if(c_fd==-1)
	{
		perror("accept:");
	}

	printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

	if(fork()==0)
	{
		while(1)
		{
			memset(&msg.cmd,0,sizeof(msg));      
			n_read=read(c_fd,&msg,sizeof(msg));
			if(n_read==0)
			{
				printf("clinet out");
				break;
			}
			else if(n_read>0)
			{
				cmdHandler(msg,c_fd);
			}
		} 
	}  

}
close(s_fd);
close(c_fd);

}


   client.c

#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
#include"config.h"

int get_cmd_type(char *cmd)
{
if(strcmp(cmd,“ls”)==0) return LS;
if(strcmp(cmd,“pwd”)==0) return PWD;
if(strcmp(cmd,“quit”)==0) return QUIT;

if(strstr(cmd,"lcd"))      return LCD;
if(strstr(cmd,"cd"))       return CD;
if(strstr(cmd,"lls"))      return LLS;  
if(strstr(cmd,"get"))      return GET;         ;   
if(strstr(cmd,"put"))      return PUT;   

return -1;

}

char* getDir(char *cmd)
{
char *p=NULL;
p=strtok(cmd," “);
p=strtok(NULL,” ");

return p;

}

int cmd_handler(struct MSG msg,int c_fd)
{
int ret;
int fd;
char *filename=NULL;
char *dir=NULL;
char buf[32]={0};

ret=get_cmd_type(msg.cmd);

switch(ret)
{
	case LS:
	case PWD:
	case CD:
	case GET:
		write(c_fd,&msg,sizeof(msg));
		break;
	case LLS:
		system("ls");
		break; 
	case LCD:
		filename=getDir(msg.cmd);
                  //  if(access(filename,F_OK)==-1)
                   // { 
                     //    printf("\nno this file\n");
                   // } 
		printf("filename:%s\n",filename);
		chdir(filename);
		break;
            case PUT:
                    strcpy(buf,msg.cmd);
                    dir=getDir(buf);
                    printf("dir:%s\n",dir);
                    if(access(dir,F_OK)==-1)
                    {
                        printf("%s no exsit\n",dir);
                    }
                    else
                    {
                        fd=open(dir,O_RDWR);
                        read(fd,msg.dataBuf,strlen(msg.dataBuf));
                        close(fd);

                        write(c_fd,&msg,sizeof(msg));
                        printf("ok\n");             
                    }
                    break;
	case QUIT:
		write(c_fd,&msg,sizeof(msg));
		close(c_fd);
		exit(-1);
		break;       
}

return ret;

}

void cmd_serverMsg_handler(struct MSG msg,int c_fd)
{
int n_read;
int fd;
char *file=NULL;
struct MSG getmsg;

n_read=read(c_fd,&getmsg,sizeof(getmsg));
if(n_read==0)
{
	printf("sever quit\n");
}

if(getmsg.type==8)
{
	file=getDir(msg.cmd);
	fd=open(file,O_RDWR|O_CREAT,0600);
            printf("open success\n");
	write(fd,getmsg.cmd,strlen(getmsg.cmd));
}
else
{
	printf("-------------------------------------\n");
	printf("%s\n",getmsg.cmd);
	printf("-------------------------------------\n");
}

}

int main(int argc,char **argv)
{
int c_fd;
int ret;

struct sockaddr_in c_addr;

struct MSG msg;

memset(&c_addr,0,sizeof(struct sockaddr_in));

if(argc!=3)
{
	printf("params is no good\n");
	exit(-1);
} 

//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1)
{
	perror("socket");
	exit(-1);
}   

//2.connect
c_addr.sin_family=AF_INET; 
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);   

if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in))==-1)
{
	perror("connect");
	exit(-1);
}  

printf("connect....\n");

//3.
while(1)
{
	memset(msg.cmd,0,sizeof(msg));      

	gets(msg.cmd);

	ret=cmd_handler(msg,c_fd);
	if(ret==CD||ret==LLS||ret==LCD||ret==QUIT||ret==PUT)
	{
		continue;

	}
	if(ret==-1)
	{
		printf("this is command no exist\n");
		continue;
	}

	cmd_serverMsg_handler(msg,c_fd);

}

close(c_fd);

return 0;

}


   config.h

#define LS 0
#define PWD 1

#define CD 2
#define GET 3
#define PUT 4

#define LLS 5
#define LCD 6

#define QUIT 7

struct MSG
{
int type;
char cmd[1024];
char dataBuf[1024];
};


你可能感兴趣的:(linux,网络,算法)