杨记

碎片化学习令人焦虑,系统化学习使人进步

0%

阶段性练习

学习完Linux的文件、目录、进程相关知识后,阶段性练习

文件多进程拷贝

假设有一个超大文件,需对其完成拷贝工作。为提高效率,可采用多进程并行拷贝的方法来实现。假设文件大小为len,共有n个进程对该文件进行拷贝。那每个进程拷贝的字节数应为len/n。但未必一定能整除,我们可以选择让最后一个进程负责剩余部分拷贝工作。可使用len % (len / n)将剩余部分大小求出。

为降低实现复杂度,可选用mmap来实现源、目标文件的映射,通过指针操作内存地址,设置每个进程拷贝的起始、结束位置。借助 MAP_SHARED 选项将内存中所做的修改反映到物理磁盘上。

image-20220605122529596

老师的答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>

void err_int(int ret, const char *err)
{
if (ret == -1) {
perror(err);
exit(1);
}

return ;
}

void err_str(char *ret, const char *err)
{
if (ret == MAP_FAILED) {
perror(err);
exit(1);
}
}

int main(int argc, char *argv[])
{
int fd_src, fd_dst, ret, len, i, n;
char *mp_src, *mp_dst, *tmp_srcp, *tmp_dstp;
pid_t pid;
struct stat sbuf;

if (argc < 3 || argc > 4) {
printf("Enter like this please: ./a.out file_src file_dst [process number]\n");
exit(1);
} else if (argc == 3) {
n = 5; //用户未指定,默认创建5个子进程
} else if (argc == 4) {
n = atoi(argv[3]);
}

//打开源文件
fd_src = open(argv[1], O_RDONLY);
err_int(fd_src, "open dict.txt err");
//打开目的文件, 不存在则创建
fd_dst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664);
err_int(fd_dst, "open dict.cp err");
//获取文件大小
ret = fstat(fd_src, &sbuf);
err_int(ret, "fstat err");

len = sbuf.st_size;
if (len < n) //文件长度小于进程个数
n = len;
//根据文件大小拓展目标文件
ret = ftruncate(fd_dst, len);
err_int(ret, "truncate fd_dst err");
//为源文件创建映射
mp_src = (char *)mmap(NULL, len, PROT_READ, MAP_SHARED, fd_src, 0);
err_str(mp_src, "mmap src err");
//为目标文件创建映射
mp_dst = (char *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_dst, 0);
err_str(mp_dst, "mmap dst err");

tmp_dstp = mp_dst;
tmp_srcp = mp_src;
//求出每个子进程该拷贝的字节数
int bs = len / n; //每个子进程应该拷贝的字节数
int mod = len % bs; //求出均分后余下的字节数,让最后一个子进程处理

//创建N个子进程
for (i = 0; i < n; i++) {
if ((pid = fork()) == 0) {
break;
}
}

if (n == i) { //父进程
for (i = 0; i < n; i++)
wait(NULL);

} else if (i == (n-1)){ //最后一个子进程,它多处理均分后剩余几个字节
memcpy(tmp_dstp+i*bs, tmp_srcp+i*bs, bs+mod);
} else if (i == 0) { //第一个子进程
memcpy(tmp_dstp, tmp_srcp, bs);
} else { //其他子进程
memcpy(tmp_dstp+i*bs, tmp_srcp+i*bs, bs);
}

munmap(mp_src, len);
munmap(mp_dst, len);

return 0;
}

我的答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// wrap.h
ssize_t Read(int fd, void *buf, size_t count);

ssize_t Write(int fd, const void *buf, size_t count);

int Stat(const char *pathname, struct stat *statbuf);

int Ftruncate(int fd, off_t length);

off_t Lseek(int fd, off_t offset, int whence);

void *Mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

int Munmap(void *addr, size_t length);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

#include "wrap.h"

ssize_t Read(int fd, void *buf, size_t count)
{
ssize_t n = read(fd, buf, count);
if (n == -1)
{
perror("read file error");
exit(-1);
}
return n;
}

ssize_t Write(int fd, const void *buf, size_t count)
{
ssize_t n = write(fd, buf, count);
if (n == -1)
{
perror("write file error");
exit(-1);
}
return n;
}

int Stat(const char *pathname, struct stat *statbuf)
{
int ret = stat(pathname, statbuf);
if (ret == -1)
{
perror("stat");
exit(-1);
}
return ret;
}

int Ftruncate(int fd, off_t length)
{
if (ftruncate(fd, length) == -1)
{
perror("ftruncate");
exit(-1);
}
return 0;
}


off_t Lseek(int fd, off_t offset, int whence)
{
off_t ret = lseek(fd, offset, whence);
if (ret == (off_t)-1)
{
perror("lseek");
exit(-1);
}
return ret;
}

void *Mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
void *ret = mmap(addr, length, prot, flags, fd, offset);
if (MAP_FAILED == ret)
{
perror("mmap error");
exit(-1);
}
return ret;
}

