当前位置: 首页 > news >正文

系统+网络练习题代码汇总

练习题

本节所有代码均为本人手写, 不出意外将全部可以正确执
建议读者学习完基本课程后, 最后统一复习用,建议读者先根据题目自己写, 有问题自己先调试

文章目录

  • 练习题
    • 错误号返回值考虑
      • EAGIN: 非阻塞状态下, 操作暂时无法完成, 会自动重试
      • EINTR: 系统调用被信号中断。处理方式通常是重新调用
      • mutex增加一个: EBUSY, 在互斥锁中, 表示锁已经被其他进程或线程持有,当前无法获取。
    • 目录操作
      • 实现递归遍历目录
      • ls-R.c
      • ls-R2.c
    • 循环创建n个子进程并使用wait回收
      • 虽然wait有点复杂,但是wait(NULL)更方便,不关心子进程退出状态
    • 使用pipe管道 实现简单的进程间通讯
      • 父子进程间通信 ls | wc -l
      • 兄弟进程间通信
      • loop-fork.c
    • 写一个简单的 fifo管道
      • 实现了 非血缘关系, fifo通讯
      • 这里自己写, 发现了 细节
      • pipe和fifo读写行为, 详看快速复习,有区别
      • 1-pipe.c
      • 2-fifo.c
      • 3-fifo.c
    • 共享内存映射-复杂
      • 1. 实现 无血缘关系 进程通信
      • 2. 实现 父子进程 普通映射通讯
      • 3. 实现 父子进程 匿名映射通讯
      • 1-mmap.c
      • 2-mmap.c
      • 3-mmap.c
      • 4-mmap.c
    • 简单的信号集操作函数例子
      • 将SIGINT,SIGQUIT加入信号集并屏蔽, 并循环打印 未决信号集
      • signal.c
    • 信号捕捉例子
      • 捕捉 SIGINT,并回调打印
      • 注意 测试sa_mask的作用
      • 1-sigaction.c
    • SIGCHILD信号回收子进程
      • sigchild.c
    • 手写一个简单的 守护进程
      • 使用子进程 创建新会话,然后创建守护进程
      • 1-setsid.c
    • 循环创建n个子线程
      • 并 循环回收---难点
      • 1-loop-pthread.c
      • 2-pthread-join-error.c
      • 3-pthread-join-true.c
    • 使用 pthread_cancel函数取消线程
      • 使用默认系统调用 作为取消点
      • 使用 指定的pthread_testcancel()作为取消点
      • pthread-cancel.c
    • 设置 线程分离, 并 jion回收, 查看出错
      • pthread-detach.c
    • 使用线程 属性设置 分离态
      • pthread-attr.c
    • 简单的 互斥锁(4个)
      • 不使用锁的效果
      • 正常加锁解锁
      • tyrlock 替换进去看效果
      • 营造死锁的 问题
      • 1-mutex-no-lock.c
      • 2-mutex-lock.c
      • 3-mutex-trylock.c
      • 4-mutex-si.c
    • 条件变量---重点
      • 生产者和消费者模型
      • producer-customer.c
    • 信号量
      • 实现这个的 生产者和消费者模型, 有点不同
      • 只使用 信号量的 例子,
      • 理解为什么 可以 只使用 信号量
      • sem-pro-cus.c
    • TCP/IP协议模型--练习题
      • server.c
      • client.c
      • 1-socket-server.c
      • 2-socket-client.c
    • 多进程并发 and 多线程并发
      • 特别注意: 多进程并发时, 一定要 对accept 进行判断, 都则可能会 返回-1,还在执行, 会一直打印 read error
      • 特别注意: 端口号, 一定要找 没有被占用的, 否则 也会 一直打印 read error
      • 1-more-fork-socket.c
      • 4-more-pthread-socket.c
    • epoll 服务-客户练习
      • 做 多客户端链接的 服务端, 注意 epoll函数的使用
      • 1-epoll-server.c
      • 2-epoll-client.c

错误号返回值考虑

EAGIN: 非阻塞状态下, 操作暂时无法完成, 会自动重试

EINTR: 系统调用被信号中断。处理方式通常是重新调用

mutex增加一个: EBUSY, 在互斥锁中, 表示锁已经被其他进程或线程持有,当前无法获取。

目录操作

实现递归遍历目录

ls-R.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>

void Is_file(char *name);

void prr(char *str)
{
    perror(str);
    exit(1);
}


void Read_dir(char *dir)  
{
    char buf[256];
    DIR *dp;
    struct dirent *sdp;
    dp = opendir(dir);  // 打开目录
    if(dp == NULL)
    {
        prr("opendir error");
    }

    sdp = readdir(dp);  // 每读一次, 会向后移动

    if(sdp == NULL)
    {
        prr("readdir error");
    }

    while(sdp != NULL)
    {

        if(strcmp(sdp->d_name , ".") == 0 || strcmp(sdp->d_name , "..") == 0)   //忽略 .和..   字符串比较不能使用 ==, 是函数
        {
            sdp = readdir(dp);
            continue;
        }
        if((strlen(dir) + strlen(sdp->d_name)) +1 < 256)   // +1 表示 /
        {
            snprintf(buf, sizeof(buf), "%s/%s", dir, sdp->d_name);   // 这个函数 打印并拼接
            Is_file(buf);  // 注意, 必须传进去 正确的 相对路径或者 绝对路径,  否则 找不到路径
        }
        else
        {
            printf("dir too long: %s/%s", dir, sdp->d_name);
        }
        sdp = readdir(dp);
    }
    closedir(dp);
    
}

void Is_file(char *name) // 是文件, 打印路径和大小, 不是文件, 去递归
{
    struct stat stat_1;   // 获取name属性
    int ret = stat(name, &stat_1);
    if(ret < 0)
    {
        prr("stat error");
    }
    if(S_ISDIR(stat_1.st_mode))
    {
        Read_dir(name);
    }

    printf("%10s\t%ld\n", name, stat_1.st_size);

}



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

    if(argc==1)
    {
        Is_file(".");  // 无参时,默认显示 当前目录
    }
    else
    {
        Is_file(argv[1]);
    }
        
    return 0;
}


ls-R2.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>

// 修改使用 函数指针 进行调用


void Is_file(char *name);

void prr(char *str)
{
    perror(str);
    exit(1);
}


