Linux高并发服务器开发——1系统编程入门

本文介绍了Linux系统编程的基础知识,包括进程间通信方式、进程和线程的概念及其管理,讲解了静态库和动态库的制作与使用,以及TCP和UDP的区别。同时,详细阐述了网络编程中的IO多路复用、TCP的三次握手和四次挥手,还涉及GDB调试工具的使用和文件描述符的相关操作。
摘要由CSDN通过智能技术生成

01/为什么要学习这门课

1.进程间通信的方式

2.僵尸进程,孤儿进程

3.线程同步怎么解决

4.大端和小端的区别

5.IO多路复用有哪些方式?区别?

6.静态库跟共享库的制作以及使用

7.滑动窗口的机制

8.TCP三次握手,四次挥手

9.TCP和UDP的区别

image-20230404151541451

02/课程内容

1.系统编程入门

2.多进程开发

3.多线程开发

4.网络编程

5.项目实战与总结

1.1 Linux开发环境搭建

虚拟机软件修改网络适配器为NAT模式。

1.2 1.3 GCC

GCC(GNU Compiler Collection,GNU编译器套件)

工作流程

image-20230406110100451

◼ gcc 和 g++都是GNU(组织)的一个编译器。

xftp设置utf-8避免中文乱码的方法

https://jingyan.baidu.com/article/0bc808fc03296a1bd485b9ed.html

常用参数选项

image-20230406112838366

1.4 静态库的制作

image-20230406115601281

1.5 静态库的使用

//src目录下生成.o文件
gcc -c add.c sub.c mult.c div.c -I ../include/
ar rcs libsuanshu.a add.o div.o mult.o sub.o
//将库移动到lib目录下,返回到上一级
mv libsuanshu.a ../lib/
cd ..
//编译自己的程序
gcc main.c -o app -I ./include/ -L ./lib/ -l suanshu
//编译通过生成app可执行文件

1.6 动态库的制作和使用

image-20230407190230223

◼ 静态库:GCC 进行链接时,会把静态库中代码打包可执行程序

◼ 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中

◼ 程序启动之后,动态库会被动态加载到内存中,通过 **ldd (list dynamic dependencies)**命令检查动态库依赖关系

◼ 如何定位共享库文件呢? 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路 径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是 由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib 目录找到库文件后将其载入内存。

动态库加载失败解决方法

第一种方法-配置环境变量

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chaoyu/linux/lesson06/library/lib
echo $LD_LIBRARY_PATH
//这种方法只能在一个终端下,关闭该终端,下次则会失效

//用户级别下配置
//在根目录下编辑.bashrc文件,在文件结尾添加LD_LIBRARY_PATH,保存退出记得source
vim .bashrc
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chaoyu/linux/lesson06/library/lib

//或者在管理员级别下配置
//在文件结尾添加LD_LIBRARY_PATH,保存退出记得source
sudo vim /etc/profile
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chaoyu/linux/lesson06/library/lib

第二种方法-配置/etc/ld.so.cache文件列表

sudo vim /etc/ld.so.conf
//直接把路径加到末尾即可
/home/chaoyu/linux/lesson06/library/lib

//保存退出再执行
sudo ldconfig

第三种方法-添加到/lib/,/usr/lib 目录(这个方法不建议)

1.9 静态库和动态库的对比

image-20230407202020190

静态库制作过程

image-20230407202340471

动态库制作过程

image-20230407202427666

静态库的优缺点

image-20230407202812926

动态库的优缺点

image-20230407202839533

1.10 Makefile

Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编 译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就 像一个 Shell 脚本一样,也可以执行操作系统的命令。

“自动化编译” ,一旦写好,只需要一个 make 命令,整 个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个 解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令

image-20230409214208127

工作原理

◼ 命令在执行之前,需要先检查规则中的依赖是否存在

 如果存在,执行命令

 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的, 如果找到了,则执行该规则中的命令

检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

 如果依赖的时间比目标的时间晚,需要重新生成目标

 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被 执行