int Munmap(void *addr, size_t length)
{
int ret = munmap(addr, length);
if (ret == -1)
{
perror("munmap");
exit(-1);
}
return ret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#include "wrap.h"

int subProcessNum = 8; // 进程数

int dirCopy(char* src, char* dest);

int fileCopy(char* src, char* dest);

int main(int argc, char *argv[])
{
int srcfd;
struct stat srcStat, destStat;
memset(&srcStat, 0, sizeof(struct stat));
memset(&destStat, 0, sizeof(struct stat));

if (argc < 3)
{
printf("need 2 or 3 parameters: src dest [num]\n");
printf("- src : 源文件路径\n");
printf("- dest: 目的路径\n");
printf("- num : 指定进程数量5-20(可选,默认8)\n");
exit(-1);
}

if (argc == 4)
{
int num = atoi(argv[3]);
if (num > 20) num = 20;
if (num < 5) num = 5;
subProcessNum = num;
}

char* src = argv[1];
char* dest = argv[2];

srcfd = open(src, O_RDONLY); // 判断源文件是否存在
if (srcfd == -1)
{
perror(src);
printf("%d\n", __LINE__);
exit(-1);
}

// 判断src 是 文件 or 目录
memset(&srcStat, 0, sizeof(struct stat));
Stat(src, &srcStat);
if (S_ISDIR(srcStat.st_mode))
{
Stat(dest, &destStat);
if (!S_ISDIR(destStat.st_mode))
{
printf("%s is dir, then %s must be dir too\n", src, dest);
printf("%d\n", __LINE__);
exit(-1);
}
dirCopy(src, dest);
}
else
{
stat(dest, &destStat);
if ((stat(dest, &destStat) != -1) && S_ISDIR(destStat.st_mode)) // 是目录
{
// 提取文件名
char* filename = strrchr(src, '/');
// 文件名拼接路径
if (filename != NULL)
{
dest = strcat(dest, filename);
}
else
{
dest = strcat(dest, src);
}
}
printf("start copy ....\n");
fileCopy(src, dest);
printf("end copy ....\n");
}

return 0;
}

int dirCopy(char* src, char* dest)
{
printf("暂不支持目录的复制\n");
return 1;
}

int fileCopy(char* src, char* dest)
{
int srcfd, destfd;
struct stat srcStat, destStat;
off_t fileSize, subSize;
int n4k, subn4k;
long pageSize; // 页大小
memset(&srcStat, 0, sizeof(struct stat));
memset(&destStat, 0, sizeof(struct stat));

if (access(dest, F_OK) == 0)
{
char yorn = '\0';
printf("%s exists, do you want cover(Y|N): ", dest);
yorn = getchar();
if (yorn != 'Y' && yorn != 'y') exit(0);
}

srcfd = open(src, O_RDONLY);
destfd = open(dest, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (destfd == -1)
{
perror("open destfd");
printf("%d\n", __LINE__);
exit(-1);
}

pageSize = sysconf(_SC_PAGESIZE); // 获取页大小
Stat(src, &srcStat);
fileSize = srcStat.st_size; // 获取源文件大小

Ftruncate(destfd, fileSize); // 设置目的文件大小

n4k = fileSize / pageSize; // fileSize中有多少个4k
subn4k = n4k / subProcessNum; // 每个进程几个4k

for(int i = 0; i < subProcessNum; ++i)
{
pid_t p = fork();
if (p == 0)
{
subSize = subn4k * pageSize;
if (i == subProcessNum - 1)
{
subSize += (n4k % subProcessNum) * pageSize + fileSize % pageSize;
}
char* srcmap = Mmap(NULL, subSize, PROT_READ, MAP_SHARED, srcfd, pageSize * subn4k * i);
char* destmap = Mmap(NULL, subSize, PROT_READ | PROT_WRITE, MAP_SHARED, destfd, pageSize * subn4k * i);

strncpy(destmap, srcmap, subSize);

Munmap(srcmap, subSize);
Munmap(destmap, subSize);

printf("%d process over %dB\n", i, subSize);
exit(0);
}
else if(p == -1)
{
perror("fork error");
exit(-1);
}
}

while( wait(NULL) != -1 );

close(srcfd);
close(destfd);
return 1;
}

简单Shell

使用已学习的各种 C 函数实现一个简单的交互式shell,要求:

  1. 给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的 exec 函数执行程序,待执行完成后再次给出提示符。
  2. 该程序可识别和处理一下符号:

    1. 简单的标准输入输出重定向:如:ls | wc -l,先dup2然后exec
    2. 管道(|):Shell进程先调用pipe创建管道,然后fork两个子进程。一个子进程关闭读端,调用dup2将写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait等待两个子进程终止。
  3. 可以有多个空格

实现步骤:

  1. 接收用户输入命令字符串,拆分命令及参数存储。(自行设计数据存储结构)
  2. 实现普通命令加载功能
  3. 实现输入、输出重定向的功能
  4. 实现管道
  5. 支持多重管道

老师的答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>

#define MAXLINE 4096
#define MAXPIPE 16
#define MAXARG 8

struct {
char *argv[MAXARG];
char *in, *out;
} cmd[MAXPIPE+1];

int parse(char *buf, int cmdnum)
{
int n = 0;
char *p = buf;
cmd[cmdnum].in = cmd[cmdnum].out = NULL;

//ls -l -d -a -F > out
while (*p != '\0') {

if (*p == ' ') { //将字符串中所有的空格,替换成'\0',方便后续拆分字符串
*p++ = '\0';
continue;
}

if (*p == '<') {
*p = '\0';
while (*(++p) == ' '); /* cat < file 处理连续多个空格的情况*/
cmd[cmdnum].in = p;
if (*p++ == '\0')
return -1;
continue;
}

if (*p == '>') {
*p = '\0';
while (*(++p) == ' ');
cmd[cmdnum].out = p;
if (*p++ == '\0')
return -1;
continue;
}

if (*p != ' ' && ((p == buf) || *(p-1) == '\0')) {

if (n < MAXARG - 1) {
cmd[cmdnum].argv[n++] = p++; //"ls -l -R > file"
continue;
} else {
return -1;
}
}
p++;
}

if (n == 0) {
return -1;
}

cmd[cmdnum].argv[n] = NULL;

return 0;
}

int main(void)
{
char buf[MAXLINE];
pid_t pid;
int fd, i, j, pfd[MAXPIPE][2], pipe_num, cmd_num;
char* curcmd, *nextcmd;

while (1) {
printf("mysh%% ");
if (!fgets(buf, MAXLINE, stdin))
exit(0);
// "ls -l\n"
if (buf[strlen(buf)-1]=='\n')
buf[strlen(buf)-1]='\0';
cmd_num = 0;
nextcmd = buf;

while ((curcmd = strsep(&nextcmd, "|"))) {

if (parse(curcmd, cmd_num++)<0) {
cmd_num--;
break;
}

if (cmd_num == MAXPIPE + 1)
break;
}

if (!cmd_num)
continue;

pipe_num = cmd_num - 1; //根据命令数确定要创建的管道数目

for (i = 0; i < pipe_num; i++) { //创建管道
if (pipe(pfd[i])) {
perror("pipe");
exit(1);
}
}

for (i = 0; i < cmd_num; i++) { //管道数目决定创建子进程个数
if ((pid = fork()) == 0)
break;
}

if (pid == 0) {
if (pipe_num) { //用户输入的命令中含有管道

if (i == 0) { //第一个创建的子进程
dup2(pfd[0][1], STDOUT_FILENO);
close(pfd[0][0]);

for (j = 1; j < pipe_num; j++) { //在该子进程执行期间,关闭该进程使用不到的其他管道的读端和写端
close(pfd[j][0]);
close(pfd[j][1]);
}

} else if (i==pipe_num) { //最后一个创建的子进程
dup2(pfd[i-1][0], STDIN_FILENO);
close(pfd[i-1][1]);

for (j = 0; j < pipe_num-1; j++) { //在该子进程执行期间,关闭该进程不使用的其他管道的读/写端
close(pfd[j][0]);
close(pfd[j][1]);
}

} else {
dup2(pfd[i-1][0], STDIN_FILENO); //重定中间进程的标准输入至管道读端
close(pfd[i-1][1]); //close管道写端

dup2(pfd[i][1], STDOUT_FILENO); //重定中间进程的标准输出至管道写端
close(pfd[i][0]); //close管道读端

for (j = 0; j < pipe_num; j++) //关闭不使用的管道读写两端
if (j != i || j != i-1) {
close(pfd[j][0]);
close(pfd[j][1]);
}
}
}
if (cmd[i].in) { /*用户在命令中使用了输入重定向*/
fd = open(cmd[i].in, O_RDONLY); //打开用户指定的重定向文件,只读即可
if (fd != -1)
dup2(fd, STDIN_FILENO); //将标准输入重定向给该文件
}
if (cmd[i].out) { /*用户在命令中使用了输出重定向*/
fd = open(cmd[i].out, O_WRONLY|O_CREAT|O_TRUNC, 0644); //使用写权限打开用户指定的重定向文件
if (fd != -1)
dup2(fd, STDOUT_FILENO); //将标准输出重定向给该文件
}

execvp(cmd[i].argv[0], cmd[i].argv); //执行用户输入的命令
fprintf(stderr, "executing %s error.\n", cmd[i].argv[0]);
exit(127);
}

/* parent */
for (i = 0; i < pipe_num; i++) { /*父进程不参与命令执行,关闭其掌握的管道两端*/
close(pfd[i][0]);
close(pfd[i][1]);
}

for (i = 0; i < cmd_num; i++) { /*循环回首子进程*/
wait(NULL);
}
}
}

我的答案

忘了重定向,没设计结构体,不好改,懒得改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int splitSpace(const char *str, char **argv);

int find(const char *str, char c);

void freeArgv(char **argv);

void err_int(int ret, const char *err)
{
if (ret == -1)
{
perror(err);
exit(-1);
}
}

void err_str(char *ret, const char *err)
{
if (ret == NULL)
{
perror(err);
exit(-1);
}
}

int main(void)
{
system("stty erase ^H"); // 解决backspace键显示^H问题
char buf[1024] = {0};
//int pfd[1024][2];
char *ret_char;
int ret_int;
char *cmdArgvs[50][50] = {NULL};
int cmdNum = 0;
char *cmdArgv[30] = {NULL}; // 命令及参数
int pre = -1, pipePos = 0; // 查找到 | 在字符串中的下标
int len = 0; // 输入的字符长度
int pipefd[20][2];

while(1)
{
printf("myshell$ ");
ret_char = fgets(buf, sizeof(buf), stdin);
err_str(ret_char, "fgets error");
buf[strlen(buf)-1] = '\0'; // 将最后的 \n 改为 \0
len = strlen(buf); // 字符串长度

// 根据 | 分割字符串
while (pre + 1 < len)
{
pipePos = find(buf + pre + 1, '|'); // 查找 |
if (pipePos != -1)
{
buf[pipePos + pre + 1] = '\0';
}

// 再根据 空格 分割字符串
int num = splitSpace(buf + pre + 1, cmdArgv);
if (num == 0 || pipePos == len - 1)
{
for (int i = 0; i < cmdNum; ++i)
{
freeArgv(cmdArgvs[i]);
}
cmdNum = 0;
break;
}
for (int i = 0; i < num; ++i)
{
cmdArgvs[cmdNum][i] = cmdArgv[i];
}
++cmdNum;
pre = pipePos == -1 ? len : pipePos + pre + 1; // 更新前一个 | 的位置
}

// 创建cmdNum-1个管道
for (int i = 0; i < cmdNum - 1; ++i)
{
ret_int = pipe(pipefd[i]);
err_int(ret_int, "pipe error");
}
// 创建子进程 执行exec函数
for (int i = 0; i < cmdNum; ++i)
{
pid_t pid = vfork();
err_int(pid, "vfork error");
if (pid == 0)
{
if (i > 0) dup2(pipefd[i - 1][0], STDIN_FILENO);
if (cmdNum != i + 1) dup2(pipefd[i][1], STDOUT_FILENO);
for (int i = 0; i < cmdNum - 1; ++i)
{
close(pipefd[i][0]); // 关闭读端
close(pipefd[i][1]); // 关闭写端
}
ret_int = execvp(cmdArgvs[i][0], cmdArgvs[i]);
err_int(ret_int, "execvp error");
}
}
// 关闭父进程对管道的引用
for (int i = 0; i < cmdNum - 1; ++i)
{
close(pipefd[i][0]); // 关闭读端
close(pipefd[i][1]); // 关闭写端
}
// 回收子进程
while(wait(NULL) != -1);

for (int i = 0; i < cmdNum; ++i)
{
freeArgv(cmdArgvs[i]);
}

cmdNum = 0;
pre = -1;
pipePos = 0;
memset(buf, 0, sizeof(buf));
len = 0;
}
return 0;
}

int splitSpace(const char *str, char **argv)
{
int len = strlen(str);
int pre = 0, pos = 0, num = 0, n = 0;
for (pos = 0; pos < len; )
{
while (pos < len && str[pos] == ' ') ++pos;
pre = pos;
while (pos < len && str[pos] != ' ') ++pos;
n = pos - pre;
if (n == 0) continue;
argv[num] = malloc(n + 1);
err_str(argv[num], "malloc error");
argv[num] = strncpy(argv[num], str + pre, n);
argv[num][n] = '\0';
n = 0;
++num;
}
return num; // 返回命令加参数的个数 如 wc -l 返回2
}

int find(const char *str, char c)
{
char *ret = strchr(str, c);
if (ret == NULL) return -1;
return ret - str;
}

void freeArgv(char **argv)
{
int i = 0;
while(argv[i] != NULL)
{
free(argv[i]);
argv[i] = NULL;
++i;
}
}

本地聊天室

简易本地聊天室。

借助IPC完成一个简易的本地聊天功能。设有服务器端和客户端两方。服务启动监听客户端请求,并负责记录处理客户端登录、聊天、退出等相关数据。客户端完成登录、发起聊天等操作。可以借助服务器转发向某个指定客户端完成数据包发送(聊天)。

客户端向服务器发送数据包,可采用如下协议格式来存储客户端数据,使用“协议号”区分客户端请求的各种状况。服务器依据包号处理客户端对应请求。

image-20220605145114802

老师的答案

qq_ipc.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef QQ_IPC_H
#define QQ_IPC_H

struct QQ_DATA_INFO {
int protocal;
char srcname[20];
char destname[20];
char data[100];
};

/*
* protocal srcname destname data
* 1 登陆者 NULL
* 2 发送方 接收方 数据
* 3 NULL(不在线)
* 4 退出登陆用户(退出登陆)
*/

#endif

mylink.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef _MYLINK_H_
#define _MYLINK_H_

typedef struct node *mylink;
struct node {
char item[20]; //记录客户端名字
int fifo_fd; //该客户端使用的私有管道文件描述符(写端)
mylink next; //struct node *next;
};

void mylink_init(mylink *head);
mylink make_node(char *name, int fd);
void mylink_insert(mylink *head, mylink p);
mylink mylink_search(mylink *head, char *keyname);
void mylink_delete(mylink *head, mylink p);
void free_node(mylink p);
void mylink_destory(mylink *head);
void mylink_travel(mylink *head, void (*vist)(mylink));

#endif

mylink.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mylink.h"

void mylink_init(mylink *head) //struct node **head = &head
{
*head = NULL;
}

mylink make_node(char *item, int fd)
{
mylink p = (mylink)malloc(sizeof(struct node));
strcpy(p->item,item); //(*p).itme = item;
p->fifo_fd = fd;

p->next = NULL; //#define NULL (void *)0
return p;
}

void mylink_insert(mylink *head, mylink p)
{
p->next = *head;
*head = p;
}

mylink mylink_search(mylink *head, char *keyname)
{
mylink p;
for (p = *head; p != NULL; p = p->next)
if (strcmp(p->item,keyname) == 0)
return p;
return NULL;
}

void mylink_delete(mylink *head, mylink q)
{
mylink p;
if (q == *head) {
*head = q->next;
return;
}
for (p = *head; p != NULL; p = p->next)
if (p->next == q) {
p->next = q->next;
return;
}
}

void free_node(mylink p)
{
free(p);
}

void mylink_destory(mylink *head)
{
mylink p= *head, q;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
*head = NULL;
}

void mylink_travel(mylink *head, void (*vist)(mylink))
{
mylink p;
for (p = *head; p != NULL; p = p->next)
vist(p);
}

qq_ipc_server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "qq_ipc.h"
#include "mylink.h"

#define SERVER_PROT "SEV_FIFO" /*定义众所周知的共有管道*/

mylink head = NULL; /*定义用户描述客户端信息的结构体*/

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

/*有新用户登录,将该用户插入链表*/
int login_qq(struct QQ_DATA_INFO *buf, mylink *head)
{
int fd;

fd = open(buf->srcname, O_WRONLY); /*获取登录者名字,以只写方式打开以其名字命名的私有管道*/
mylink node = make_node(buf->srcname, fd); /*利用用户名和文件描述符创建一个节点*/
mylink_insert(head, node); /*将新创建的节点插入链表*/

return 0;
}

/*客户端发送聊天,服务器负责转发聊天内容*/
void transfer_qq(struct QQ_DATA_INFO *buf, mylink *head)
{
mylink p = mylink_search(head, buf->destname); /*遍历链表查询目标用户是否在线*/
if (p == NULL) {
struct QQ_DATA_INFO lineout = {3}; /*目标用户不在, 封装3号数据包*/
strcpy(lineout.destname, buf->destname); /*将目标用户名写入3号包*/
mylink q = mylink_search(head, buf->srcname); /*获取源用户节点,得到对应私有管道文件描述符*/

write(q->fifo_fd, &lineout, sizeof(lineout)); /*通过私有管道写给数据来源客户端*/
} else
write(p->fifo_fd, buf, sizeof(*buf)); /*目标用户在线,将数据包写给目标用户*/
}

/*客户端退出*/
int logout_qq(struct QQ_DATA_INFO *buf, mylink *head)
{
mylink p = mylink_search(head, buf->srcname); /*从链表找到该客户节点*/

close(p->fifo_fd); /*关闭其对应的私有管道文件描述符*/
mylink_delete(head, p); /*将对应节点从链表摘下*/
free_node(p); /*释放节点*/
}

void err_qq(struct QQ_DATA_INFO *buf)
{
fprintf(stderr, "bad client %s connect \n", buf->srcname);
}

int main(void)
{
int server_fd; /*公共管道文件描述符(读端)*/
struct QQ_DATA_INFO dbuf; /*定义数据包结构体对象*/

if (access(SERVER_PROT, F_OK) != 0) { /*判断公有管道是否存在, 不存在则创建*/
mkfifo(SERVER_PROT, 0664);
}

if ((server_fd = open(SERVER_PROT, O_RDONLY)) < 0) /*服务器以只读方式打开公有管道一端*/
sys_err("open");

mylink_init(&head); /*初始化链表*/

while (1) {
read(server_fd, &dbuf, sizeof(dbuf)); /*读取公共管道,分析数据包,处理数据*/
switch (dbuf.protocal) {
case 1: login_qq(&dbuf, &head); break;
case 2: transfer_qq(&dbuf, &head); break;
case 4: logout_qq(&dbuf, &head); break;
default: err_qq(&dbuf);
}
}

close(server_fd);
}

qq_ipc_client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "qq_ipc.h"
#include "mylink.h"

#define SERVER_PROT "SEV_FIFO"

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

int main(int argc, char *argv[])
{
int server_fd, client_fd, flag, len;
struct QQ_DATA_INFO dbuf;
char cmdbuf[256];

if (argc < 2) {
printf("./client name\n");
exit(1);
}

if ((server_fd = open(SERVER_PROT, O_WRONLY)) < 0) /*客户端只写打开公共管道*/
sys_err("open");

mkfifo(argv[1], 0777); /*客户端登录时自己指定名称创建私有管道`*/

struct QQ_DATA_INFO cbuf, tmpbuf, talkbuf;

cbuf.protocal = 1; /*1号包表登录包*/
strcpy(cbuf.srcname,argv[1]); /*按既定设计结构,将登录者(自己的名字)写入包结构中*/
client_fd = open(argv[1], O_RDONLY|O_NONBLOCK); /*只读打开私有管道,修改私有管道的属性为非阻塞*/

flag = fcntl(STDIN_FILENO, F_GETFL); /*设置标准输入缓冲区的读写为非阻塞*/
flag |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, flag);

write(server_fd, &cbuf, sizeof(cbuf)); /*向公共管道中写入"登录包"数据,表示客户端登录*/

while (1) {
len = read(client_fd, &tmpbuf, sizeof(tmpbuf)); /*读私有管道*/
if (len > 0) {
if (tmpbuf.protocal == 3) { /*对方不在线*/
printf("%s is not online\n", tmpbuf.destname);
} else if (tmpbuf.protocal == 2) { /*显示对方对自己说的话*/
printf("%s : %s\n", tmpbuf.srcname, tmpbuf.data);
}
} else if (len < 0) {
if (errno != EAGAIN)
sys_err("client read");
}

len = read(STDIN_FILENO, cmdbuf, sizeof(cmdbuf)); /*读取客户端用户输入*/
if (len > 0) {
char *dname, *databuf;
memset(&talkbuf, 0, sizeof(talkbuf)); /*将存储聊天内容的缓存区清空*/
cmdbuf[len] = '\0'; /*填充字符串结束标记*/
//destname#data
//B#你好
dname = strtok(cmdbuf, "#\n"); /*按既定格式拆分字符串*/

if (strcmp("exit", dname) == 0) { /*退出登录:指定包号,退出者名字*/
talkbuf.protocal = 4;
strcpy(talkbuf.srcname, argv[1]);
write(server_fd, &talkbuf, sizeof(talkbuf));/*将退出登录包通过公共管道写给服务器*/
break;
} else {
talkbuf.protocal = 2; /*聊天*/
strcpy(talkbuf.destname, dname); /*填充聊天目标客户名*/
strcpy(talkbuf.srcname, argv[1]); /*填充发送聊天内容的用户名*/

databuf = strtok(NULL, "\0");
strcpy(talkbuf.data, databuf);
}
write(server_fd, &talkbuf, sizeof(talkbuf)); /*将聊天包写入公共管道*/
}
}

unlink(argv[1]); /*删除私有管道*/
close(client_fd); /*关闭私有管道的读端(客户端只掌握读端)*/
close(server_fd); /*关闭公共管道的写端(客户端值掌握写端)*/

return 0;
}

我的答案

qqserver.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#define PUBLIC_FIFO "./publicfifo"

struct package
{
char protocal[4]; // 协议号 4B
char src[4]; // 发送方 4B
char dest[4]; // 接收方 4B
char data[100]; // 数据 100B
}; // 包结构 112B

struct usrInfo
{
char num[4]; // 用户id
char name[20]; // 用户名
int online; // 是否在线 0-否 1-是
char password[20]; // 用户密码
}; // 用户信息

struct usrInfoNode
{
struct usrInfo usr;
struct usrInfoNode *next;
int fifo_fd; // fifo的文件描述符
} *usrInfoList, *node;

void release(void) // 终止处理函数
{
node = usrInfoList->next;
free(usrInfoList);
usrInfoList = node;
while (usrInfoList != NULL)
{
node = usrInfoList;
usrInfoList = usrInfoList->next;
close(node->fifo_fd); // 关闭管道
unlink(node->usr.num);
printf("%s over\n", node->usr.num);
free(node);
node = NULL;
}
printf("资源已被释放\n");
}

void catch_SIGINT(int sig)
{
exit(sig);
}

void err_int(int ret, char *tip)
{
if (ret == -1)
{
perror(tip);
exit(-1);
}
}

void err_pointer(void *ret, char *tip)
{
if (ret == NULL)
{
perror(tip);
exit(-1);
}
}

int handlePack(const struct package *pack);

int main()
{
int fd, ret_int;
ssize_t longint;

atexit(release); // 注册终止处理函数

struct sigaction act; // 捕捉SIGINT信号CTRL+C
memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = catch_SIGINT;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
act.sa_flags = 0;
sigaction(SIGINT, &act, NULL);

usrInfoList = malloc(sizeof(struct usrInfoNode));
err_pointer(usrInfoList, "malloc");
memset(usrInfoList, 0, sizeof(struct usrInfoNode));
node = NULL;

mkfifo(PUBLIC_FIFO, 0664); // 创建公共FIFO

fd = open(PUBLIC_FIFO, O_RDONLY); // 打开公共FIFO
err_int(fd, "open PUBLIC_FIFO");

//ret_int = unlink(PUBLIC_FIFO); // 在程序退出后删除FIFO文件

struct package recvpack;

while (1) {
memset(&recvpack, 0, sizeof(recvpack));
while((longint = read(fd, (void *)&recvpack, sizeof(recvpack))) == 0);
err_int(longint, "read public_fifo");
//printf("%ld\n", longint);
//printf("%s#%s#%s#%s\n", recvpack.protocal, recvpack.src, recvpack.dest, recvpack.data);

handlePack(&recvpack);
}

close(fd);

return 0;
}

int handlePack(const struct package *pack)
{
if (strcmp(pack->protocal, "1") == 0) // 登录包
{
node = malloc(sizeof(struct usrInfoNode));
err_pointer(node, "malloc");
memset(node, 0, sizeof(struct usrInfoNode));
strcpy(node->usr.num, pack->src);
strcpy(node->usr.name, pack->data); // 在num为1时,data为name+password(暂时不管password)
node->usr.online = 1;
mkfifo(node->usr.num, 0664); // 创建公共FIFO
node->fifo_fd = open(node->usr.num, O_WRONLY | O_NONBLOCK); // 打开命名管道的写端
err_int(node->fifo_fd, "fifo_fd open");
node->next = usrInfoList->next;
usrInfoList->next = node; // 添加到链表首部
node = NULL;
printf("%s login success \n", pack->src);
}
else if (strcmp(pack->protocal, "2") == 0) // 数据包
{
ssize_t longint;
node = usrInfoList->next;
while (node != NULL) // 向dest发送数据
{
if (strcmp(node->usr.num, pack->dest) == 0)
{
longint = write(node->fifo_fd, pack, sizeof(struct package));
break;
}
node = node->next;
}
if (node == NULL) // dest不存在(不在线或无此人)
{
node = usrInfoList->next;
while (node != NULL)
{
if (strcmp(node->usr.num, pack->src) == 0) // 向src发送协议号为3的包
{
struct package tmp;
memset(&tmp, 0, sizeof(tmp));
strcpy(tmp.protocal, "3"); // 客户不存在
strcpy(tmp.src, "SEV");
sprintf(tmp.data, "%s not online", pack->dest);
longint = write(node->fifo_fd, &tmp, sizeof(struct package));
break;
}
node = node->next;
}
}
node = NULL;
}
else if (strcmp(pack->protocal, "4") == 0) // 用户退出
{
node = usrInfoList;
while (node->next != NULL)
{
if (strcmp(node->next->usr.num, pack->src) == 0) // 找到用户列表中的src用户
{
struct usrInfoNode *tmp = node->next;
node->next = tmp->next;
close(tmp->fifo_fd);
unlink(tmp->usr.num);
free(tmp);
printf("%s exit\n", pack->src);
break;
}
node = node->next;
}
node = NULL;
}
else
{
printf("%s\n", pack->protocal);
return -1;
}
return 0;
}

qqclient.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

#define PUBLIC_FIFO "./publicfifo"

char uid[4] = {'\0'};
int public_fd, private_fd;

struct package
{
char protocal[4]; // 协议号 4B
char src[4]; // 发送方 4B
char dest[4]; // 接收方 4B
char data[100]; // 数据 100B
}; // 数据包 112B

void err_int(int ret, char *tip)
{
if (ret == -1)
{
perror(tip);
exit(-1);
}
}

void catch_SIGINT(int sig) // 捕捉ctrl+c
{
struct package sendpack;
strcpy(sendpack.protocal, "4");
strcpy(sendpack.src, uid);
write(public_fd, &sendpack, sizeof(sendpack));
exit(-1);
}

int handlePack(const struct package *pack);

int main(int argc, char *argv[])
{
int ret_int;
char buf[256];
struct package sendpack, recvpack;
char *ret_char;
ssize_t longint;

if (argc != 2)
{
printf("2 parameters : ./qqclient uid\n");
printf("000 <= uid <= 999");
printf("\n");
exit(-1);
}

system("stty erase ^H"); // 解决backspace键显示^H问题

strncpy(uid, argv[1], 3);

public_fd = open(PUBLIC_FIFO, O_WRONLY); // 公共FIFO写端
err_int(public_fd, "open public_fifo");
mkfifo(uid, 0664);
private_fd = open(uid, O_RDONLY | O_NONBLOCK); // 专用FIFO读端、非阻塞
err_int(private_fd, "open private_fifo");

int flags = fcntl(STDIN_FILENO, F_GETFL); // 设置标准输入缓冲区的读写为非阻塞
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

// 发送登录包
memset(&sendpack, 0, sizeof(struct package));
strcpy(sendpack.protocal, "1");
strcpy(sendpack.src, uid);
longint = write(public_fd, &sendpack, sizeof(sendpack));

struct sigaction act; // 捕捉SIGINT信号CTRL+C
memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = catch_SIGINT;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
act.sa_flags = 0;
sigaction(SIGINT, &act, NULL);

int flag = 1; // 控制 "%s > "的输出

while(1)
{
memset(&sendpack, 0, sizeof(struct package));
if (flag)
{
printf("%3s > ", uid);
fflush(stdout); //刷新输出缓冲区
flag = 0;
}

// 读取用户输入 格式以#分割
// 1)112#how are you? 向112发送数据
// 2)exit# 退出
longint = read(STDIN_FILENO, buf, sizeof(buf));
if (longint > 0) // 用户输入数据
{
if (buf[longint - 1] == '\n' || longint == 256)
{
buf[longint - 1] = '\0';
}
if (strcmp(buf, "exit#") == 0)
{
strcpy(sendpack.protocal, "4");
strcpy(sendpack.src, uid);
longint = write(public_fd, &sendpack, sizeof(sendpack));
exit(-1);
}
strcpy(sendpack.protocal, "2");
strcpy(sendpack.src, uid);
ret_char = strchr(buf, '#');
int index;
if (ret_char == NULL || (index = ret_char -buf) > 3)
{
printf("输入格式错误\n");
}
else
{
ret_char[0] = '\0';
strcpy(sendpack.dest, buf);
strcpy(sendpack.data, ret_char + 1);
longint = write(public_fd, &sendpack, sizeof(sendpack));
//printf("%ld\n", longint);
//printf("%s#%s#%s#%s\n", sendpack.protocal, sendpack.src, sendpack.dest, sendpack.data);
}
flag = 1;
}

memset(&recvpack, 0, sizeof(struct package));
longint = read(private_fd, &recvpack, sizeof(struct package)); // 读私有管道
if (longint > 0) // 收到包
{
handlePack(&recvpack); // 进行处理
flag = 1;
}
}

close(public_fd);
close(private_fd);
unlink(uid);
}

int handlePack(const struct package *pack)
{
time_t tnow = time(NULL); // 获取当前时间秒数
struct tm *stnow;
stnow = localtime(&tnow); // 转化成结构体
// 输出时间 格式 年-月-日 时:分
printf("\n%d-%d-%d %d:%d\t", stnow->tm_year+1900, stnow->tm_mon, stnow->tm_mday, stnow->tm_hour, stnow->tm_min);
printf("%s < %s\n", pack->src, pack->data);
return 0;
}

文件多线程拷贝

wrap.h

1
2
3
4
5
6
7
8
9
10
11
12
13
ssize_t Read(int fd, void *buf, size_t count);

ssize_t Write(int fd, const void *buf, size_t count);

int Stat(const char *pathname, struct stat *statbuf);

int Ftruncate(int fd, off_t length);

off_t Lseek(int fd, off_t offset, int whence);

void *Mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

int Munmap(void *addr, size_t length);

wrap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

#include "wrap.h"

ssize_t Read(int fd, void *buf, size_t count)
{
ssize_t n = read(fd, buf, count);
if (n == -1)
{
perror("read file error");
exit(-1);
}
return n;
}

ssize_t Write(int fd, const void *buf, size_t count)
{
ssize_t n = write(fd, buf, count);
if (n == -1)
{
perror("write file error");
exit(-1);
}
return n;
}

int Stat(const char *pathname, struct stat *statbuf)
{
int ret = stat(pathname, statbuf);
if (ret == -1)
{
perror("stat");
exit(-1);
}
return ret;
}

int Ftruncate(int fd, off_t length)
{
if (ftruncate(fd, length) == -1)
{
perror("ftruncate");
exit(-1);
}
return 0;
}


off_t Lseek(int fd, off_t offset, int whence)
{
off_t ret = lseek(fd, offset, whence);
if (ret == (off_t)-1)
{
perror("lseek");
exit(-1);
}
return ret;
}

void *Mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
void *ret = mmap(addr, length, prot, flags, fd, offset);
if (MAP_FAILED == ret)
{
perror("mmap error");
exit(-1);
}
return ret;
}