void Read_dir(char *dir, const void(*func)(char *)) 
{
    char buf[256];
    DIR *dp;
    struct dirent *sdp;
    dp = opendir(dir);  // 打开目录
    if(dp == NULL)
    {
        prr("opendir error");
    }

    sdp = readdir(dp);  // 每读一次, 会向后移动

    if(sdp == NULL)
    {
        prr("readdir error");
    }

    while(sdp != NULL)
    {

        if(strcmp(sdp->d_name , ".") == 0 || strcmp(sdp->d_name , "..") == 0)   //忽略 .和..   字符串比较不能使用 ==, 是函数
        {
            sdp = readdir(dp);
            continue;
        }
        if((strlen(dir) + strlen(sdp->d_name)) +1 < 256)   // +1 表示 /
        {
            snprintf(buf, sizeof(buf), "%s/%s", dir, sdp->d_name);   // 这个函数 打印并拼接
            Is_file(buf);  // 注意, 必须传进去 正确的 相对路径或者 绝对路径,  否则 找不到路径
        }
        else
        {
            printf("dir too long: %s/%s", dir, sdp->d_name);
        }
        sdp = readdir(dp);
    }
    closedir(dp);
    
}

void Is_file(char *name) // 是文件, 打印路径和大小, 不是文件, 去递归
{
    struct stat stat_1;   // 获取name属性
    int ret = stat(name, &stat_1);
    if(ret < 0)
    {
        prr("stat error");
    }
    if(S_ISDIR(stat_1.st_mode))
    {
        Read_dir(name, Is_file);
    }

    printf("%10s\t%ld\n", name, stat_1.st_size);

}



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

    if(argc==1)
    {
        Is_file(".");  // 无参时,默认显示 当前目录
    }
    else
    {
        Is_file(argv[1]);
    }
        
    return 0;
}


循环创建n个子进程并使用wait回收

虽然wait有点复杂,但是wait(NULL)更方便,不关心子进程退出状态

使用pipe管道 实现简单的进程间通讯

父子进程间通信 ls | wc -l

  • 父进程将ls的内容发到管道
    子进程用wc -l读取, wc本身带了读取功能
    本联系将会输出 行数

兄弟进程间通信

loop-fork.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{   
    pid_t pid, wpid;
    int status;
    int i = 1;
    while(i<=5)
    {
        pid=fork();
        if(pid < 0)
        {
            perror("fork error");
            exit(1);
        }
        else if(pid == 0)
        {
            break;
        }
        sleep(1);
        i++;
    }
    if(pid > 0)
    {
        printf("i am parent, pid is %d\n", getpid());
        i--;
        while(i > 0)
        {
            
            wpid = wait(&status);  // wait 应用
            if(wpid < 0)
            {
                perror("wait error");
                exit(1);
            }

        // status获取
            if(WIFEXITED(status))
            {
                printf("child %d exit with %d\n",i,  WEXITSTATUS(status));
            }

            if(WIFSIGNALED(status))
            {
               printf("child %d KILL with %d\n",i, WTERMSIG(status));
            }

           printf("parent wait child %d, wait pid is %d\n",i ,wpid);
           i--;
        }
    }
    else{
        printf("i am %d child, pid is %d, my parent pid is %d\n",i, getpid(), getppid());
        sleep(10);  //测试 回收每个子进程
    }
    
    return 0;
}


写一个简单的 fifo管道

实现了 非血缘关系, fifo通讯

这里自己写, 发现了 细节

  • 对于fifo文件, open时, 只读,只写,读写, 就表示了 是打开读端, 打开写端, 还是都打开,

这就相当于 pipe的 fd[0]和fd[1]!!!

  • 按理说, 当生产者只打开写端,消费者只打开读端,时

只打开生产者, 由于 两个读端都关闭, 因此会阻塞在write
当生产者以 RDWR打开,将会读写端都打开, 就不会产生阻塞了

pipe和fifo读写行为, 详看快速复习,有区别

1-pipe.c

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

int main(int argc, char *argv[])
{   
    int pipefd[2];
    pid_t pid;
    int ret = pipe(pipefd);
    if(ret<0)
    {
        perror("pipe error");
    }
    
    pid = fork();
    if(pid<0)
    {
        perror("fork error");
    }
    else if(pid>0)
    {
        close(pipefd[0]);  // 关闭读
        dup2(pipefd[1], STDOUT_FILENO);  // 重定向, ls后会直接进入 pipe
        execlp("ls","ls", "-l", NULL); // 父进程实现 ls功能, -l 会一行显示一个,并且加了total这一行    默认的, 是有换行符, 虽然一行多个
        perror("execlp pipefd[1] error");
        close(pipefd[1]);
        wait(NULL);
    }
    else if(pid==0)
    {
        close(pipefd[1]); //关闭写
        dup2(pipefd[0], STDIN_FILENO); // 重定向, 将读出来的传到标准输入
        execlp("wc", "wc", "-l", NULL);  // wc 实际上是从管道中读取数据。也就 不需要显示调用 读了
        perror("execlp pipefd[0] error");
        close(pipefd[0]);
    }

    return 0;
}


2-fifo.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{   
    int fd;
    // fd = mkfifo("fifoipc.txt")   // 错误的, mkfifo 成功0, 失败 error
    int ret = mkfifo("myfifo", 0664);
    if(ret < 0 && errno!=EEXIST)
    {
        perror("mkfifo error");
        exit(1);
    }
    
    fd = open("myfifo", O_WRONLY);
    if(fd < 0)
    {
        perror("mkfifo error");
        exit(1);
    }
    write(fd, "hello-1!",strlen("hello-1!"));
    close(fd);


    return 0;
}


3-fifo.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{   
    int fd;
    char buf[1024];
    // fd = mkfifo("fifoipc.txt")   // 错误的, mkfifo 成功0, 失败 error
    // int ret = mkfifo("myfifo", 0664);
    // if(ret < 0 && errno!=EEXIST)  //忽略文件存在错误
    // {
    //     perror("mkfifo error");
    //     exit(1);
    // }
    
    fd = open("myfifo", O_RDWR);
    if(fd < 0)
    {
        perror("open error");
        exit(1);
    }
    int n= read(fd, buf, 1024);
    while(n>0)
    
    {
        n = read(fd, buf, 1024);
        write(STDOUT_FILENO, buf, n);
    }
    close(fd);


    return 0;
}