image-20230410100543013

image-20230410101503452

image-20230410102052879

image-20230410102113276

image-20230410103432499

image-20230410103547090

使用make clean可以将.o等依赖文件都删除

image-20230410104218335

1.13 GDB调试

GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环 境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

准备工作

通常,在为调试而编译时,我们会()关掉编译器的优化选项(-O), 并打开调 试选项(-g)。另外,-Wall在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。

gcc -g -Wall program.c -o program

GDB命令-启动、退出、查看代码

image-20230411101555186

GDB命令-断点操作

image-20230411103646076

GDB命令-调试命令

image-20230411104852362

总结:

gcc -g -Wall program.c -o program

gdb 可执行程序

quit

set args 10 20

show args

help

l

l 行号

l 函数名

l 文件名:行号

l 文件名:函数名

show list

set list 行数

b 行号/函数

b 文件名:行号/函数

i b

d 断点编号

dis 断点编号

ena 断点编号

b 行号 if i==5

start

run

c

n

s

finish

p 变量名

ptype 变量名

display 变量名

i display

undisplay 编号

set var 变量名=变量值

until

1.17 标准C库IO函数和Linux系统IO函数对比

站在内存角度看输入输出,考虑IO,输入为从文件里面读取内容到内存里面,输出为从内存里面把数据写到文件里面

二者的关系为调用和被调用的关系

image-20230411121251000

1.18 虚拟地址空间

image-20230411121225869

1.19 文件描述符

PCB 进程控制块,文件描述符表默认有1024个大小

image-20230412200713062

