Linux系统编程(2)

news2024/10/7 8:26:17

手动文件锁定

#include <stdio.h>
void flockfile(FILE* stream);

void funlockfile(FILE* stream);

//非阻塞函数
int ftrylockfile(FILE* stream);

不会锁定流的操作

#define _GNU_SOURCE
#include <stdio.h>

int fgetc_unlocked(FILE* stream);
char *fgets_unlocked(char*str, int size, FILE* stream);
size_t fread_unlocked(void* buf, size_t size, size_t nr, FILE* stream);
int fputc_unlocked(int c, FILE* stream);
int fputs_unlocked(const char* str, FILE* stream);
size_t fwrite_unlocked(void * buf, size_t size, size_t nr, FILE* stream);

int fflush_unlocked(FILE* stream);
int feof_unlocked(FILE* stream);
int ferror_unlocked(FILE* stream);
int fileno_unlocked(FILE* stream);
void clearerr_unlocked(FILE* stream);

标准IO最大的问题在于性能受到两次复制的影响,读取数据时,标准IO会对内核进行read()系统调用,从内核复制数据到标准IO缓冲区,如果有一个应用程序接着通过标准IO送出一个读取请求,则数据会被再复制一次,会从标准IO缓冲区复制到所提供的缓冲区中,写入请求则逆向运作,数据从提供的缓冲区被复制到标准IO缓冲区,然后通过write()系统调用从标准IO缓冲区被复制到内核。

readv()和writev()

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int count);
ssize_t writev(int fd, const struct iovec* iov, int count);

