大二上某科技大学某海滨老师布置的最烦人的linux实验,任务要求使用C语言编程进行程序交互,(本来C语言教的课时就短,连指针都没有教),就让用C写这么高难度的代码(对我们来说),对想拿高分的同学太不友好了。在临近期末的时候,用了一个周,硬生生地学了C和进程间通讯,硬给它肝出来了,呜呜呜。现在大三,当时学的啥一点都不记得了,只有痛苦的回忆。
代码供学弟学妹和感兴趣的人参考。前人栽树后人乘凉了属于是。
一、实验目的:
- 掌握Linux环境下进程的创建函数;
- 掌握Linux环境下进程间通讯技术。
二、实验内容:
采用C语言编程,设计结构体:学生,包含姓名,性别,年龄,班级信息。设计两个程序,一个程序负责提供命令交互,另一个程序接收命令,实现对于文件的读写,对students
文件进行维护和查询。两者间通讯方式自己设计。实现的功能包括:
- 录入:可以添加一个学生信息
- 查询:可以根据姓名查询并显示
- 删除:可以根据姓名删除一个学生
- 排序:可以根据姓名或班级进行排序,并重新写入文件
- 统计:可以统计学生人数,以及性别分别为男生或女生的人数。
三、实验环境:
Ubuntu系统
四、实验原理:
-
FIFO (命名管道) 的使用:
Client和Server程序通过一个命名的管道(FIFO)进行通信。FIFO相当于一个特殊的文件,允许一个进程向其中写入数据,而另一个进程从中读取数据。
在本实验中,Client程序将用户的命令写入FIFO,而Server程序从FIFO读取这些命令并进行处理。 -
Client程序的命令输入和发送:
Client程序提供一个简单的命令行界面,让用户输入命令,如添加、删除、查找学生等。
输入的命令通过sendCommand
函数发送给Server。该函数使用open、write和close系统调用来操作FIFO。 -
Server程序的命令接收和处理:
Server程序使用open、read、close系统调用从FIFO读取命令。
收到命令后,Server根据命令类型(如add、find、delete等)执行相应的操作。 -
结构体的应用:
使用Student结构体来组织和存储学生信息,包括姓名、性别、年龄和班级。
在添加学生信息时,Server程序会创建一个新的Student结构体实例,并将其写入文件。 -
文件的读写:
Server程序使用标准的文件I/O操作(如fopen、fprintf、fscanf、fclose)来维护学生信息。
学生信息存储在一个名为students.txt
的文本文件中,每行代表一个学生的信息。
#students.txt存储结构如下
niu,Male,13,2b
wangwu,Male,23,2b
zhangsan,Female,12,3b
su,Male,21,4b
五、实验步骤:
-
编写Client程序:
实现命令行界面,接收用户输入的命令。
使用sendCommand
函数将命令通过FIFO发送给Server。 -
编写Server程序:
循环等待并读取从FIFO传来的命令。
根据命令类型执行对应的操作,如添加、查找、删除学生等。 -
编译程序:
使用GCC编译器分别编译Client和Server程序。 -
运行程序:
先运行Server程序,然后运行Client程序。
在Client程序中测试不同命令,观察并验证Server的响应。 -
调试和优化:
根据运行结果对程序进行调试和优化。
确保程序能正确处理各种输入,并且能稳定运行。
六、实验结果:
对于每个命令(如add、find、delete等),提供Client和Server程序的截图,展示命令的输入和相应的输出。
1、先打开两个终端,分别使用./Client
和./Server
命令执行交互程序。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5eb147ceefe74738878d3c8e7a500346.png)
2、对于add命令:
先使用cat
命令查看students.txt
文件中的学生信息
正常的输入:
执行./Client
的终端
执行./Server
的终端,屏幕打印“Add success”
查看添加后的students.txt
文件
异常输入:
当输入的命令不符合要求时,执行./Server
命令的终端会给出相应的提示
若只输入一个add
:
执行./Client
的终端
执行./Server
命令的终端显示“Incomplete add command.”
3、对于find命令:
正常的输入:
执行./Client
的终端
执行./Server
的终端
异常输入:
当输入的命令不符合要求时,执行./Server命令
的终端会给出相应的提示,若只输入一个find
:
执行./Client
的终端
执行./Server
的终端,显示“No name provided for find command.”
4、对于delete命令
正常的输入:
先使用cat
命令查看students.txt
文件的数据:
使用delete
命令进行学生的删除:
执行./Client
的终端
执行./Server
的终端,屏幕打印“Student deleted”
查看删除后的students.txt
文件
异常输入:
当输入的命令不符合要求时,执行./Server
命令的终端会给出相应的提示, 若只输入一个delete
:
执行./Client
的终端
执行./Server
的终端显示“No name provided for delete command.”
5、对于sort命令:
查看排序前的students.txt
文件
在执行./Client
命令的终端上输入sort
后,会出现‘name’与‘class’的选项,让用户选择排序的方式。此图为按照class排序
执行./Server
的终端,屏幕打印“Sort success”
按照class选项排序的结果如下:
可以看出,按照班级进行了升序排序
按照name排序:
执行./Client
的终端
执行./Server
的终端,屏幕打印“Sort success”
按照name选项排序的结果如下:
可以看出,按照姓名的首字母进行了升序排序
6、对于count命令:
执行./Client
的终端
执行./Server
的终端,屏幕打印“Total students:4,Male:3,Female:1”
7、对于exit命令:
执行./Client
的终端
执行./Server
的终端,屏幕打印“Goodbye!”
8、若命令输入错误:
执行./Client
的终端
执行./Server
命令的终端显示“Unknown command: abc”
七、实验总结与分析:
实验收获:
-
进程间通信理解:深入了解了Linux下的进程间通信机制,特别是命名管道(FIFO)。理解了如何创建和使用FIFO以及其在进程间通信中的作用。
-
C语言文件操作应用:掌握了使用C语言进行文件读写的方法,包括打开文件、读取文件、写入文件以及关闭文件的操作。这在管理学生信息的存储和检索方面非常关键。
-
结构化数据管理:通过设计Student结构体,实践了如何使用结构体来组织和管理复杂数据。这增强了对C语言数据结构的理解。
遇到的问题及解决办法:
1、FIFO的同步问题:初期在Client和Server之间通信时遇到了数据同步的问题。通过在Server端正确地打开和关闭FIFO,以及在Client端发送完命令后进行适当的等待,解决了这一问题。
2、内存管理:在处理动态分配的内存时遇到了挑战,特别是在排序功能中。通过使用malloc和realloc以及确保及时释放内存,解决了内存泄露的问题。
3、字符串处理的错误:最初在解析从FIFO读取的字符串时遇到了一些问题,如段错误。这是因为错误地处理了strtok的返回值。通过添加对strtok返回值的检查,确保了程序的稳定性。
实验心得:
本实验不仅加深了对Linux系统编程的理解,特别是在进程间通信方面,还提高了对C语言的掌握程度,特别是在文件操作和数据结构管理方面。
八、附录:
Server.c的关键代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FIFO_FILE "myfifo"
#define STUDENT_FILE "students.txt"
// 学生结构定义
typedef struct {
char name[50];
char gender[10];
int age;
char class[50];
} Student;
// 函数声明
void addStudent(Student student);
void findStudent(char* name);
void deleteStudent(char* name);
void sortStudents();
void countStudents(int* male, int* female);
int main() {
char buffer[1024];
int fd;
// 检查FIFO是否已经存在
if (access(FIFO_FILE, F_OK) == -1) {
if (mkfifo(FIFO_FILE, 0666) == -1) {
perror("Error creating FIFO");
return 1;
}
}
while (1) {
// 从FIFO读取命令
fd = open(FIFO_FILE, O_RDONLY);
if (fd == -1) {
perror("Error opening FIFO");
continue;
}
ssize_t numRead = read(fd, buffer, sizeof(buffer));
close(fd);
if (numRead <= 0) {
perror("Error reading from FIFO");
continue;
}
buffer[numRead] = '\0'; // 确保字符串正确终止
char* command = strtok(buffer, "|");
if (command == NULL) {
fprintf(stderr, "Invalid command received.\n");
continue;
}
// 命令处理逻辑
if (strcmp(command, "exit") == 0) {
printf("Goodbye!\n");
break;
} else if (strcmp(command, "add") == 0) {// ... [添加学生的逻辑]
Student s;
char *name = strtok(NULL, "|");
char *gender = strtok(NULL, "|");
char *ageStr = strtok(NULL, "|");
char *class = strtok(NULL, "|");
if (name == NULL || gender == NULL || ageStr == NULL || class == NULL) {
fprintf(stderr, "Incomplete add command.\n");
continue;
}
strcpy(s.name, name);
strcpy(s.gender, gender);
s.age = atoi(ageStr);
strcpy(s.class, class);
addStudent(s);
} else if (strcmp(command, "find") == 0) {//查找学生
char* name = strtok(NULL, "|");
findStudent(name);
} else if (strcmp(command, "delete") == 0) { //删除学生
char* name = strtok(NULL, "|");
deleteStudent(name);
} else if (strcmp(command, "sort") == 0) {//排序学生
char* sortBy = strtok(NULL, "|");
if (sortBy != NULL) {
sortStudents(sortBy);
} else {
fprintf(stderr, "No sort criteria specified.\n");
}
} else if (strcmp(command, "count") == 0) {//统计学生
int male = 0, female = 0;
countStudents(&male, &female);
printf("Total students: %d, Male: %d, Female: %d\n", male + female, male, female);
}else {
fprintf(stderr, "Unknown command: %s\n", command);
}
}
// 清除FIFO文件
unlink(FIFO_FILE);
return 0;
}
void addStudent(Student student) {
FILE* file = fopen(STUDENT_FILE, "a");
if (file == NULL) {
perror("Error opening file");
return;
}
fprintf(file, "%s,%s,%d,%s\n", student.name, student.gender, student.age, student.class);
printf("Add success!\n");
fclose(file);
}
void findStudent(char* name) {
if (name == NULL) {
fprintf(stderr, "No name provided for find command.\n");
return;
}
FILE* file = fopen(STUDENT_FILE, "r");
char line[1024];
int found = 0;
if (file == NULL) {
perror("Error opening file");
return;
}
while (fgets(line, sizeof(line), file)) {
char tempLine[1024]; // 创建临时字符串用于复制line,因为strtok会修改原字符串
strcpy(tempLine, line);
char* token = strtok(tempLine, ",");
if (token != NULL && strcmp(token, name) == 0) {
found = 1;
break; // 找到匹配的学生,停止循环
}
}
fclose(file);
if (!found) {
printf("Student not found.\n");
} else {
// 打印找到的学生的全部信息
char* token;
int field = 0;
token = strtok(line, ",");
while (token != NULL) {
switch (field) {
case 0: printf("Name: %s\n", token); break;
case 1: printf("Gender: %s\n", token); break;
case 2: printf("Age: %s\n", token); break;
case 3: printf("Class: %s\n", token); break;
}
token = strtok(NULL, ",");
field++;
}
}
}
void deleteStudent(char* name) {
if (name == NULL) {
fprintf(stderr, "No name provided for delete command.\n");
return;
}
FILE* file = fopen(STUDENT_FILE, "r");
FILE* tempFile = fopen("temp.txt", "w");
char line[1024];
int found = 0;
if (file == NULL || tempFile == NULL) {
perror("Error opening file");
if (file != NULL) fclose(file);
if (tempFile != NULL) fclose(tempFile);
return;
}
while (fgets(line, sizeof(line), file)) {
char tempLine[1024]; // 为了保护原始line字符串,使用临时字符串
strcpy(tempLine, line);
char* token = strtok(tempLine, ",");
// 仅当当前行的姓名与要删除的姓名不匹配时,才将其写入临时文件
if (token == NULL || strcmp(token, name) != 0) {
fputs(line, tempFile);
} else {
found = 1;
}
}
fclose(file);
fclose(tempFile);
if (found) {
remove(STUDENT_FILE);
rename("temp.txt", STUDENT_FILE);
printf("Student deleted\n");
} else {
printf("Student not found\n");
remove("temp.txt"); // 删除未使用的临时文件
}
}
// 比较函数,用于qsort
int compareStudentsByName(const void* s1, const void* s2) {
const Student *student1 = (const Student*)s1;
const Student *student2 = (const Student*)s2;
return strcmp(student1->name, student2->name);
}
int compareStudentsByClass(const void* s1, const void* s2) {
const Student *student1 = (const Student*)s1;
const Student *student2 = (const Student*)s2;
return strcmp(student1->class, student2->class);
}
// 重新实现的sortStudents函数
void sortStudents(const char* sortBy) {
FILE *file = fopen(STUDENT_FILE, "r");
Student *students = NULL;
int size = 0, capacity = 10;
if (file == NULL) {
perror("Error opening file");
return;
}
// 动态分配内存
students = malloc(capacity * sizeof(Student));
if (students == NULL) {
perror("Memory allocation failed");
fclose(file);
return;
}
// 读取文件内容到动态数组
while (!feof(file)) {
if (size == capacity) {
capacity *= 2;
students = realloc(students, capacity * sizeof(Student));
if (students == NULL) {
perror("Memory reallocation failed");
break;
}
}
fscanf(file, "%[^,],%[^,],%d,%[^\n]\n", students[size].name, students[size].gender, &students[size].age, students[size].class);
size++;
}
fclose(file);
// 使用qsort排序
if (strcmp(sortBy, "name") == 0) {
qsort(students, size, sizeof(Student), compareStudentsByName);
} else if (strcmp(sortBy, "class") == 0) {
qsort(students, size, sizeof(Student), compareStudentsByClass);
}
// 将排序后的数据写回文件
file = fopen(STUDENT_FILE, "w");
for (int i = 0; i < size; i++) {
fprintf(file, "%s,%s,%d,%s\n", students[i].name, students[i].gender, students[i].age, students[i].class);
}
printf("Sort success\n");
fclose(file);
free(students);
}
void countStudents(int* male, int* female) {
FILE* file = fopen(STUDENT_FILE, "r");
char line[1024];
*male = 0;
*female = 0;
if (file == NULL) {
perror("Error opening file");
return;
}
while (fgets(line, sizeof(line), file)) {
char* gender = strtok(line, ",");
gender = strtok(NULL, ","); // 第二个字段是性别
if (gender != NULL && strcmp(gender, "Male") == 0) {
(*male)++;
} else if (gender != NULL && strcmp(gender, "Female") == 0) {
(*female)++;
}
}
fclose(file);
}
Client.c
程序关键代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_FILE "myfifo"
// 向FIFO发送命令的函数
void sendCommand(const char* command) {
int fd = open(FIFO_FILE, O_WRONLY);
if (fd == -1) {
perror("Error opening FIFO");
return;
}
if (write(fd, command, strlen(command) + 1) == -1) {
perror("Error writing to FIFO");
}
close(fd);
}
// 打印用户可用命令的菜单
void printMenu() {
printf("============================================================\n");
printf("Available commands:\n");
printf(" add - Add a new student (add|name|gender|age|class)\n");
printf(" find - Find a student by name (find|name)\n");
printf(" delete - Delete a student by name (delete|name)\n");
printf(" sort - Sort students\n");
printf(" count - Count students\n");
printf(" exit - Exit the program\n");
printf("============================================================\n");
printf("Enter your command: ");
}
int main() {
char command[1024];
// 检查FIFO是否已经存在
if (access(FIFO_FILE, F_OK) == -1) {
if (mkfifo(FIFO_FILE, 0666) == -1) {
perror("Error creating FIFO");
return 1;
}
}
while (1) {
printMenu();
fgets(command, sizeof(command), stdin);
command[strcspn(command, "\n")] = 0; // 去除换行符
if (strcmp(command, "exit") == 0) {
sendCommand(command);
break;// 如果输入的命令是exit,则退出程序
} else if (strcmp(command, "sort") == 0) {
// 如果命令是sort,则询问用户排序依据
char sortCommand[1024];
printf("Sort by 'name' or 'class'? ");
fgets(sortCommand, sizeof(sortCommand), stdin);
sortCommand[strcspn(sortCommand, "\n")] = 0;
// 将排序依据添加到命令中
strcat(command, "|");
strcat(command, sortCommand);
sendCommand(command);
} else {
sendCommand(command);
}
}
// 程序结束时删除FIFO文件
unlink(FIFO_FILE);
return 0;
}