1.20 open

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    //打开一个已经存在的文件
    int open(const char *pathname, int flags);
    //创建一个新的文件
    int open(const char *pathname, int flags, mode_t mode);
        参数:
            pathname:要打开的文件路径
            flags:对文件的操作权限设置还有其他设置
                必选项:O_RDONLY,  O_WRONLY,  or O_RDWR这三个设置是互斥的
                可选项:O_CREAT 文件不存在,创建新文件
            mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
            最终的权限是:mode & ~umask
            0777    ->  111111111
        &   0775    ->  111111101
        -------------------------
                        111111101
        umask的作用就是抹去某些权限

        flags是一个int类型数据,占4个字节,32位
        flags 32个位,每一位就是一个标志位

        返回值:返回一个新的文件描述符,如失败,返回-1

    error:属于linux系统函数库,库里面的一个全局变量,记录的是最近的错误号

    #include <stdio.h>
    void perror(const char *s);作用:打印error对应的错误描述
        s参数:用户描述,比如hello,最终输出内容:hello:xxx(实际错误描述)
    
    
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(){

    //打开一个文件
    int fd = open("a.txt",O_RDONLY);

    if(fd == -1){
        perror("open");
    }
    //读写操作


    //关闭
    close(fd);

    return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(){

    //创建一个文件
    int fd = open("create.txt",O_RDWR|O_CREAT,0777);

    if(fd == -1){
        perror("create");
    }
    //读写操作


    //关闭
    close(fd);

    return 0;
}

1.22 read、write函数

/*
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    参数:
        -fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
        -buf:要读取数据存放的地方,数组的地址(传出参数)
        -count:指定的数组的大小
    返回值:
        -成功
            >0:返回实际的读取到的字节数
            =0:文件已经读完了
        -失败:-1,并且设置errono

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
    参数:
        -fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
        -buf:要往磁盘写入的数据
        -count:要写的数据的实际的大小
    返回值:
        -成功:实际的写入的字节数
        -失败:-1,并且设置errono
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(){
    //1.通过open打开a.txt文件
    int srcfd = open("a.txt",O_RDONLY);
    if(srcfd == -1){
        perror("open");
        return -1;
    }

    //2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt",O_WRONLY|O_CREAT,0664);
    if(destfd == -1){
        perror("open");
        return -1;
    }

    //3.频繁的读写文件
    char buff[1024] = {0};
    int len = 0;
    while((len = read(srcfd,buff,sizeof(buff))) > 0){
        write(destfd,buff,len);
    }

    //4.关闭文件
    close(destfd);
    close(srcfd);

    return 0;
}

1.23 lseek函数

/*
    //标准c库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    //Linux系统函数
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
        参数:
            -fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            -offset:偏移量
            -whence:
                SEEK_SET
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置+第二个参数offset的值
                SEEK_END
                    设置偏移量:文件大小+第二个参数offset的值
        返回值:返回文件指针的位置

    作用:
        1.移动文件指针到文件头
        lseek(fd,0,SEEK_SET);

        2.获取当前文件指针的位置
        lseek(fd,0,SEEK_CUR);

        3.获取文件长度
        lseek(fd,0,SEEK_END);

        4.拓展文件的长度,当前文件10b,110b,增加100个字节
        lseek(fd,100,SEEK_END);
        注意:需要写一次数据才可以
        (举例子:在下载文件时候,先划好一个空间,再在这个空间里面下载)
*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(){
    int fd = open("hello.txt",O_RDWR);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //扩展
    int ret = lseek(fd,100,SEEK_END);
    if(ret == -1){
        perror("lseek");
        return -1;
    }
    //写入空数据
    write(fd," ",1);
    close(fd);


    return 0;
}

1.24 stat、lstat函数

image-20230414112903783

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>

    int stat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的一些信息
        参数:
            -pathname:操作的文件的路径
            -statbuf:结构体变量,传出参数,用于保存获取到的文件信息
        返回值:
            成功:返回0
            失败:返回-1,设置errono
    int lstat(const char *pathname, struct stat *statbuf);
        作用:获取软链接文件的信息,而不是软链接指向的文件的信息
        参数:
            -pathname:操作的文件的路径
            -statbuf:结构体变量,传出参数,用于保存获取到的文件信息
        返回值:
            成功:返回0
            失败:返回-1,设置errono

*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(){

    struct stat statbuf;

    int ret = stat("a.txt",&statbuf);

    if(ret == -1){
        perror("stat");
    }

    printf("size: %ld\n",statbuf.st_size);



    return 0;
}

1.25 模拟实现 ls -l命令

image-20230414112922513

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>
//模拟实现ls -l指令
//-rw-rw-r-- 1 chaoyu chaoyu 12 4月  14 10:57 a.txt

int main(int argc,char* argv[]){

    //判断输入的参数是否正确
    if(argc<2){
        printf("%s filename\n",argv[0]);
        return -1;
    }
    //通过stat函数获取用户传入的文件的信息
    struct stat st;
    int ret = stat(argv[1],&st);
    if(ret == -1){
        perror("stat");
        return -1;
    }
    //获取文件类型和文件权限
    char perms[11] = {0};//用于保存文件类型和文件权限的字符串

    switch (st.st_mode & S_IFMT)
    {
    case S_IFLNK:
        perms[0] = 'l';
        break;
    case S_IFDIR:
        perms[0] = 'd';
        break;
    case S_IFREG:
        perms[0] = '-';
        break;
    case S_IFBLK:
        perms[0] = 'b';
        break;
    case S_IFCHR:
        perms[0] = 'c';
        break;
    case S_IFIFO:
        perms[0] = 'p';
        break;
    default:
        perms[0] = '?';
        break;
    }

    //判断文件的访问权限
    //文件所有者
    perms[1] = (st.st_mode & S_IRUSR)  ? 'r':'-';
    perms[2] = (st.st_mode & S_IWUSR)  ? 'w':'-';
    perms[3] = (st.st_mode & S_IXUSR)  ? 'x':'-';
    //文件所在组
    perms[4] = (st.st_mode & S_IRGRP)  ? 'r':'-';
    perms[5] = (st.st_mode & S_IWGRP)  ? 'w':'-';
    perms[6] = (st.st_mode & S_IXGRP)  ? 'x':'-';
    //其他人
    perms[7] = (st.st_mode & S_IROTH)  ? 'r':'-';
    perms[8] = (st.st_mode & S_IWOTH)  ? 'w':'-';
    perms[9] = (st.st_mode & S_IXOTH)  ? 'x':'-';

    //硬连接数
    int linkNum = st.st_nlink;

    //文件所有者
    char* fileUser = getpwuid(st.st_uid)->pw_name;

    //文件所在组
    char* fileGrp = getgrgid(st.st_uid)->gr_name;

    //文件大小
    long int fileSize = st.st_size;

    //获取修改时间
    char* time = ctime(&st.st_mtime);

    char mtime[512] = {0};
    strncpy(mtime,time,strlen(time)-1);

    char buf[1024];
    sprintf(buf,"%s %d %s %s %ld %s %s",perms,linkNum,fileUser,fileGrp,fileSize,mtime,argv[1]);

    printf("%s\n",buf);

    return 0;
}

1.26 文件属性操作函数

/*
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否具有某个权限,或者是否存在
        参数:
            pathname:文件名称
            mode:F_OK存在,  or R_OK读, W_OK写, and X_OK可执行
        返回值:0成功 -1失败

*/
/*
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        作用:修改文件权限
        参数:
            pathname:文件名称
            mode:需要修改的权限值,八进制的数
        返回值:0成功 -1失败

    #include <unistd.h>
    int chown(const char *pathname, uid_t owner, gid_t group);
        作用:修改文件所有者或所在组

*/
/*
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸
        参数:
            pathname:文件路径
            length:需要最终文件变成的大小
        返回值:0成功 -1失败

*/

1.27 目录操作函数

int mkdir(const char *pathname, mode_t mode);//创建新目录
int rmdir(const char *pathname);//删除空目录
int rename(const char *oldpath, const char *newpath);//重命名目录
int chdir(const char *path);//修改进程的工作目录,默认是当前目录下的进程
char *getcwd(char *buf, size_t size);//获取当前工作目录

1.28 目录遍历函数

DIR *opendir(const char *name);//打开一个目录;DIR *类型理解为目录流
struct dirent *readdir(DIR *dirp);//读取目录中的数据
int closedir(DIR *dirp);//关闭目录流

image-20230415165343535

/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        - 参数:dirp是opendir返回的结果
        - 返回值:
            struct dirent,代表读取到的文件的信息
            读取到了末尾或者失败了,返回NULL

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);