共享内存映射-复杂

1. 实现 无血缘关系 进程通信

2. 实现 父子进程 普通映射通讯

  • 注意: mmap返回值, 如果是 结构体, 返回 结构体指针

3. 实现 父子进程 匿名映射通讯

1-mmap.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <stddef.h> // 包含 offsetof 宏

// 父子 mmap 普通通信,  父写, 子读

struct student 
{
    int id;
    char name[256];
    int score;
};

int main(int argc, char *argv[])
{   
    struct student st = {1, "hzh", 95};

    int fd;
    fd = open("1-mmap-txt", O_CREAT | O_RDWR | O_TRUNC, 0664);
    ftruncate(fd, sizeof(st)); // 将文件 大小 与 数据大小 一样
    // struct student *p = mmap(NULL, sizeof(st), PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
    int *p = mmap(NULL, sizeof(st), PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid > 0)
    {
        while(1)
        {
            memcpy(p, &st, sizeof(st)); //每次修改, 可以看是不是 反映到了 磁盘上
            st.id++;
            sleep(2);
        }
        
    }
    else if(pid == 0)
    {
        while(1)
        {
            // printf("id is %d, name is %s\n", p->id, p->name);
            int *s = (int *)((char *)p+offsetof(struct student, score));   
            /*
            offsetof 是一个宏定义,它用于计算结构体成员相对于结构体起始位置的偏移量。
            返回值是 字节类型, 所以要强转
            
            */
            printf("id is %d, name is %s, score is %d\n", *p, (char *)(p+1), *s);
            sleep(2);
        }
        
    }
    close(fd);
    munmap(p, sizeof(st));


    return 0;
}


2-mmap.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>

//无血缘关系 mmap通讯

int main(int argc, char *argv[])
{   
    int fd;
    char *buf = "hello---------\n";
    fd = open("2-mmap-txt", O_CREAT | O_RDWR, 0664);
    if(fd < 0)
    {
        perror("open error");
        exit(1);
    }

    int *p = mmap(NULL, sizeof(buf), PROT_WRITE, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    printf("%ld\n", strlen(buf));
    write(fd, buf, strlen(buf));
    close(fd);
    munmap(p, sizeof(buf));
        
    return 0;
}


3-mmap.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>

//无血缘关系 mmap通讯

int main(int argc, char *argv[])
{   
    int fd, n;
    char buf[1024];
    fd = open("2-mmap-txt", O_CREAT | O_RDWR, 0664);
    if(fd < 0)
    {
        perror("open error");
        exit(1);
    }

    int *p = mmap(NULL, sizeof(buf), PROT_READ, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }

    while((n=read(fd, buf, 1024))>0)
    {
        printf("%ld\n", n);
        write(STDOUT_FILENO, buf, n);
    }
    
    close(fd);
    munmap(p, sizeof(buf));
        
    return 0;
}


4-mmap.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <stddef.h> // 包含 offsetof 宏

// 匿名映射

struct student 
{
    int id;
    char name[256];
    int score;
};

int main(int argc, char *argv[])
{   
    struct student st = {1, "hzh", 95};

    // struct student *p = mmap(NULL, sizeof(st), PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
    int *p = mmap(NULL, sizeof(st), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    if(p == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid > 0)
    {
        while(1)
        {
            memcpy(p, &st, sizeof(st)); //每次修改, 可以看是不是 反映到了 磁盘上
            st.id++;
            sleep(2);
        }
        
    }
    else if(pid == 0)
    {
        while(1)
        {
            // printf("id is %d, name is %s\n", p->id, p->name);
            int *s = (int *)((char *)p+offsetof(struct student, score));   
            /*
            offsetof 是一个宏定义,它用于计算结构体成员相对于结构体起始位置的偏移量。
            返回值是 字节类型, 所以要强转
            
            */
            printf("id is %d, name is %s, score is %d\n", *p, (char *)(p+1), *s);
            sleep(2);
        }
        
    }
    munmap(p, sizeof(st));


    return 0;
}


简单的信号集操作函数例子

将SIGINT,SIGQUIT加入信号集并屏蔽, 并循环打印 未决信号集

signal.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

// 信号集操作函数 使用

void print_ped(sigset_t *pedset)
{
    int i;
    for(i=1; i<32;i++)
    {
        if(sigismember(pedset, i))
        {
            putchar('1');
        }  //查看 信号是否在未决信号集里,并打印 代号
        else 
        {
            putchar('0');
        }
    }
    printf("\n");
}


int main(int argc, char *argv[])
{
    sigset_t set, oldset, pedset;
    int ret = 0;
    sigemptyset(&set); // 清空信号集
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT); // 加入信号集

    ret = sigprocmask(SIG_BLOCK, &set, &oldset); // 将信号集 设置为屏蔽
    if (ret == -1)
    {
        perror("sigprocmask error");
        exit(1);
    }

    while (1)
    {
        ret = sigpending(&pedset);  // 查看未决信号集   那些已经到达但由于被屏蔽而未处理的信号。
        if (ret == -1)
        {
            perror("sigprocmask error");
            exit(1);
        }

        print_ped(&pedset);
        sleep(2);
    }

    return 0;
}

信号捕捉例子

捕捉 SIGINT,并回调打印

注意 测试sa_mask的作用

1-sigaction.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

void sig_catch(int signo)
{
    if(signo == SIGINT)
    {
        printf("SIGINT is %d\n", signo);
        sleep(3);
    }
    else if(signo == SIGQUIT)
    {
        printf("SIGQUIT is %d\n", signo);
    }
    return ;
}


int main(int argc, char *argv[])
{   
    struct sigaction act,oldact;

    act.sa_handler = sig_catch;   // 回调函数
    act.sa_flags = 0;

    sigemptyset(&(act.sa_mask));
    sigaddset(&act.sa_mask, SIGQUIT);  //将 SIGQUIT 信号添加到 act.sa_mask 中,表示当信号处理程序(回调函数)执行时,SIGINT 信号将被阻塞,不会再被捕获,避免递归调用。    回调函数执行完后, 这个信号会收到, 由于没有捕捉, 将执行 默认动作
    // 不加这个时, 随时SIGQUIT, 都会退出
    // 加了这个, 在回调函数期间, 无反应, 完成后, 才会处理这个

    int ret = sigaction(SIGINT, &act, &oldact );  // 注册信号捕捉函数
    if(ret < 0)
    {
        perror("sigaction error");
        exit(1);
    }

    while(1);
        
    return 0;
}


SIGCHILD信号回收子进程

sigchild.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>


void sig_catch(int signo)
{
    pid_t wpid;
    int status;
    while((wpid = waitpid(-1, &status, 0)) != -1)
    {
        if(WIFEXITED(status))   // 正常退出, 才打印
        {
            printf("waitpid pid is %d, ret = %d\n", wpid, WEXITSTATUS(status));  //打印回收的 pid
        }
    }
    return;

}


int main(int argc, char *argv[])
{   
    pid_t pid;
    sigset_t set, oldset;

    sigemptyset(&set);


    // 添加阻塞, 未进入 子进程时, 阻塞这个信号
    sigaddset(&set, SIGCHLD); 
    int ret = sigprocmask(SIG_BLOCK, &set, &oldset);
    if(ret < 0)
    {
        perror("sigprocmask error");
        exit(1);
    } 
    printf("SIGCHLD is BLOCK\n");
    
    int i;
    for(i = 0; i<10; i++)
    {
        pid = fork();
        if(pid < 0)
        {
            perror("fork error");
            exit(1);
        }
        else if(pid == 0)
        {
            sleep(i);
            break;
        }
        else if(pid > 0)
        {
            continue;
        }
        
    }

    if(i == 10)
    {
        struct sigaction act, oldact;
        act.sa_handler = sig_catch;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        ret = sigaction(SIGCHLD, &act, &oldact);
        if(ret < 0)
        {
           perror("sigaction error");
            exit(1);
        } 

        // 解除阻塞
        sigprocmask(SIG_UNBLOCK, &set, &oldset);
        if(ret < 0)
        {
           perror("sigprocmask error");
           exit(1);
        } 
        printf("SIGCHLD is UNBLOCK, i'm parent, pid is %d\n", getpid());
        while(1);
    }

    else
    {
        printf("i'm is child--%d, pid is %d\n", i, getpid());
    }
        
    return 0;
}


手写一个简单的 守护进程

使用子进程 创建新会话,然后创建守护进程

1-setsid.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{   
        
    pid_t pid;
    int ret, fd;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid > 0)
    {
        exit(0);
    }
    
    pid = setsid(); // 创建会话
    /*
    setsid() 创建一个新的会话,并将当前进程从终端(控制终端)中分离出来,成为会话的领导进程。调用该函数后,进程就不再属于任何终端控制,避免后续信号(如 SIGHUP)干扰。
    
    */



    if(pid < 0)
    {
        perror("setsid error");
        exit(1);
    }

    ret = chdir("/root/hzhdata/");

    /*
    避免了进程保持对根目录(/)的依赖。如果进程工作目录依然在某个已挂载的文件系统上,系统重启或者卸载该文件系统时可能会出问题
    
    */

    umask(0222);   // 头文件 sys/stat.h
    close(STDIN_FILENO);  // 不能乱收东西

    fd = open("/dev/null", O_RDWR);
    if(fd < 0)
    {
        perror("fd error");
        exit(1);
    }
    printf("fd is %d\n", fd);

    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    while(1);

    return 0;
}