int Munmap(void *addr, size_t length)
{
int ret = munmap(addr, length);
if (ret == -1)
{
perror("munmap");
exit(-1);
}
return ret;
}

multThreadCopy.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#include <stdio.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#include "wrap.h"

struct pthreadArgs {
int num;
char *src;
char *dest;
size_t size;
off_t offset;
};

int subProcessNum = 8;

int Pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

int Pthread_detach(pthread_t thread);

int dirCopy(char* src, char* dest);

int fileCopy(char* src, char* dest);

void* pthreadStart(void *arg);

int main(int argc, char *argv[])
{
int srcfd, destfd;
struct stat srcStat, destStat;
memset(&srcStat, 0, sizeof(struct stat));
memset(&destStat, 0, sizeof(struct stat));

if (argc < 3)
{
printf("need 2 or 3 parameters: src dest [num]\n");
printf("- src : 源文件路径\n");
printf("- dest: 目的路径\n");
printf("- num : 指定进程数量5-20(可选,默认8)\n");
exit(-1);
}

if (argc == 4)
{
int num = atoi(argv[3]);
if (num > 20) num = 20;
if (num < 5) num = 5;
subProcessNum = num;
}

char* src = argv[1];
char* dest = argv[2];

srcfd = open(src, O_RDONLY); // 判断源文件是否存在
if (srcfd == -1)
{
perror(src);
printf("%d\n", __LINE__);
exit(-1);
}

// 判断src 是 文件 or 目录
memset(&srcStat, 0, sizeof(struct stat));
Stat(src, &srcStat);
if (S_ISDIR(srcStat.st_mode))
{
Stat(dest, &destStat);
if (!S_ISDIR(destStat.st_mode))
{
printf("%s is dir, then %s must be dir too\n", src, dest);
printf("%d\n", __LINE__);
exit(-1);
}
dirCopy(src, dest);
}
else
{
stat(dest, &destStat);
if ((stat(dest, &destStat) != -1) && S_ISDIR(destStat.st_mode)) // 是目录
{
// 提取文件名
char* filename = strrchr(src, '/');
// 文件名拼接路径
if (filename != NULL)
{
dest = strcat(dest, filename);
}
else
{
dest = strcat(dest, src);
}
}
fileCopy(src, dest);
}

return 0;
}

