系统+网络练习题代码汇总
练习题
本节所有代码均为本人手写, 不出意外将全部可以正确执
建议读者学习完基本课程后, 最后统一复习用,建议读者先根据题目自己写, 有问题自己先调试
文章目录
- 练习题
- 错误号返回值考虑
- 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;
}