*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);

// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {

    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    int num = getFileNum(argv[1]);

    printf("普通文件的个数为:%d\n", num);

    return 0;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {

    // 1.打开目录
    DIR * dir = opendir(path);

    if(dir == NULL) {
        perror("opendir");
        exit(0);
    }

    struct dirent *ptr;

    // 记录普通文件的个数
    int total = 0;

    while((ptr = readdir(dir)) != NULL) {

        // 获取名称
        char * dname = ptr->d_name;

        // 忽略掉. 和..
        if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
            continue;
        }

        // 判断是否是普通文件还是目录
        if(ptr->d_type == DT_DIR) {
            // 目录,需要继续读取这个目录
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);
        }

        if(ptr->d_type == DT_REG) {
            // 普通文件
            total++;
        }


    }

    // 关闭目录
    closedir(dir);

    return total;
}

1.29 dup、dup2函数

int dup(int oldfd);//复制文件描述符
int dup2(int oldfd, int newfd);//重定向文件描述符
/*
    #include <unistd.h>
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        oldfd 指向 a.txt, newfd 指向 b.txt
        调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd和newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) {
        perror("open");
        return -1;
    }

    printf("fd : %d, fd1 : %d\n", fd, fd1);

    int fd2 = dup2(fd, fd1);
    if(fd2 == -1) {
        perror("dup2");
        return -1;
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char * str = "hello, dup2";
    int len = write(fd1, str, strlen(str));

    if(len == -1) {
        perror("write");
        return -1;
    }

    printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);

    return 0;
}

1.30 fcntl函数

 int fcntl(int fd, int cmd, ... /* arg */ );//复制文件描述符;设置/获取文件的状态标志
/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O)NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值