struct iovec {
    void* iov_base;
    size_t iov_len;
};

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
    struct iovec iov[3];
    ssize_t nr;
    int fd, i;

    char* buf[] = {"The term buccaneer comes from the word boucan.\n", 
                   "A boucan is a wooden frame used for cooking mear.\n",
                   "Buccaneer is the West Indies name for a pirate.\n"};

    fd = open("buccaneer.txt", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 填三个iovec结果
    for (int i = 0; i < 3; i++) {
        iov[i].iov_base = buf[i];
        iov[i].iov_len = strlen(buf[i]);
    }

    // 单词调用将他们写出
    nr = writev(fd, iov, 3);
    if (nr == -1) {
        perror("writev");
        return 1;
    }
    printf("wrote %ld bytes\n", nr);

    if (close(fd)) {
        perror("close");
        return 1;
    }
    return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <string.h>

int main() {
    // 下面的长度如果变化,结果会有不同的
    // char foo[47], bar[50], baz[48];
    // char foo[47], bar[51], baz[48];
    char foo[47], bar[50], baz[47];
    struct iovec iov[3];
    ssize_t nr;
    int fd, i;
    fd = open("buccaneer.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    iov[0].iov_base = foo;
    iov[0].iov_len = sizeof(foo);
    iov[1].iov_base = bar;
    iov[1].iov_len = sizeof(bar);
    iov[2].iov_base = baz;
    iov[2].iov_len = sizeof(baz);

    // 单次调用读取
    nr = readv(fd, iov, 3);
    if (nr == -1) {
        perror("readv");
        return 1;
    }

    for (i = 0; i < 3; i++) {
        printf("%d-%ld: %s", i, strlen((char*)iov[i].iov_base), (char*)iov[i].iov_base);
    }
    if (close(fd)) {
        perror("close");
        return 1;
    }
    return 0;

}

获取页大小

long page_size = sysconf(_SC_PAGESIZE);

或者

#include <unistd.h>
int getpagesize(void);

页面大小也被静态存储在PAGE_SIZE(定义于<asm/page.h>)中,因此获取页面大小的第三种方式为int page_size = PAGE_SIZE;

 mmap函数的用法详解及实例分析-CSDN博客

 Linux C | mmap使用实例_mmap使用示例-CSDN博客

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>


int main() {
    int fd;
    void *mmap_addr;
    struct stat sb;

    fd = open("buccaneer.txt", O_RDONLY);
    fstat(fd, &sb);
    mmap_addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (mmap_addr == MAP_FAILED) {
        return -1;
    }
    printf("%s\n", (char*)mmap_addr);
    munmap(mmap_addr, sb.st_size);
    close(fd);
    return 0;
}

mmap优点

相比较于标准的read()和write()系统调用,经过mmap映射有以下优点:

1、对内存映射文件进行读取和写入操作,可避免使用read和write系统调用时所产生的无关副本,read和write会将数据复制到一个用户空间缓冲区,并从用户缓冲区复制回来。

2、除了可能的页面失误,对内存映射文件的读写操作不会产生任何系统调用或操作环境切换的开销,进行简单的内存操作即可。

3、当有多个进程将同一个对象映射至内存时,数据由这些进程共享,对类型为read-only以及shared的可写入映射而言,所共享的是他们的全部,对类型为private的可写入映射而言,所共享的是他们尚未被“写入时复制”的页面。

4、映射的查找仅涉及很少的指针操作,不需要使用lseek系统调用。

mmap缺点

1、内存映射往往是页面大小的整数倍,可能会造成内存浪费。

2、内存映射必须适合放入进程的地址空间。

3、创建和维护内存映射以及内核内部相关的数据结构是有代价的。

调整映射的大小

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>

void* mremap(void* addr, size_t old_size, size_t new_size, unsigned long flags);

 Linux所特有的mremap系统调用可以用于扩大或者缩小所指定的映射的大小。将位于[addr, addr+old_size)中的内存区间扩大或者缩小为new_size,可能会发生地址移动。

输出特定文件的iNode编号

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int get_inode(int fd) {
    struct stat buf;
    int ret;

    ret = fstat(fd, &buf);
    if (ret < 0){
        perror("fstat");
        return -1;
    }
    return buf.st_ino;
}

int main(int argc, char* argv[]) {
    int fd, inode;
    if (argc < 2) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        return 1;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    inode = get_inode(fd);
    printf("%d\n", inode);
    return 0;
}

获取文件的逻辑块

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

/*
*fd相关的文件,映射至logical_block物理块
*/
int get_block(int fd, int logical_block) {
    int ret;
    ret = ioctl(fd, FIBMAP, &logical_block);
    if (ret < 0) {
        perror("ioctl");
        return -1;
    }
    return logical_block;
}

/*
* get_nr_blocks   返回与fd相关联的文件所耗用的逻辑块的数目
*/
int get_nr_blocks(int fd) {
    struct stat buf;
    int ret;

    ret = fstat(fd, &buf);
    if (ret < 0) {
        perror("fstat");
        return -1;
    }

    return buf.st_blocks;
}

/**
 * @brief 输出(logical blocks, physical block)
 * 
 */
void print_blocks(int fd) {
    int nr_blocks, i;
    nr_blocks = get_nr_blocks(fd);
    if (nr_blocks < 0) {
        fprintf(stderr, "get_nr_blocks failed!\n");
        return;
    }

    if (nr_blocks == 0) {
        printf("no allocated blocks\n");
        return;
    } else if (nr_blocks == 1) {
        printf("1 bllock\n\n");
    } else {
        printf("%d blocks\n\n", nr_blocks);
    }

    for (i = 0; i < nr_blocks; i++) {
        int phys_block;
        phys_block = get_block(fd, i);
        if (phys_block < 0) {
            fprintf(stderr, "get_block failed!\n");
            return;
        }
        if (!phys_block) {
            continue;
        }
        printf("(%u, %u) ", i, phys_block);
    }
    putchar('\n');

}

int main(int argc, char* argv[]) {
    int fd;
    if(argc < 2) {
        fprintf(stderr, "usage: %s<file>\n", argv[0]);
        return 1;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    print_blocks(fd);
    return 0;
}

获取进程号和父进程号

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

pid_t getpid(void);
pid_t getppid(void);

exec系列调用

#include <unistd.h>

int execl(const char* path, const char* arg, ...);
//不定参数必须以NULL结尾
//下面执行会将/bin/vi取代当前所执行的程序

int ret;
ret = execl("/bin/vi", "vi", NULL);
if (ret == -1) 
    perror("execl");
#include <unistd.h>

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 execve(const char* filename, char* const argv[], char* const envp[]);

使用wait检测子进程的状态

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main() {
    int status;
    pid_t pid;

    if (!fork()) {
        // 子进程
        // return 1;
        abort();
    }
    pid = wait(&status);
    if (pid == -1) 
        perror("wait");
    printf("pid=%d\n", pid);
    
    if (WIFEXITED(status)) {
        printf("Normal termination with exit status=%d\n", WEXITSTATUS(status));
    }

    if (WIFSIGNALED(status)) {
        printf("Killed by signlal=%d%s\n", WTERMSIG(status), WCOREDUMP(status) ? " (dumped core)" : "");
    }

    if (WIFSTOPPED(status)) {
        printf("Stopped by signal=%d\n", WSTOPSIG(status));
    }

    if (WIFCONTINUED(status)) {
        printf("Continued\n");
    }
    return 0;
}

等待特定进程waitpid

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

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

/**
 * @brief pid参数说明
 * <-1  等待其进程组ID等于此值的绝对值的任何子进程,例如传入-500, 则等待进程组ID为500的任何进程
 * -1   等待任何子进程,此刻的行为如同wait()
 * 0    等待与calling process (进行调用的进程)均属于同一个进程组的任何子进程
 * >0   等待其pid等于所指定值的任何子进程,例如传入500,则表示等待进程号为500的子进程
 * status参数的行为如同wait的参数,可以使用前面的宏来操作它
 * options参数可以是零个或者多个以下选项的OR逻辑运算
 * WNOHANG 非阻塞,如果没有符合的子进程终止,则立即放回
 * WUNTRACED  
 *      如果设定了,则设定WIFSTOPPED,及时calling process 并未追踪子进程
 * WCONTINUED
 *      如果设定了,则设定WIFCONTINUED,即使calling process并为追踪子进程,如同WUNTRACED
 */


int main() {
    pid_t pid;
    int status;

    pid = waitpid(2448, &status, WNOHANG);
    if (pid == -1) {
        perror("waitpid");
    } else {
        printf("pid=%d\n", pid);
        if (WIFEXITED(status)) {
            printf("Normal termination with exit status=%d\n", WEXITSTATUS(status));
        }
        if (WIFSIGNALED(status)) {
            printf("killed by signal=%d%s\n", WTERMSIG(status), WCOREDUMP(status) ? " (dump core)" : "");
        }
    }
    return 0;
}

利用fork(),exec,和waitpid来实现

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int my_system(const char* cmd) {
    int status;
    pid_t pid;

    pid = fork();
    if (pid == -1) {
        perrorr("fork");
        return -1;
    }

    if (pid == 0) {
        const char* argv[4];
        argv[0] = "sh";
        argv[1] = "-c";
        argv[2] = cmd;
        argv[3] = NULL;
        execv("/bin/sh", argv);
        exit(-1);
    }
    if (waitpid(pid, &status, 0) == -1) {
        return -1;
    } else if (WIFEXITED(status)) {
        return WEXITSTATUS(status);
    }
    retrun 1;
}

获取和修改资源限制,setrlimit,getrlimit

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

/**
 * @brief 
 * struct rlimit {
 *      rlim_t rlim_cur;   //soft limit
 *      rlim_t rlim_max;   //hard limit
 * };
 * 
 * int getrlimit(int resource, struct rlimit * rlim);
 * int setrlimit(int resource, const struct rlimit* rlim);
 * @return int 
 */

int main() {
    struct rlimit rlim;
    int ret;
    // 取得core文件大小的限制
    ret = getrlimit(RLIMIT_CORE, &rlim);
    if (ret == -1) {
        perror("getrlimit");
        return 1;
    }
    printf("before : RLIMIT_CORE limits: soft=%ld hard=%ld\n", rlim.rlim_cur, rlim.rlim_max);
    // 产生如下输出
    // RLIMIT_CORE limits: soft=0 hard=-1  -1表示无穷大

    rlim.rlim_cur = 32*1024*1024;  //32MB
    rlim.rlim_max = RLIM_INFINITY;
    ret = setrlimit(RLIMIT_CORE, &rlim);
    if (ret == -1) {
        perror("setrlimit");
        return 1;
    }
    printf("setrlimit success!\n");

    ret = getrlimit(RLIMIT_CORE, &rlim);
    if (ret == -1) {
        perror("getrlimit");
        return 1;
    }
    printf("after: RLIMIT_CORE limits: soft=%ld hard=%ld\n", rlim.rlim_cur, rlim.rlim_max);
}

stat函数,获取文件元数据

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

int stat(const char* path, struct stat* buf);
int fstat(int fd, struct stat* buf);
int lstat(const char* path, struct stat* buf);

 

 

 使用stat获取指定文件的大小

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

int main(int argc, char* argv[]) {
    struct stat sb;
    int ret;
    if (argc < 2) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        return 1;
    }

    ret = stat(argv[1], &sb);
    if (ret == -1) {
        perror("stat error");
        return 1;
    }

    printf("%s is %ld bytes\n", argv[1], sb.st_size);

    return 0;
}

下面这段代码会使用fstat检查一个已经打开的文件是否位于一个物理设备上

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/sysmacros.h>

/**
 * @brief 如果fd位于一个物理设备上,则返回一个正整数,如果位于一个非物理或者虚拟设备上
 * 则返回0,发生错误,返回-1
 * 
 * @param fd 
 * @return int 
 */
int is_on_physical_device(int fd) {
    struct stat sb;
    int ret;

    ret = fstat(fd, &sb);
    if (ret == -1) {
        perror("fstat error");
        return -1;
    }
    return gnu_dev_major(sb.st_dev);
}

int main(int argc, char* argv[]) {
    struct stat sb;
    int ret;
    if (argc < 2) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        return 1;
    }

    ret = stat(argv[1], &sb);
    if (ret == -1) {
        perror("stat error");
        return 1;
    }

    printf("%s is %ld bytes\n", argv[1], sb.st_size);

    int fd = open("buccaneer.txt", O_RDONLY);
    ret = is_on_physical_device(fd);
    if (ret == -1) {
        perror("is_on_physical_device faile\n");
        return -1;
    }
    if (ret == 0) {
        printf("buccaneer.txt is not on physical device!\n");
    } else {
        printf("buccaneer.txt is on physical device!\n");
    }
    return 0;
}

输出:
buccaneer.txt is 149 bytes
buccaneer.txt is on physical device!

 获取文件使用权限

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

int chmod(const char* path, mode_t mode);
int fchmod(int fd, modt_t mode);

将fd文件拥有者和组设定为root

int make_root_owner(int fd) {
    int ret;
    // root的gid与uid皆为0
    ret = fchown(fd, 0, 0);
    if (ret)
        perror("fchown");
    return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1076243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言利用计算机找系统的最小通路集的算法

背景&#xff1a; 有人求助到博主希望分析一下他们老师给出的题目&#xff0c;博主思路分析和解题过程如下 题目要求&#xff1a; 联络矩阵法&#xff0c;当 n 较小时可以用手算,当然也可以用计算机计算。但当 n 很大时&#xff0c;需要计 算机的容量很大才行。为此要探求有…

网络机顶盒哪个好?达人分享最新网络电视机顶盒排名TOP5

看视频、网游戏、上网课等等功能网络机顶盒都能实现&#xff0c;可以说是我们使用频率最高的了&#xff0c;尤其是对老人小孩来说。我每年都会进行上百次测评&#xff0c;网络机顶盒就是其中品类之一&#xff0c;很多朋友都在私信我不知道网络机顶盒哪个好&#xff0c;跟着我一…

京东运营数据分析:2023年8月京东饮料行业品牌销售排行榜

鲸参谋监测的京东平台8月份饮料市场销售数据已出炉&#xff01; 8月份&#xff0c;饮料市场整体销售下滑。根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年8月&#xff0c;京东平台饮料市场的总销量将近820万&#xff0c;环比下滑约8%&#xff0c;同比下滑约20%&am…

高精度电流源的应用领域有哪些

高精度电流源是一种能够提供稳定、准确、可控的电流输出的仪器设备&#xff0c;广泛应用于多个领域。以下是一些高精度电流源的应用领域。 科学研究&#xff1a;在物理学、化学、材料科学等领域中&#xff0c;需要进行精确的电流实验和测试。高精度电流源可以提供稳定的电流输出…

方法在template内用v-if调用,在main.js内引入并挂载全局

utils内index.js内的isAuth方法 main.js内引入isAuth&#xff0c;并全局注册 vue页面在template内用v-if调用isAuth方法

linux 给根目录扩容(lvm CentOS 7.6 kylinx86)

问题:Linux系统挂载到根目录的磁盘空间满了,如何扩容? 用命令:lsblk 可以查看磁盘和分区情况,可以发现磁盘vda下面的还有大部分空间没有使用。 操作步骤 1、使用 fdisk -l 查看硬盘序号,并用 fdisk 对硬盘操作,格式化成lvm的格式 (用命令lsblk可以看到,挂载到根目录…

算法题:K 次取反后最大化的数组和(典型的贪心算法问题)

这道题没有看题解&#xff0c;直接提交&#xff0c;成绩超越99.5%&#xff0c;说明思路是优的。就是考虑的情况里面弯弯绕比较多&#xff0c;需要考虑全面一点。&#xff08;本题完整题目附在了最后面&#xff09; 具体思路如下&#xff1a; 1、首先排序&#xff0c;然后从最…

如何在 Spring Boot 中进行文件上传

在 Spring Boot 中进行文件上传 文件上传是Web应用程序中常见的功能之一&#xff0c;它允许用户将文件从客户端上传到服务器。Spring Boot提供了便捷的方式来处理文件上传&#xff0c;并且整合了Spring框架的强大功能&#xff0c;使文件上传变得相对简单。本文将介绍如何在Spr…

深度学习DAY2:n-gram

什么是LM(language model语言模型)&#xff1f; 引例&#xff1a; 1、统计机器学习时期的语言模型–语音识别 2、贝叶斯公式求P(s|A)——在有了语音信号的前提下是文本的概率 1 n-gram模型概述 n-gram模型是一种统计语言模型&#xff0c;用于建模文本数据中的语言结构。…

释放Sqlite数据库占用的多余空间

当删除sqlite数据库中的数据之后&#xff0c;会发现内容确实删除掉了&#xff0c;但是sqlite数据库占用的磁盘大小没有缩小&#xff0c;那是因为&#xff0c;删除数据后&#xff0c;未使用的磁盘空间被添加到一个内在的“空闲列表”中用于储存你下次插入的数据&#xff0c;磁盘…

使用yum 安装mysql数据库

h这是参照msyql官方文档写的&#xff08;网上的教程五花八门&#xff0c;各有各的特色和技巧&#xff0c;不过还是以官方文档为主要参考最靠谱&#xff09; 注意 : 适用于第一次安装的情况&#xff0c;系统上已经有之前安装过的版本的话&#xff0c;官方文档同一章也有升级、替…

【广州华锐互动】AR轨道交通综合教学平台的应用

轨道交通是一种复杂且精密的系统&#xff0c;涵盖了众多技术和工程学科&#xff0c;包括机械、电气和计算机科学等。对于学生来说&#xff0c;理解和掌握这些知识是一项挑战。然而&#xff0c;AR技术的出现为解决这一问题提供了可能。 通过AR技术&#xff0c;教师可以创建生动、…

Linux ❀ 磁盘IO较大故障告警排查确认方法

文章目录 1、iotop2、iostat3、磁盘压力测试 问题描述&#xff1a;在日常运维工作中&#xff0c;经常会遇到Linux服务器出现Disk磁盘I/O&#xff08; I/O 英文全称是 Input/Output&#xff0c;中文译为 输入与输出&#xff0c;通常指存储器与其他设备之间的数据交换操作&#x…

sip网络话筒主机SIP桌面式对讲广播主机

sip网络话筒主机SIP桌面式对讲广播主机 SV-8003VP是我司的一款SIP桌面式对讲主机&#xff0c;具有10/100M以太网接口&#xff0c;配置了麦克风输入和扬声器输出&#xff0c;还配置多达22个按键和2.8英寸液晶显示屏&#xff0c;可以配合SIP服务器使用。SV-8003VP网路寻呼话筒可以…

SAP SD定价过程 含税未税 最大区别

SAP SD定价过程 含税&未税 最大区别 最大区别应该为&#xff0c;税条件类型&#xff1a; 含税一般用 MWSI 未税一般用 MWST

HarmonyOS/OpenHarmony原生应用开发-华为Serverless认证服务说明(二)

一、支持HarmonyOS(Stage模型-API9)应用的账户注册登录方式 文档中的TS作者认为就是ArkTS之意。暂时支持四种模式&#xff0c;手机、邮箱、匿名、自有账户。 二、暂时不支持HarmonyOS(Stage模型-API9)应用的账户注册登录方式 包括华为账户注册登录&#xff0c;HarmonyOS…

使用Fillder的一点总结

文章目录 最近使用Fillder的一点总结一、前言二、关于FildderScript的吐槽三、关于filters四、关于快捷方式的吐槽五、关于断点 最近使用Fillder的一点总结 纯乱搞&#xff0c;但是不得不说Fillder确实厉害 一、前言 安装请在官网下载 简单食用方法可参阅&#xff1a;https…

C#实现OPC DA转OPC UA服务器

运行软件前提前安装好OPC运行组件&#xff1a; 为方便演示&#xff0c;提前准备好了一个DAServer服务器&#xff1a; 接下来开始配置&#xff1a; 该软件主要实现的功能如下&#xff1a; 配置过程也相对简单&#xff1a; 第一步&#xff1a; 编辑如下文件&#xff1a; 第二步…

git介绍和安装、(git,github,gitlab,gitee介绍)、git工作流程、git常用命令、git忽略文件

1 git介绍和安装 2 git&#xff0c;github&#xff0c;gitlab&#xff0c;gitee介绍 3 git工作流程 4 git常用命令 5 git忽略文件 1 git介绍和安装 首页功能写完了---》正常应该提交到版本仓库---》大家都能看到这个---》 运维应该把现在这个项目部署到测试环境中---》测试…

最新抖音去水印PHP源码 非第三方接口

简介&#xff1a; 最新抖音去水印PHP源码 非第三方接口 源码全开源 视频解析接口来自官方抖音视频接口!非第三方接口!上传PHP环境中即可运行!支持上传二级目录访问! 访问你的域名地址/douyin.php douyin.php(此文件可以自行重新命名) 支持带有文本的链接和视频ID或者分享的…