循环创建n个子线程

并 循环回收—难点

1-loop-pthread.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// 循环创建子线程, 并验证 是不是共享全局变量

int var = 100;


void *tfn(void *arg)
{
    int i = (int)arg;
    var = 400;
    printf("thread--%d: pid = %d, tid is %lu, var = %d\n", i, getpid(), pthread_self(), var);
    sleep(2);
    printf("test ----%d\n", i);
    return NULL; 
}

int main(int argc, char *argv[])
{   
    pthread_t tid;
    int i=0;
    var = 200;
    while(i<10)
    {
        int ret = pthread_create(&tid, NULL, tfn, (void*)i);
        // int ret = pthread_create(&tid, NULL, tfn, (void*)&i);   // 容易导致 内容错乱
        if(ret < 0)
        {
            fprintf(stderr, "pthread error: %s\n", strerror(ret));
            exit(1);
        }
        i++;
        sleep(1);
    }
    printf("main: pid is %d, tid is %lu, var = %d\n", getpid(), pthread_self(), var);
    
    pthread_exit((void*)0);  // 会退出 当前线程, 也就是只退出了 主线程
    // return 0;//线程不能使用这个   会出现 子线程还未结束, 整体 进程 就退出
}


2-pthread-join-error.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// pthread_join 测试
// 错误示范, 局部变量 地址 会被释放, 所以一直返回 0

int var = 100;


void *tfn(void *arg)
{
    int i = (int)arg;
    var = 400;
    printf("thread--%d: pid = %d, tid is %lu, var = %d\n", i, getpid(), pthread_self(), var);
    sleep(2);
    printf("test ----%d\n", i);
    int *r=&i;
    return (void*)r; 
}

