前言
师弟高兴的说道:“师兄,你猜我今天上课看见谁了?”
我:“谁呢?”
师弟:“程夏,没想到,她竟然来旁听我们计算机系的课程了。虽然我从前门进去的,但是我还是一眼就看见了坐在后面的她。”
我:“有意思,你没过去打个招呼?”
师弟:“我正要挨过去坐着,被占位的室友拉住了,就不好意思了。还好第一节课间休息的时候,我果断溜了过去。”
师弟开始回想当时的场景,嘴角先是上扬,接着又下扬。当时她在忙着看书,等我在她旁边坐下,稍微撇过头看她看什么书的时候,她才发现的我。她说:‘上课都不积极,踩着点来上课。’我先是惊讶了,然后不好意思地说:‘程老师批评的对。’接下来竟是一段沉默。打破沉默的还是她,她说道:‘你现在的学生成绩管理系统只支持运行时候,在运行的程序终端进行输入。你能够支持一下,我在我的电脑上,也能连上你的系统设置么?’我听得有点懵,我从来没想到还能这样实现。我原以为我的系统已经可以了。她见我茫然,就说:‘可以好好想想。期待你更新的版本。’说完她就自顾看书去了。我则一整节课都在思考这个问题。
师弟:“师兄,她说要实现在她电脑上,也能连上我的系统,这个有经验可以分享一下么?”
我:“这个倒是不复杂,就是c/s架构,客户端和服务端分离。可以通过c语言中的网络套接字实现。”
背景
在C语言中,可以使用网络套接字(Socket)来实现基于网络的进程间通信。可以先看下面TCP通信的服务端和客户端代码。主要实现的功能是客户端发送一个“Hello message sent”,服务端回复一个“Hello from server”。
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080int main() {int server_fd, conn_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};char *hello = "Hello from server";// Creating socket file descriptorif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// Forcefully attaching socket to the port 8080if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// Bind the socket to the addressif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// Listen for connectionsif (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}// Accept a connectionif ((conn_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// Read data from the socketread(conn_socket, buffer, 1024);printf("Message from client: %s\n", buffer);// Send data to the clientsend(conn_socket, hello, strlen(hello), 0);printf("Hello message sent\n");close(conn_socket);close(server_fd);return 0;
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>#define PORT 8080int main() {int sock = 0;struct sockaddr_in serv_addr;char *hello = "Hello from client";char buffer[1024] = {0};// Create a socketif ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// Convert IPv4 and IPv6 addresses from text to binary formif(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {printf("\nInvalid address/ Address not supported \n");return -1;}// Connect to the serverif (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}// Send data to the serversend(sock, hello, strlen(hello), 0);printf("Hello message sent\n");// Read data from the serverread(sock, buffer, 1024);printf("Message from server: %s\n", buffer);close(sock);return 0;
}
整个编程实现的过程可以总结如下:
学生成绩管理系统的实现
这里主要是考虑将上文中的客户端和服务端代码如何在学生系统中实现。其中的关键就是socket_server_init,实现了服务端的监听,以及数据的处理。
int socket_server_init()
{int ret;int server_fd, conn_fd;int opt = 1;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUF_SIZE] = {0};// 创建 TCP socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed!");exit(EXIT_FAILURE);}if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt!");exit(EXIT_FAILURE);}#define PORT 8080address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// Bind the socket to the addressif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed!");exit(EXIT_FAILURE);}// Listen for connectionsif (listen(server_fd, 3) < 0) {perror("listen!");exit(EXIT_FAILURE);}while (1) {// Accept a connectionif ((conn_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}while (1) {// Send data to the clientret = send(conn_fd, welcome_info, strlen(welcome_info), 0);if (ret == -1) {perror("sent failed!\n");break;}// Read data from the socketret = read(conn_fd, buffer, BUF_SIZE);if (ret == -1) {perror("read failed!\n");break;}printf("recv: %s\n", buffer);ret = server_process_to_client(conn_fd, buffer);if (ret == 1) {break;}}close(conn_fd);}close(server_fd);return 0;
}
完整服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for access() function
#include <sys/socket.h>
#include <netinet/in.h>#define MAX_STUDENTS 100
#define MAX_NAME_LEN 50
#define STUDENT_SYSTEM "student_system"
#define TRUE 1
#define FALSE 0
#define BUF_SIZE 1024char *welcome_info = "\nWelcome to Student Score Management System\n"\"0. Exit\n"\"1. Add Student\n"\"2. Display All Students\n"\"3. Find Student by ID\n"\"4. Find Student by Name\n"\"5. Add Score\n"\"6. Display Average Score\n"\"7. Display by Score sort\n"\"Enter your choice: ";typedef struct {int id; // 学号char name[MAX_NAME_LEN]; // 姓名float score; // 成绩
} Student;int student_count = 0; // 学生数量Student *students; // 学生数组指针
int g_max_student = MAX_STUDENTS;Student *stu_sys_init(int num)
{Student *stu_sys;stu_sys = malloc(num * sizeof(Student));if (stu_sys == NULL) {printf("student system malloc failed!\n");}return stu_sys;
}int write_student_info(Student *s)
{FILE *fp = fopen(STUDENT_SYSTEM, "a");if (fp == NULL) {printf("fopen student_system failed!\n");return 1;}fprintf(fp, "%-4d %-10s %-.2f\n", s->id, s->name, s->score);fclose(fp);return 0;
}int check_if_student_exsit(int id)
{int i;for(i = 0; i < student_count; i++) {if(students[i].id == id) {return 1;}}return 0;
}void update_student_info(Student s, int need_write)
{Student *stu_reinit;int old_max_student;if(student_count >= g_max_student) {old_max_student = g_max_student;g_max_student = g_max_student << 1;stu_reinit = stu_sys_init(g_max_student);if (stu_reinit == NULL) {printf("Database is full!\n");return;}memcpy(stu_reinit, students, old_max_student * sizeof(Student));free(students);students = stu_reinit;}if (!check_if_student_exsit(s.id)) {students[student_count++] = s;if (need_write) {printf("Student added successfully, all student: %d!\n", student_count);write_student_info(&s);}} else {printf("student has in db, do nothing!\n");}
}void add_student()
{Student s;printf("Enter student ID: ");scanf("%d", &s.id);printf("Enter student name: ");scanf("%s", s.name);s.score = 0.0; // 初始成绩设置为0update_student_info(s, TRUE);
}void print_title()
{printf("%-4s %-10s %-5s\n", "ID", "Name", "Score");
}void display_all_students()
{int i;printf("-------- All students info --------\n");if (student_count == 0) {printf("No students!\n");} else {print_title();for(i = 0; i < student_count; i++) {printf("%-4d %-10s %-.2f\n", students[i].id, students[i].name, students[i].score);}}printf("-------- End -----------\n");
}/*** @arr: 有序数组* @l: 待查找区间左端点* @r: 待查找区间右端点* @target: 需要查找的元素*/
int binary_search(Student *s, int l, int r, int target)
{int m;while (l <= r) {// 计算中间位置m = l + (r - l) / 2; // 防止(l+r)直接相加导致的溢出// 检查x是否存在于中间位置if (s[m].id == target)return m;// 若x大,则忽略左半部分if (s[m].id < target) {l = m + 1;} else {// 若x小,则忽略右半部分r = m - 1;}}// 若未找到元素,返回-1return -1;
}void find_student_by_id()
{int id, ret;printf("Enter student ID to search: ");scanf("%d", &id);ret = binary_search(students, 0, student_count, id);if (ret != -1) {print_title();printf("%-4d %-10s %-.2f\n", students[ret].id, students[ret].name, students[ret].score);return;}printf("Student with ID %d not found!\n", id);
}void find_student_by_name()
{int i, is_find = 0;char name[MAX_NAME_LEN];printf("Enter student name to search: ");scanf("%s", name);for(i = 0; i < student_count; i++) {if(strcmp(students[i].name, name) == 0) {print_title();printf("%-4d %-10s %-.2f\n", students[i].id, students[i].name, students[i].score);is_find = 1;}}if (is_find == 0) {printf("Student with name %s not found!\n", name);}
}void add_score()
{int id, i;float score;printf("Enter student ID: ");scanf("%d", &id);printf("Enter student score: ");scanf("%f", &score);for(i = 0; i < student_count; i++) {if(students[i].id == id) {students[i].score = score;printf("Score added successfully!\n");return;}}printf("Student with ID %d not found!\n", id);
}void display_average_score()
{float total = 0.0;int i;for(i = 0; i < student_count; i++) {total += students[i].score;}printf("Average score of all students: %.2f\n", total / student_count);
}int init_student_info()
{if(access(STUDENT_SYSTEM, F_OK) != 0) { // 文件不存在return 0;}FILE *fp = fopen(STUDENT_SYSTEM, "r");if (fp == NULL) {printf("fopen student_system failed!\n");return 1;}#define BUF_SIZE 1024char buf[BUF_SIZE];Student s;while(fgets(buf, BUF_SIZE - 1, fp) != NULL) {sscanf(buf, "%d %s %f\n", &s.id, s.name, &s.score);update_student_info(s, FALSE);}fclose(fp);return 0;}void swap(Student *a, Student *b)
{Student tmp = *a;*a = *b;*b = tmp;
}void bubble_sort_by_score(Student *s, int n)
{int i, j;for (i = 0; i < n-1; i++) {for (j = 0; j < n-i-1; j++) { // 最后 i 个已经排序好了, 遍历未排序的部分if (s[j].score < s[j+1].score) {// 如果当前元素大于后面的元素,交换它们swap(&s[j], &s[j+1]);}}}
}int do_process(int choice)
{switch(choice) {case 0:printf("Exiting...\n");break;case 1:add_student();break;case 2:display_all_students();break;case 3:find_student_by_id();break;case 4:find_student_by_name();break;case 5:add_score();break;case 6:display_average_score();break;case 7:bubble_sort_by_score(students, student_count);display_all_students();break;default:printf("Invalid choice!\n");}return 0;
}int do_exit(int fd)
{int ret;char *exit_instruct = "Exit";ret = send(fd, exit_instruct, strlen(exit_instruct), 0);if (ret == -1) {perror("sent failed!\n");return -1;}printf("send: %s\n", exit_instruct);return 0;
}int do_add_student(int fd)
{int ret;char *enter_id_helps = "Enter student ID:";char *name_helps = "Enter student name:";Student s;char buf[BUF_SIZE];ret = send(fd, enter_id_helps, strlen(enter_id_helps), 0);if (ret == -1) {perror("sent failed!\n");return -1;}printf("send: %s\n", enter_id_helps);// Read data from the socketret = read(fd, buf, BUF_SIZE);if (ret == -1) {perror("read failed!\n");return -1;}s.id = atoi(buf);ret = send(fd, name_helps, strlen(name_helps), 0);if (ret == -1) {perror("sent failed!\n");return -1;}// Read data from the socketmemset(buf, 0, BUF_SIZE);ret = read(fd, buf, BUF_SIZE);if (ret == -1) {perror("read failed!\n");return -1;}strcpy(s.name, buf);s.score = 0.0; // 初始成绩设置为0update_student_info(s, TRUE);return 0;
}int server_process_to_client(int fd, char *buffer)
{int choice = atoi(buffer);switch(choice) {case 0:do_exit(fd);return 1;case 1:do_add_student(fd);break;default:printf("Invalid choice!\n");}return 0;
}int socket_server_init()
{int ret;int server_fd, conn_fd;int opt = 1;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUF_SIZE] = {0};// 创建 TCP socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed!");exit(EXIT_FAILURE);}if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt!");exit(EXIT_FAILURE);}#define PORT 8080address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// Bind the socket to the addressif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed!");exit(EXIT_FAILURE);}// Listen for connectionsif (listen(server_fd, 3) < 0) {perror("listen!");exit(EXIT_FAILURE);}while (1) {// Accept a connectionif ((conn_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}while (1) {// Send data to the clientret = send(conn_fd, welcome_info, strlen(welcome_info), 0);if (ret == -1) {perror("sent failed!\n");break;}// Read data from the socketret = read(conn_fd, buffer, BUF_SIZE);if (ret == -1) {perror("read failed!\n");break;}printf("recv: %s\n", buffer);ret = server_process_to_client(conn_fd, buffer);if (ret == 1) {break;}}close(conn_fd);}close(server_fd);return 0;
}void welcome_sys_menu()
{printf("%s", welcome_info);
}int main()
{int choice;int ret;students = stu_sys_init(MAX_STUDENTS);if (students == NULL) {printf("student system init failed, exit!\n");return -1;}ret = init_student_info();if (ret) {printf("init_student_info failed!\n");return 1;}display_all_students();socket_server_init();#if 0 //通过if 0注释代码do {welcome_sys_menu();scanf("%d", &choice);do_process(choice);} while(choice != 0);
#endifreturn 0;
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>#define PORT 8080
#define BUF_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUF_SIZE] = {0};// Create a socketif ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("Socket creation error!\n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// Convert IPv4 and IPv6 addresses from text to binary formif(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { //127.0.0.1需要换成服务器所在地址printf("Invalid address/ Address not supported!\n");return -1;}// Connect to the serverif (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("Connection Failed!\n");return -1;}while (1) {memset(buffer, 0, BUF_SIZE);// Read data from the serverread(sock, buffer, BUF_SIZE);printf("%s", buffer);if (strcmp(buffer, "Exit") == 0) {printf("\nNormal Exit, bye!\n");break;}// Send data to the serverscanf("%s", buffer);send(sock, buffer, strlen(buffer), 0);}close(sock);return 0;
}
在实际应用中127.0.0.1需要换成服务器所在地址。
结尾
在这里只是实现了学生成绩管理系统的添加功能和退出功能,其他功能如显示、查询功能有待进一步实现。你以为这就构造好了一个学生成绩管理系统?其实还可以有很多的工作可以做。
我:“你都被提问了这么久,你怎么没给人家出个编程题试试呢?”
师弟:“也对哦,我的思想似乎总是转的比较慢。我先去问问,她最近是不是也在鼓捣什么系统。”