int dirCopy(char* src, char* dest)
{
printf("暂不支持目录复制\n");
return 1;
}

int fileCopy(char* src, char* dest)
{
struct stat srcStat, destStat;
off_t fileSize, subSize;
int n4k, subn4k;
long pageSize; // 页大小
memset(&srcStat, 0, sizeof(struct stat));
memset(&destStat, 0, sizeof(struct stat));

if (access(dest, F_OK) == 0)
{
char yorn = '\0';
printf("%s exists, do you want cover(Y|N): ", dest);
yorn = getchar();
if (yorn != 'Y' && yorn != 'y') exit(0);
}

pageSize = sysconf(_SC_PAGESIZE); // 获取页大小
Stat(src, &srcStat);
fileSize = srcStat.st_size; // 获取源文件大小

int destfd = open(dest, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (destfd == -1)
{
perror("open destfd");
printf("%d\n", __LINE__);
exit(-1);
}
Ftruncate(destfd, fileSize); // 设置目的文件大小

n4k = fileSize / pageSize; // fileSize中有多少个4k
subn4k = n4k / subProcessNum; // 每个进程几个4k

for(int i = 0; i < subProcessNum; ++i)
{
subSize = subn4k * pageSize;
if (i == subProcessNum - 1)
{
subSize += (n4k % subProcessNum) * pageSize + fileSize % pageSize;
}
struct pthreadArgs *args = malloc(sizeof( struct pthreadArgs));
args->offset = pageSize * subn4k * i;
args->size = subSize;
args->src = src;
args->dest = dest;
args->num = i + 1;

pthread_t pthreadid;
pthread_create(&pthreadid, NULL, pthreadStart, (void*)args);
pthread_detach(pthreadid);
}

pthread_exit(NULL);
}

void* pthreadStart(void *arg)
{
char* src = ((struct pthreadArgs*)arg)->src;
char* dest = ((struct pthreadArgs*)arg)->dest;
size_t size = ((struct pthreadArgs*)arg)->size;
size_t offset = ((struct pthreadArgs*)arg)->offset;
int num = ((struct pthreadArgs*)arg)->num;

int srcfd, destfd;
srcfd = open(src, O_RDONLY);
destfd = open(dest, O_RDWR);

char* srcmap = Mmap(NULL, size, PROT_READ, MAP_SHARED, srcfd, offset);
char* destmap = Mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, destfd, offset);
close(srcfd);
close(destfd);

strncpy(destmap, srcmap, size);

Munmap(srcmap, size);
Munmap(destmap, size);

free(arg);

printf("%d pthread over %dB\n", num, size);
pthread_exit(NULL);
}

int Pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
{
int ret = pthread_create(thread, attr, start_routine, arg);
if (ret != 0)
{
perror("pthread_create");
exit(-1);
}
return ret;
}

int Pthread_detach(pthread_t thread)
{
int ret = pthread_detach(thread);
if (ret != 0)
{
perror("pthread_detach");
exit(-1);
}
return ret;
}

欢迎关注我的其它发布渠道