int main(int argc, char *argv[])
{   
    pthread_t tid;
    int i=0;
    var = 200;
    
    int *tidret;  

    while(i<10)
    {
        int ret = pthread_create(&tid, NULL, tfn, (void*)i);
        if(ret < 0)
        {
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
        
        ret = pthread_join(tid, (void **)&tidret);
        if(ret < 0)
        {
            fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
            exit(1);
        }
        printf("child %d return %d\n",i, *tidret);

        i++;
        sleep(1);
    }
    printf("main: pid is %d, tid is %lu, var = %d\n", getpid(), pthread_self(), var);
    
    pthread_exit((void*)0);  // 会退出 当前线程, 也就是只退出了 主线程
    // return 0;//线程不能使用这个   会出现 子线程还未结束, 整体 进程 就退出
}


3-pthread-join-true.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// pthread_join 测试
// 错误示范, 局部变量 地址 会被释放, 所以一直返回 0
// 这是正确示范   使用malloc 可以防止销毁
 
int var = 100;


void *tfn(void *arg)
{
    int i = (int)arg;
    var = 400;
    printf("thread--%d: pid = %d, tid is %lu, var = %d\n", i, getpid(), pthread_self(), var);
    sleep(1);
    printf("test ----%d\n", i);
    int *r = malloc(sizeof(int *));   
    *r = i;   // 不能传地址, 地址会释放, 传递值  注意不是  r=i
    return (void*)r; 
}

int main(int argc, char *argv[])
{   
    pthread_t tid;
    int i=0;
    var = 200;
    
    // int *tidret;   // 外部容易 内存 泄漏

    while(i<10)
    {
        int *tidret; 

        int ret = pthread_create(&tid, NULL, tfn, (void*)i);
        if(ret < 0)
        {
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
        
        ret = pthread_join(tid, (void **)&tidret);
        if(ret < 0)
        {
            fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
            exit(1);
        }
        printf("child %d return %d\n",i, *tidret);
        free(tidret);
        i++;
        sleep(1);
    }
    printf("main: pid is %d, tid is %lu, var = %d\n", getpid(), pthread_self(), var);
    
    pthread_exit((void*)0);  // 会退出 当前线程, 也就是只退出了 主线程
    // return 0;//线程不能使用这个   会出现 子线程还未结束, 整体 进程 就退出
}


使用 pthread_cancel函数取消线程

使用默认系统调用 作为取消点

使用 指定的pthread_testcancel()作为取消点

pthread-cancel.c

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void *tfn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void *)111;
}
void *tfn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void *)222);
}
void *tfn3(void *arg)
{
    while (1)
    {
        // printf("thread 3: I'm going to die in 3 seconds ...\n");  
        // sleep(1); //这两句 会进入系统调用, 到达 取消点, 若没有这两句, 需手动添加取消点
        pthread_testcancel(); //自己添加取消点*/
    }
    return (void *)666;
}
int main(void)
{
    pthread_t tid;
    void *tret = NULL;
    pthread_create(&tid, NULL, tfn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code = %d\n\n", (int)tret);


    pthread_create(&tid, NULL, tfn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code = %d\n\n", (int)tret);


    pthread_create(&tid, NULL, tfn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code = %d\n", (int)tret);


    return 0;
}

设置 线程分离, 并 jion回收, 查看出错

pthread-detach.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

void *tf(void *arg)
{
    printf("exit ----- \n");
    return NULL;
}


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

    int ret = pthread_create(&tid, NULL, tf, NULL);
    if(ret != 0)
    {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_detach(tid);
    if(ret != 0)
    {
        fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
        exit(1);
    }

    sleep(2);  // 不等待,看不出区别
    ret = pthread_join(tid, NULL);
    if(ret != 0)
    {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(1);
    }
    printf("main----\n");

    pthread_exit(NULL);
}


使用线程 属性设置 分离态

pthread-attr.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

void *fn(void *arg)
{
    printf("child-----\n");
    return NULL;
}


int main(int argc, char *argv[])
{   
    pthread_t tid;
    
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    int ret = pthread_create(&tid, &attr, fn, NULL);
    if(ret != 0)
    {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_join(tid, NULL);
    if(ret != 0)
    {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(1);
    }


    pthread_attr_destroy(&attr);
    pthread_exit(NULL);
}


简单的 互斥锁(4个)

读写锁差不多,不写案例

不使用锁的效果

正常加锁解锁

tyrlock 替换进去看效果

营造死锁的 问题

1-mutex-no-lock.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// 无锁, 多个线程 会抢占资源, 不能按照顺序打印

void *fn(void *arg)
{
    while(1)
    {
        printf("hello--2\n");
    sleep(rand()%3);
    printf("world--2\n");
    sleep(rand()%3);
    }
    return NULL;
}


int main(int argc, char *argv[])
{   
    pthread_t tid;
    srand(time(NULL));

    int ret = pthread_create(&tid, NULL, fn, NULL);
    if(ret < 0 )
    {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }
    while(1)
    {
        printf("HELLO--1\n");
        sleep(rand()%3);
        printf("WORLD--1\n");
        sleep(rand()%3);
    }
    

    pthread_join(tid, NULL);
    pthread_exit(NULL);

}

2-mutex-lock.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// 无锁, 多个线程 会抢占资源, 不能按照顺序打印
// 加锁,  会 一个线程 一会占用 

pthread_mutex_t mutex;  // 定义一个全局, 可以使得 所有线程 都能加锁解锁

void *fn(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("hello--2\n");
        sleep(rand()%3);
        printf("world--2\n");
        pthread_mutex_unlock(&mutex);
        sleep(rand()%3);
    }
    return NULL;
}


int main(int argc, char *argv[])
{   
    pthread_t tid;
    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);

    int ret = pthread_create(&tid, NULL, fn, NULL);
    if(ret < 0 )
    {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("HELLO--1\n");
        sleep(rand()%3);
        printf("WORLD--1\n");
        pthread_mutex_unlock(&mutex);
        sleep(rand()%3);
        
    }
    
    
    pthread_join(tid, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_exit(NULL);

}


3-mutex-trylock.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

// 无锁, 多个线程 会抢占资源, 不能按照顺序打印
// 加锁,  会 一个线程 一会占用 

pthread_mutex_t mutex;  // 定义一个全局, 可以使得 所有线程 都能加锁解锁

void *fn(void *arg)
{
    if((pthread_mutex_trylock(&mutex) == 0))
    {
        printf("thread %ld: acquire lock\n", (long)arg);
        sleep(2);
        pthread_mutex_unlock(&mutex);
    }
    else
    {
        printf("thread %ld: none lock\n", (long)arg);
    }
    return NULL;
}


int main(int argc, char *argv[])
{   
    pthread_t tid,tid2;
    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);

    int ret = pthread_create(&tid, NULL, fn, (void *)1);
    pthread_join(tid, NULL);   // 这个位置决定了 1结束, 2才会创建, 因此都会打印 拿到锁

    ret = pthread_create(&tid2, NULL, fn, (void *)2);
    // pthread_join(tid, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_exit(NULL);

}


4-mutex-si.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>



// 反复加锁, 会死锁, 一直阻塞

pthread_mutex_t mutex;  // 定义一个全局, 可以使得 所有线程 都能加锁解锁

void *fn(void *arg)
{
    if((pthread_mutex_lock(&mutex) == 0))
    {
        printf("thread %ld: acquire lock\n", (long)arg);
        sleep(2);
        // pthread_mutex_unlock(&mutex);
    }
    else
    {
        printf("thread %ld: none lock\n", (long)arg);
    }
    return NULL;
}


int main(int argc, char *argv[])
{   
    pthread_t tid,tid2;
    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);

    int ret = pthread_create(&tid, NULL, fn, (void *)1);
    pthread_join(tid, NULL);   // 这个位置决定了 1结束, 2才会创建, 因此都会打印 拿到锁

    ret = pthread_create(&tid2, NULL, fn, (void *)2);
    // pthread_join(tid, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_exit(NULL);

}


条件变量—重点

生产者和消费者模型

producer-customer.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond_product;

struct product *head;
int i=10,j=10;
struct product
{
    int num;
    struct product *next;
};

void Prr(int ret, char *str)
{
    if(ret < 0)
    {
        fprintf(stderr, "%s error : %s\n",str, strerror(ret));
        exit(1);
    }
}

void *Customer(void *arg)
{
    struct product *stc;
    
    while(i!=0)
    {
        pthread_mutex_lock(&mutex); 
        while(head == NULL) // 为空, 则条件变量, 阻塞等待   不使用if, 解除阻塞后, 需要再次检查, 若是多个customer, 可能为空
        {
            pthread_cond_wait(&cond_product, &mutex);
        }
        stc = head;
        head = head->next;
        printf("customer ------ %d\n", stc->num);
        free(stc);
        pthread_mutex_unlock(&mutex);
        sleep(rand()%4);
        i--;
    }
    return NULL;
}

void *Productor(void *arg)
{
    struct product *st;
    while(j!=0)
    {
        st = malloc(sizeof(struct product));
        st->num = rand()%1000;
        printf("product ------ %d\n", st->num);
        
        pthread_mutex_lock(&mutex);  // st 链表作为 共享资源, 因此改变链表需要加锁

        st->next = head;
        head = st;  // 头插法 head要一直保持在 开头,  新元素 next先指向head, head再指向新元素

        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond_product);  // 解锁并 唤醒
        sleep(rand()%4);
        j--;
    }
    return NULL;
}



