前言
师弟高兴的说道:“师兄,你猜我今天上课看见谁了?”
我:“谁呢?”
师弟:“程夏,没想到,她竟然来旁听我们计算机系的课程了。虽然我从前门进去的,但是我还是一眼就看见了坐在后面的她。”
我:“有意思,你没过去打个招呼?”
师弟:“我正要挨过去坐着,被占位的室友拉住了,就不好意思了。还好第一节课间休息的时候,我果断溜了过去。”
师弟开始回想当时的场景,嘴角先是上扬,接着又下扬。当时她在忙着看书,等我在她旁边坐下,稍微撇过头看她看什么书的时候,她才发现的我。她说:‘上课都不积极,踩着点来上课。’我先是惊讶了,然后不好意思地说:‘程老师批评的对。’接下来竟是一段沉默。打破沉默的还是她,她说道:‘你现在的学生成绩管理系统只支持运行时候,在运行的程序终端进行输入。你能够支持一下,我在我的电脑上,也能连上你的系统设置么?’我听得有点懵,我从来没想到还能这样实现。我原以为我的系统已经可以了。她见我茫然,就说:‘可以好好想想。期待你更新的版本。’说完她就自顾看书去了。我则一整节课都在思考这个问题。
师弟:“师兄,她说要实现在她电脑上,也能连上我的系统,这个有经验可以分享一下么?”
我:“这个倒是不复杂,就是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 8080
int 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 descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to the port 8080
if (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 address
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// Listen for connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// Accept a connection
if ((conn_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// Read data from the socket
read(conn_socket, buffer, 1024);
printf("Message from client: %s\n", buffer);
// Send data to the client
send(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 8080
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[1024] = {0};
// Create a socket
if ((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 form
if(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 server
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// Send data to the server
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// Read data from the server
read(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 socket
if ((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 8080
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Bind the socket to the address
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed!");
exit(EXIT_FAILURE);
}
// Listen for connections
if (listen(server_fd, 3) < 0) {
perror("listen!");
exit(EXIT_FAILURE);
}
while (1) {
// Accept a connection
if ((conn_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
while (1) {
// Send data to the client
ret = send(conn_fd, welcome_info, strlen(welcome_info), 0);
if (ret == -1) {
perror("sent failed!\n");
break;
}
// Read data from the socket
ret = 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 1024
char *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; // 初始成绩设置为0
update_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;
}
}
// 若未找到元素,返回-1
return -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 1024
char 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 socket
ret = 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 socket
memset(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; // 初始成绩设置为0
update_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 socket
if ((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 8080
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Bind the socket to the address
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed!");
exit(EXIT_FAILURE);
}
// Listen for connections
if (listen(server_fd, 3) < 0) {
perror("listen!");
exit(EXIT_FAILURE);
}
while (1) {
// Accept a connection
if ((conn_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
while (1) {
// Send data to the client
ret = send(conn_fd, welcome_info, strlen(welcome_info), 0);
if (ret == -1) {
perror("sent failed!\n");
break;
}
// Read data from the socket
ret = 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);
#endif
return 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 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUF_SIZE] = {0};
// Create a socket
if ((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 form
if(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 server
if (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 server
read(sock, buffer, BUF_SIZE);
printf("%s", buffer);
if (strcmp(buffer, "Exit") == 0) {
printf("\nNormal Exit, bye!\n");
break;
}
// Send data to the server
scanf("%s", buffer);
send(sock, buffer, strlen(buffer), 0);
}
close(sock);
return 0;
}
在实际应用中127.0.0.1需要换成服务器所在地址。
结尾
在这里只是实现了学生成绩管理系统的添加功能和退出功能,其他功能如显示、查询功能有待进一步实现。你以为这就构造好了一个学生成绩管理系统?其实还可以有很多的工作可以做。
我:“你都被提问了这么久,你怎么没给人家出个编程题试试呢?”
师弟:“也对哦,我的思想似乎总是转的比较慢。我先去问问,她最近是不是也在鼓捣什么系统。”