int main(int argc, char *argv[])
{   
    pthread_t tid1,tid2;
    
    int ret = pthread_create(&tid1, NULL, Productor, NULL);  // 生产和消费的顺序, 决定了 在哪里  malloc
    Prr(ret,"create");
    ret = pthread_create(&tid2, NULL, Customer, NULL);
    Prr(ret, "create");
    

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
        
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_product);
    pthread_exit(NULL);
}


信号量

实现这个的 生产者和消费者模型, 有点不同

只使用 信号量的 例子,

理解为什么 可以 只使用 信号量

sem-pro-cus.c

/*信号量实现 生产者 消费者问题*/
// 如果缓冲区是一个固定大小的数组,且生产者和消费者只通过索引访问缓冲区,那么可以只用信号量来控制访问。
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

#define NUM 5               

int queue[NUM];                                     //全局数组实现环形队列
sem_t blank_number, product_number;                 //空格子信号量, 产品信号量

void *producer(void *arg)
{
    int i = 0;

    while (1) {
        sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待
        queue[i] = rand() % 1000 + 1;               //生产一个产品
        printf("----Produce---%d\n", queue[i]);        
        sem_post(&product_number);                  //将产品数++

        i = (i+1) % NUM;                            //借助下标实现环形
        sleep(rand()%1);
    }
}

void *consumer(void *arg)
{
    int i = 0;

    while (1) {
        sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待
        printf("-Consume---%d\n", queue[i]);
        queue[i] = 0;                               //消费一个产品 
        sem_post(&blank_number);                    //消费掉以后,将空格子数++

        i = (i+1) % NUM;
        sleep(rand()%3);
    }
}

int main(int argc, char *argv[])
{
    pthread_t pid, cid;

    sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享 -- 0
    sem_init(&product_number, 0, 0);                //产品数为0

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);

    sem_destroy(&blank_number);
    sem_destroy(&product_number);

    return 0;
}

TCP/IP协议模型–练习题

server.c

client.c

1-socket-server.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h> // toupper

#define SERV_PORT 9527

int main(int argc, char *argv[])
{   
    int sockfd_1, sockfd_2;
    // 1. 先把 地址结构 初始化好
    struct sockaddr_in serv_addr, clit_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);   
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 2. 创建套接字
    sockfd_1 = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd_1 < 0)
    {
        perror("socket error");
        exit(1);
    }

    // 3. 绑定地址结构
    socklen_t serv_addelen = sizeof(serv_addr);  // bind这个参数 有类型
    if (bind(sockfd_1, (struct sockaddr*)&serv_addr, serv_addelen) < 0)
    {
        perror("bind error");
        exit(1);
    }

    // 4.设置监听数量
    if (listen(sockfd_1, 128) < 0)
    {
        perror("listen error");
        exit(1);
    }

    // 5. 阻塞监听
    socklen_t clit_addrlen = sizeof(clit_addr);
    sockfd_2 = accept(sockfd_1, (struct sockaddr*)&clit_addr, &clit_addrlen); // 注意, 这个地址结构是 传出参数
    if(sockfd_2 < 0)
    {
        perror("accept error");
        exit(1);
    }

    // 6. 打印 连接的客户端 ip 和端口号-----学会用函数
    char client_ip[1024];
    printf("client ip is %s, port is %d\n", inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(clit_addr.sin_port));

    // 7. 实现 服务端的 功能,  小写转大写
    // 套接字的 数据是一次性的
    char buf[1024];
    while(1)
    {
        int n = read(sockfd_2, buf, sizeof(buf));
        if (n <= 0)
        {
            if (n == 0)
            {
                printf("Client disconnected.\n");  
                /*
                当一方关闭连接后,另一方的 read() 操作会收到一个 EOF(End of File)指示,即 read() 返回 0。
                */        
            }
            else
            {
                perror("read error");
            }
            break; // 退出循环
        }

        write(STDOUT_FILENO, "Received: ", 10);
        write(STDOUT_FILENO, buf, n);

        for(int i = 0; i < n; i++)
        {
            buf[i] = toupper(buf[i]);
        }

        write(STDOUT_FILENO, "To upper: ", 10);
        write(STDOUT_FILENO, buf, n);

        // 将转换后的数据发送回客户端
        write(sockfd_2, buf, n);
    }

    close(sockfd_1);
    close(sockfd_2);

    return 0;
}

2-socket-client.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_PORT 9527   // 无分号
// 不使用 有参命令


int main(int argc, char *argv[])
{   
    int sockfd;
    // 1. 初始化地址, 客户端里, 这个地址结构是服务器的 各项数据
    struct sockaddr_in serve_addr;
    serve_addr.sin_family = AF_INET;
    serve_addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, "127.0.0.1", &serve_addr.sin_addr.s_addr);

    // 2. 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 3. 隐式绑定, 连接客户端
    socklen_t serve_addrlen = sizeof(serve_addr);
    if(connect(sockfd, (struct sockaddr*)&serve_addr, serve_addrlen)!=0)
    {
        perror("connect error");
        exit(1);
    }

    // 4.输入
    int count = 5;
    char buf[1024];
    do
    {
        write(sockfd, "hello\n", 7);
        int n = read(sockfd, buf, sizeof(buf));
        if(n<=0)
        {
            if(n==0)
            {
                printf("finish \n");
            }
            else
            {
                perror("read error");
                exit(1);
            }
        }

        write(STDOUT_FILENO, buf, n);


    } while (count--);
    
    close(sockfd);

        
    return 0;
}


多进程并发 and 多线程并发

特别注意: 多进程并发时, 一定要 对accept 进行判断, 都则可能会 返回-1,还在执行, 会一直打印 read error

特别注意: 端口号, 一定要找 没有被占用的, 否则 也会 一直打印 read error

1-more-fork-socket.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#define S_PORT 9001

void catch_child(int signo)
{
    // 只捕捉一个, 不用判断
    while((waitpid(0, NULL, WNOHANG))>0);
    return ;
}

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

    // 捕捉信号
    struct sigaction act;
    act.sa_handler = catch_child;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    int ret = sigaction(SIGCHLD, &act, NULL);
    if(ret < 0)
    {
        perror("sigaction error");
        exit(1);
    }


    
    struct sockaddr_in se_addr, cli_addr;
    bzero(&se_addr, sizeof(se_addr));
    se_addr.sin_family = AF_INET;
    se_addr.sin_port = htons(S_PORT);
    se_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int sockfd_child; // 共享文件描述符

    socklen_t len_se = sizeof(se_addr);
    bind(sockfd, (struct sockaddr*)&se_addr, len_se);
    listen(sockfd, 128);

    while(1)
    {
        // sockfd_child 在这里定义, 会是局部的, 不能传递到外面
        socklen_t len = sizeof(cli_addr);
        if ((sockfd_child = accept(sockfd, (struct sockaddr*)&cli_addr, &len)) < 0) {
            perror("accept error");
            continue; // 如果接受失败,继续等待其他连接
        }
        pid = fork();
        if(pid < 0)
        {
            perror("fork error");
            exit(1);
        }
        else if(pid == 0)
        {
            close(sockfd);
            char buf[1024];
            while(1) 
        {
            
            int n = read(sockfd_child, buf, sizeof(buf));
            if (n <= 0) {
                if (n == 0)
                {
                    printf("Client disconnected.\n");  
                    /*
                    当一方关闭连接后,另一方的 read() 操作会收到一个 EOF(End of File)指示,即 read() 返回 0。
                    */   
                }
                else
                {
                    perror("read error");
                }
                close(sockfd_child);
                // break; // 退出循环
                exit(0);
            }
            for (int i = 0; i < n; i++)
                buf[i] = toupper(buf[i]);
            write(sockfd_child, buf, n);
            write(STDOUT_FILENO, buf, n);
        }
        }
        else
        {
            close(sockfd_child);
        }
    }

    // if(pid == 0)
    // {
        
        
    // }
    close(sockfd); // 关闭服务器监听套接字
    return 0;
}


4-more-pthread-socket.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <pthread.h>
#include <ctype.h>
#include <arpa/inet.h>

#define S_PORT 9001

void* fn(void *arg)
{
    int sockfd_fn = (int)arg;
    char buf[1024];
    while(1)
    {
        int n = read(sockfd_fn, buf, sizeof(buf));
        if(n<=0)
        {
            if(n==0)
            {
                printf("client disconnect---\n");
                // exit(0);
            }
            else{
                perror("read error");
                // exit(1);
            }
            close(sockfd_fn);
            break;
        }
        printf("receve :\n");
        write(STDOUT_FILENO, buf, n);
        for(int i=0; i<n; i++)
        {
            buf[i] = toupper(buf[i]);
        }
        printf("toupper: \n");
        write(sockfd_fn, buf, n);
        write(STDOUT_FILENO, buf, n);

    }

    return NULL;
}

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

    struct sockaddr_in ser_addr, cli_addr;
    memset(&ser_addr, 0, sizeof(ser_addr));
    memset(&cli_addr, 0, sizeof(cli_addr));

    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port = htons(S_PORT);
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd<0)
    {
        perror("socket error");
        exit(1);
    }

    socklen_t len_ser = sizeof(ser_addr);
    if(bind(sockfd, (struct sockaddr*)&ser_addr, len_ser))
    {
        perror("bind error");
        exit(1);
    }

    if(listen(sockfd, 128))
    {
        perror("listen error");
        exit(1);
    }
    socklen_t len_cli = sizeof(cli_addr);
    while(1)
    {
        int sockfd_p = accept(sockfd, (struct sockaddr*)&cli_addr, &len_cli);
        if(sockfd_p < 0)
        {
            perror("accept error");
            continue;
            // exit(1);
        }
        // close(sockfd);   // 这里不能关闭, 后续无法继续监听了 线程之间 不共享栈空间
        int ret = pthread_create(&tid, NULL, fn, (void *)sockfd_p);
        if(ret < 0)
        {
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            close(sockfd_p);
            continue;
        }
        pthread_detach(tid);  // 分离线程,  子线程自动回收
    }

    
    pthread_exit(NULL);

}


epoll 服务-客户练习

做 多客户端链接的 服务端, 注意 epoll函数的使用

1-epoll-server.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <ctype.h>

#define S_PORT 9003
#define MAX_EVENTS 10
#define BUF_SIZE 1024

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main(int argc, char *argv[]) {
    int sockfd, epfd;
    struct sockaddr_in ser_addr, cli_addr;
    socklen_t len_cli = sizeof(cli_addr);

    // 1. 创建监听套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket error");
        exit(1);
    }

    // 2. 绑定地址和端口
    memset(&ser_addr, 0, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port = htons(S_PORT);
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)) < 0) {
        perror("bind error");
        exit(1);
    }

    // 3. 开始监听
    if (listen(sockfd, 128) < 0) {
        perror("listen error");
        exit(1);
    }

    printf("Server is listening on port %d...\n", S_PORT);

    // 4. 创建 epoll 实例
    epfd = epoll_create1(0);
    if (epfd < 0) {
        perror("epoll_create1 error");
        exit(1);
    }

    // 5. 将监听套接字加入 epoll -----  先监听 主要的 sockfd, 用于接收新的客户端连接
    struct epoll_event event;
    event.events = EPOLLIN; // 监听读事件
    event.data.fd = sockfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) < 0) {
        perror("epoll_ctl error");
        exit(1);
    }

    // 6. 事件循环
    struct epoll_event events[MAX_EVENTS];
    char buf[BUF_SIZE];
    while (1) {
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait error");
            exit(1);
        }

        // 这里是处理 满足事件的 监听点
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == sockfd) {
                // 有新客户端连接
                int client_fd = accept(sockfd, (struct sockaddr*)&cli_addr, &len_cli);
                if (client_fd < 0) {
                    perror("accept error");
                    continue;
                }

                // 设置非阻塞模式
                if (set_nonblocking(client_fd) < 0) {
                    perror("set_nonblocking error");
                    close(client_fd);
                    continue;
                }

                // 将客户端套接字加入 epoll
                event.events = EPOLLIN | EPOLLET; // 边缘触发模式
                event.data.fd = client_fd;
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                    perror("epoll_ctl error");
                    close(client_fd);
                }

                printf("New client connected: fd=%d\n", client_fd);
            } else {
                // 处理客户端数据
                int client_fd = events[i].data.fd;
                while (1) {
                    ssize_t n = read(client_fd, buf, BUF_SIZE);
                    if (n > 0) {
                        // 输出到标准输出
                        for(int i=0; i<n; i++)
                        {
                            buf[i] = toupper(buf[i]);
                        }
                        if(write(client_fd, buf, n)<0)
                        {
                            perror("write error");
                            exit(1);
                        }
                        write(STDOUT_FILENO, buf, n);
                    } else if (n == 0) {
                        // 客户端断开连接
                        printf("Client disconnected: fd=%d\n", client_fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                        close(client_fd);
                        break;
                    } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // 数据读取完毕
                        break;
                    } else {
                        perror("read error");
                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                        close(client_fd);
                        break;
                    }
                }
            }
        }
    }

    close(sockfd);
    close(epfd);
    return 0;
}

2-epoll-client.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_PORT 9003   // 无分号
// 不使用 有参命令

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main(int argc, char *argv[])
{   
    int sockfd;
    // 1. 初始化地址, 客户端里, 这个地址结构是服务器的 各项数据
    struct sockaddr_in serve_addr;
    serve_addr.sin_family = AF_INET;
    serve_addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, "127.0.0.1", &serve_addr.sin_addr.s_addr);

    // 2. 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 3. 隐式绑定, 连接客户端
    socklen_t serve_addrlen = sizeof(serve_addr);
    if(connect(sockfd, (struct sockaddr*)&serve_addr, serve_addrlen)!=0)
    {
        perror("connect error");
        exit(1);
    }

    // 4.输入
    char buf[1024];
    // set_nonblocking(sockfd);
    while (1)
    {
        int n = read(sockfd, buf, sizeof(buf));   // 使得 服务端 一关闭 连接, 就收到
        if (n < 0) {
             perror("read error");
             break;
         } else if (n == 0) {
             printf("Server closed the connection\n");
             break;
         }
        n = read(STDIN_FILENO, buf, sizeof(buf));
        if(n<=0)
        {
            if(n==0)
            {
                printf("finish \n");
            }
            else
            {
                perror("read error");
                exit(1);
            }
            break;
        }
        write(sockfd, buf, n);
        // 从服务器读取响应
        n = read(sockfd, buf, sizeof(buf));
        if (n < 0) {
             perror("read error");
             break;
         } else if (n == 0) {
             printf("Server closed the connection\n");
             break;
         }
         write(STDOUT_FILENO, buf, n);
    }
    
    close(sockfd);

        
    return 0;
}


相关文章:

  • 区块链技术
  • 基于深度学习的图像识别技术在工业检测中的应用
  • 第16届蓝桥杯单片机4T模拟赛三
  • Vue3(自定义指令directive详解)
  • Kubernetes中将SpringBoot3的application.yaml配置文件迁移到ConfigMap实现配置与代码的分离
  • 简述一下Unity中的碰撞检测
  • c# 2025-3-22 周六
  • 优化 SQL 语句方向和提升性能技巧
  • 比特币驱动金融第八章——探索比特币之外:多样化的挖矿算法
  • EtherCAT转ProfiNet网关如何实现西门子1200PLC与伺服电机通讯(ProfiNet总线协议)
  • 【硬核实战】ETCD+AI智能调度深度整合!从架构设计到调优避坑,手把手教你打造高可用调度系统!
  • Cursor安装注册+基础配置+入门实操
  • elasticsearch+sentencetransformer检索样例
  • SpringBoot3+Vue3实战(Vue3快速开发登录注册页面并对接后端接口、表单项自定义校验规则、Hutool工具类)(4)
  • 深度学习复习笔记(8)特征提取与无监督学习
  • 再学:区块链基础与合约初探 EVM与GAS机制
  • LiteraSageAI 项目介绍
  • hexo+butterfly博客功能完善和美化(四)---博客上传
  • Ubuntu实时读取音乐软件的音频流
  • 网络世界探索之旅:网络编程
  • 中美经贸高层会谈将在午餐后继续
  • 云南多地突查公职人员违规饮酒:公安局门口开展酒精吹气测试
  • 上海劳模风采馆焕新升级后重新开放,展示480位劳模先进故事
  • 马上评丨学术不容“近亲繁殖”
  • 沃旭能源因成本上升放弃英国海上风电项目,或损失近40亿元
  • 2025年上海市模范集体、劳动模范和先进工作者名单揭晓