目录
更多文章和源码在我的个人博客:首页 (niuniu65.top)
如需咨询请添加个人微信:a15135158368
欢迎叨扰,多多交流
一、较与上版本的优化
(如需查看,请移步到本人上篇文章)
1、服务器和客户端加入了多线程,实现多并发处理数据传输;
2、更加健壮的代码程序
3、标准输出加入了高亮文本,更方便调试
二、代码编写思路
服务器
主函数
-
信号处理:使 'sigaction'注册了'SIGINT'
-
服务器初始化:创建服务器套接字,并设置 SO_REUSEADDR 选项,允许服务器在关闭后立即重新绑定到相同的地址和端口。
-
绑定与监听:将服务器套接字绑定到指定的 IP 地址和端口,并开始监听客户端的连接请求。
-
客户端连接处理:进入一个无限循环,持续等待客户端的连接请求。每次接收到新的客户端连接时,创建一个新线程处理客户端的请求。线程由 do_client_request 函数实现,负责处理具体的客户端请求。
2.客户端请求处理 (do_client_request
)
-
从参数中获取客户端套接字,并通过
handle_client
函数处理客户端的文件上传、下载和查询操作。 -
在处理完请求后,关闭客户端套接字并释放资源。
3.处理与客户端的通信 (handle_client
):
接收操作码:从客户端接收操作码,表示客户端希望执行的操作类型。支持的操作码有:
0x01
:文件上传。
0x02
:文件下载。
0x03
:文件查询。
接收文件名:从客户端接收文件名,并根据操作码执行相应操作。
-
上传操作 (
0x01
):-
接收文件大小,并将客户端发送的文件数据保存到服务器端的文件中。
-
-
下载操作 (
0x02
):-
检查文件是否存在,并将文件内容发送给客户端。
-
-
查询操作 (
0x03
):-
检查文件是否存在,并将查询结果发送给客户端。
-
4.信号处理函数 (handle_sigint
):
在接收到
SIGINT
信号时,关闭服务器套接字并退出程序。
客户端
-
主函数:
-
信号处理:使用
sigaction
注册信号处理函数handle_sigint
-
服务器连接:在主循环中,客户端通过套接字与服务器进行连接。
-
用户输入:
-
用户输入文件名和操作码(1: 上传, 2: 下载, 3: 查询, 0: 退出)。
-
对输入进行基本验证。
-
-
线程创建:根据用户选择的操作码,动态分配线程数据结构,将套接字和操作参数传递给新创建的线程。每个线程独立处理一个用户操作。
-
-
线程处理逻辑 (
handle_operation
函数):-
上传文件 (
upload_image
):将本地文件发送到服务器。 -
下载文件 (
download_image
):从服务器下载文件到本地。 -
查询文件 (
query_image
):检查服务器上是否存在指定文件。 -
线程执行完相应的操作后关闭套接字,并释放动态分配的内存。
-
-
文件操作
-
上传文件:
-
打开本地文件并读取其内容,将文件名、文件大小和内容发送到服务器。
-
-
下载文件:
-
从服务器接收文件内容并将其写入本地文件。先接收文件大小,然后循环接收文件数据,直到完整下载文件。
-
-
查询文件:
-
发送文件名到服务器,接收服务器返回的文件存在状态(存在或不存在)。
-
-
三、代码优缺点
服务器
优点:
多线程并发
信号处理
基本的文件操作支持
缺点:
缺乏心跳机制:无法实时监测客户端的状态
缺乏文件操作的并发控制:加入文件锁等同步机制
硬编码配置:ip地址和端口号等配置被硬编码在代码中,缺乏灵活性。
客户端
优点
多线程并发处理:
并发性:采用多线程处理用户操作,使得客户端可以同时执行多个文件操作(上传、下载、查询),提高了程序的并发性能和响应速度。
资源管理:每个操作都在独立线程中执行,使用
pthread_detach
实现了线程分离,避免了线程阻塞主线程,也免去了主线程需要手动管理线程生命周期的负担。代码结构清晰:
模块化设计:代码通过函数划分不同的功能模块(如上传、下载、查询、信号处理等),使得代码逻辑清晰易懂,便于维护和扩展。
动态内存管理:使用动态内存分配 (
malloc
和free
) 来管理线程数据,避免了不必要的全局变量,提高了程序的可扩展性和灵活性。
缺点
线程安全问题:
潜在的竞争条件:每个操作都在独立线程中执行,但如果多个线程试图同时访问或修改共享资源(如全局变量或文件),可能会引发竞争条件。
虽然使用了独立的套接字连接,但如果程序扩展涉及共享数据(如计数器、日志文件等),则需注意线程同步问题。
客户端套接字管理:
重复连接:每次用户输入操作后,客户端都会重新创建一个新的套接字并连接到服务器。虽然简化了代码逻辑,但频繁的连接/断开操作可能会带来一定的性能开销。
用户交互阻塞:
主线程阻塞:主线程在每次并启动线程后,使用
sleep(0.5)
来等待一段时间,会使主线程在这段时间内阻塞,影响程序的实时性。
四、源代码
服务器
/**
******************************************************************************
* @file : tcp_server.c
* @author : niuniu
* @brief : 该程序实现了一个简单的多线程并发TCP服务器,能够处理客户端的文件上传、下载和查询操作
* @attention :
* @date : 2024/8/13
******************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <pthread.h>
// 定义用于终端文本高亮显示的颜色代码
#define BRIGHT_RED_TEXT "\033[91m" // 红色高亮文本
#define BRIGHT_GREEN_TEXT "\033[92m" // 绿色高亮文本
#define BRIGHT_YELLOW_TEXT "\033[93m" // 黄色高亮文本
#define BRIGHT_BLUE_TEXT "\033[94m" // 蓝色高亮文本
#define RESET_COLOR "\033[0m" // 重置颜色设置
#define PORT 8080 // 服务器监听的端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
#define SERVER_IP "192.168.216.149" // 服务器IP地址
int server_socket; // 服务器套接字
struct sockaddr_in server_addr, client_addr; // 存储服务器和客户端地址信息的结构体
//@brief 处理客户端请求的函数
int handle_client(int client_socket);
//@brief 处理SIGINT信号的函数(Ctrl+C)
void handle_sigint(int sig);
/**
* @brief 客户端请求处理线程
* @param argv 客户端套接字的指针
* @return void* 线程返回值
*/
void *do_client_request(void* argv)
{
int client_socket = *((int *)argv); // 从参数中获取客户端套接字
free(argv); // 释放传递进来的指针内存
printf(BRIGHT_GREEN_TEXT"等待指定操作中...\n"RESET_COLOR);
// 处理客户端请求
int handle_ret = handle_client(client_socket);
if(handle_ret != EXIT_SUCCESS)
{
perror("handle_client");
printf(BRIGHT_RED_TEXT"\n未能正确实现功能\n"RESET_COLOR);
}
// 关闭客户端套接字,释放资源
printf(BRIGHT_GREEN_TEXT"释放client_socket:%d 资源\n\n"RESET_COLOR, client_socket);
usleep(100000); // 延迟100毫秒
close(client_socket);
return NULL;
}
/**
* @brief 服务器主函数,初始化并启动服务器,持续监听客户端的连接请求
* @return int 程序退出状态码
*/
int main(void)
{
socklen_t client_addr_size; // 客户端地址结构体的大小
// 注册信号处理函数,用于处理 SIGINT (Ctrl+C)
struct sigaction sa;
sa.sa_handler = handle_sigint; // 设置处理函数
sa.sa_flags = 0; // 默认标志
sigemptyset(&sa.sa_mask); // 清空信号屏蔽字
if (sigaction(SIGINT, &sa, NULL) == -1)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
// 创建服务器套接字
server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
// 允许在套接字关闭后立即重新绑定到相同的地址和端口
int opt = 1;
if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构体
memset(&server_addr, 0, sizeof(server_addr)); // 清空结构体
server_addr.sin_family = AF_INET; // 使用IPv4协议
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 绑定服务器IP地址
server_addr.sin_port = htons(PORT); // 绑定端口号
// 绑定服务器套接字到指定IP和端口
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind");
exit(EXIT_FAILURE);
}
// 监听端口,设置最大等待队列长度为5
if (listen(server_socket, 5) == -1)
{
perror("listen");
exit(EXIT_FAILURE);
}
printf(BRIGHT_YELLOW_TEXT"服务器正在监听【%d】号端口号\n\n"RESET_COLOR, PORT);
while (1) // 无限循环,持续接收客户端请求
{
// 初始化客户端结构体大小
client_addr_size = sizeof(client_addr);
int *client_socket = malloc(sizeof(int)); // 动态分配内存存储客户端套接字
if (client_socket == NULL)
{
perror("malloc");
continue;
}
// 接受客户端连接
*client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_size);
if(*client_socket == -1)
{
perror("accept");
free(client_socket); // 释放内存,避免内存泄漏
continue;
}
printf(BRIGHT_GREEN_TEXT"成功接收了客户端的连接\n"RESET_COLOR);
printf(BRIGHT_GREEN_TEXT"新连接到的客户端为{%s : %d}\n\n"RESET_COLOR, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 创建线程处理客户端请求
pthread_t pid;
int pid_ret = pthread_create(&pid, NULL, do_client_request, (void*)client_socket);
if (pid_ret != 0) // 线程创建失败
{
perror("pthread_create");
close(*client_socket); // 关闭连接
free(client_socket);
continue;
}
pthread_detach(pid); // 设置线程为 detached 状态,自动回收资源
printf(BRIGHT_GREEN_TEXT"创建子线程并处理为detached状态\n\n"RESET_COLOR);
}
close(server_socket); // 关闭服务器套接字
return 0;
}
/**
* @brief 处理SIGINT信号的函数(Ctrl+C)
* @param sig 信号值
*/
void handle_sigint(int sig)
{
printf(BRIGHT_RED_TEXT"\n接收到信号 %d,正在关闭服务器...\n"RESET_COLOR, sig);
printf(BRIGHT_RED_TEXT"释放资源\n"RESET_COLOR);
close(server_socket);
exit(0);
}
/**
* @brief 处理与客户端的通信,执行上传、下载、查询操作
*
* @param client_socket 与客户端通信的套接字
*/
int handle_client(int client_socket)
{
char buffer[BUFFER_SIZE]; // 用于存储接收和发送的数据的缓冲区
int name_len; // 文件名长度
char file_name[256] = {0}; // 用于存储文件名的字符数组
unsigned int file_size; // 文件大小(字节数)
// 接收操作码,表示客户端希望执行的操作类型
if (recv(client_socket, buffer, 1, 0) <= 0) // 接收操作码失败则退出循环
{
perror("recv_opcode");
return EXIT_FAILURE;
}
char opcode = buffer[0]; // 操作码,0x01上传,0x02下载,0x03查询
// 接收文件名长度
if (recv(client_socket, buffer, 1, 0) <= 0) // 接收文件名长度失败则退出循环
{
perror("recv_len");
return EXIT_FAILURE;
}
// 获取文件名长度
name_len = buffer[0];
// 接收文件名
if (recv(client_socket, file_name, name_len, 0) <= 0) // 接收文件名失败则退出循环
{
perror("recv_name");
return EXIT_FAILURE;
}
file_name[name_len] = '\0'; // 文件名字符串的末尾添加结束符
switch (opcode)
{
case 0x01:
{
int file_fd;
// 接收文件大小
if (recv(client_socket, buffer, 4, 0) <= 0) // 接收文件大小失败则退出循环
{
perror("recv4");
return EXIT_FAILURE;
}
file_size = *((unsigned int *)buffer); // 将接收的文件大小转换为整数
// 打开或者创建一个新文件,用于保存上传的图片
file_fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (file_fd < 0) // 文件打开失败则退出循环
{
perror("open");
return EXIT_FAILURE;
}
int received = 0; // 已接收的数据大小
// 循环接收文件内容,直到接收完整个文件
while (received < file_size)
{
int len = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (len <= 0) // 接收数据失败则退出循环
{
perror("recv_content");
close(file_fd);
return EXIT_FAILURE;
}
received += len; // 更新已接收的字节数
write(file_fd, buffer, len); // 将接收的数据写入文件
}
close(file_fd); // 关闭文件描述符
printf(BRIGHT_YELLOW_TEXT"已将文件【%s】上传\n"RESET_COLOR, file_name); // 打印成功接收的文件名
return EXIT_SUCCESS;
}
case 0x02:
{
int file_fd;
file_fd = open(file_name, O_RDONLY); // 打开要下载的文件
if (file_fd < 0) // 文件不存在,发送文件大小为0
{
file_size = 0;
send(client_socket, &file_size, sizeof(file_size), 0);
perror("open failed");
close(file_fd);
return EXIT_FAILURE;
}
else
{
// 计算文件大小 (通过 lseek 的偏移量返回值)
file_size = lseek(file_fd, 0, SEEK_END);
lseek(file_fd, 0, SEEK_SET);
// 发送文件大小给客户端
send(client_socket, &file_size, sizeof(file_size), 0);
// 循环发送文件内容
int read_len = 0;
while ((read_len = read(file_fd, buffer, BUFFER_SIZE)) > 0)
{
send(client_socket, buffer, read_len, 0);
}
printf(BRIGHT_YELLOW_TEXT"已将文件【%s】发送下载成功\n"RESET_COLOR, file_name);
}
close(file_fd); // 关闭文件描述符
return EXIT_SUCCESS;
}
case 0x03:
{
// 使用 access 函数判断文件是否存在,存在返回1,不存在返回0
int exists = access(file_name, F_OK) != -1;
send(client_socket, &exists, sizeof(exists), 0);
printf(BRIGHT_YELLOW_TEXT"已查询文件【%s】是否存在\n"RESET_COLOR, file_name);
return EXIT_SUCCESS;
}
}
return EXIT_SUCCESS;
}
客户端
/**
******************************************************************************
* @file : tcp_client.c
* @author : niuniu
* @brief : 采用多线程并发实现客户端程序,用于连接服务器并执行上传、下载、查询操作
* @attention : None
* @date : 2024/8/13
******************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <signal.h>
#define BRIGHT_RED_TEXT "\033[91m" //红色高亮文本
#define BRIGHT_GREEN_TEXT "\033[92m" //绿色高亮文本
#define BRIGHT_YELLOW_TEXT "\033[93m" //黄色高亮文本
#define BRIGHT_BLUE_TEXT "\033[94m" //蓝色高亮文本
#define RESET_COLOR "\033[0m" //重置颜色设置
#define SERVER_IP "192.168.216.149" // 服务器的IP地址
#define PORT 8080 // 服务器的端口号
#define BUFFER_SIZE 1024 // 缓冲区大小
#define FILE_SIZE 256 // 文件名大小
void upload_image(int client_socket, const char *file_path); //上传图片
void download_image(int client_socket, const char *file_name); //下载图片
void query_image(int client_socket, const char *file_name); //查找图片是否存在
void handle_sigint(int sig); //信号处理函数
int client_socket = -1;
// 定义一个结构体来存储线程所需的数据
typedef struct {
int client_socket;
int opcode;
char file_name[FILE_SIZE];
} thread_data_t;
// 线程处理函数
void *handle_operation(void *arg) {
// 将传入的 void* 类型参数转换为 thread_data_t* 类型
// 这样我们就可以访问传递给线程的数据,包括操作码、文件名和客户端套接字
thread_data_t *data = (thread_data_t *)arg;
// 根据操作码执行相应的文件操作
switch (data->opcode) {
case 1:
// 如果操作码为 1,则执行上传操作
printf(BRIGHT_GREEN_TEXT"上传文件: [%s]\n\n"RESET_COLOR, data->file_name);
// 调用上传函数,将文件上传到服务器
upload_image(data->client_socket, data->file_name);
break;
case 2:
// 如果操作码为 2,则执行下载操作
printf(BRIGHT_GREEN_TEXT"下载文件: [%s]\n\n"RESET_COLOR, data->file_name);
// 调用下载函数,从服务器下载文件
download_image(data->client_socket, data->file_name);
break;
case 3:
// 如果操作码为 3,则执行查询操作
printf(BRIGHT_GREEN_TEXT"查询文件: [%s]\n\n"RESET_COLOR, data->file_name);
// 调用查询函数,查询服务器上是否存在指定文件
query_image(data->client_socket, data->file_name);
break;
default:
// 如果操作码不在预期范围内,打印错误信息
fprintf(stderr, BRIGHT_RED_TEXT"无效的操作码\n"RESET_COLOR);
break;
}
// 关闭客户端套接字,以释放与服务器的连接资源
close(data->client_socket);
// 释放线程数据结构所占用的内存
// 该内存是在主线程中为每个线程动态分配的
free(data);
// 线程执行完毕后返回 NULL
return NULL;
}
int main(void) {
// 注册信号处理函数,用于处理 SIGINT (Ctrl+C)
struct sigaction sa;
sa.sa_handler = handle_sigint;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) == -1)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
// 清零 server_addr 结构体
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // 设置地址族为 IPv4
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 设置服务器 IP 地址
server_addr.sin_port = htons(PORT); // 设置端口号,使用网络字节序
while (1) {
// 创建套接字
client_socket = socket(PF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
perror("socket"); // 打印错误信息
exit(EXIT_FAILURE); // 退出程序
}
// 尝试连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect"); // 打印错误信息
close(client_socket); // 关闭套接字
exit(EXIT_FAILURE); // 退出程序
}
printf(BRIGHT_BLUE_TEXT"已连接到服务器\n"RESET_COLOR);
char file_name[FILE_SIZE];
int opcode;
// 提示用户输入文件名
printf(BRIGHT_BLUE_TEXT"请输入文件名(最大为 %d 字符)\n"RESET_COLOR, FILE_SIZE);
if (fgets(file_name, FILE_SIZE, stdin) == NULL) {
fprintf(stderr, BRIGHT_RED_TEXT"读取文件名失败\n"RESET_COLOR);
close(client_socket);
continue;
}
// 去除文件名末尾的换行符
size_t len = strlen(file_name);
if (len > 0 && file_name[len - 1] == '\n') {
file_name[len - 1] = '\0';
}
// 提示用户输入操作码
printf(BRIGHT_BLUE_TEXT"请输入操作码 (1: 上传, 2: 下载, 3: 查询, 0: 退出): "RESET_COLOR);
if (scanf("%d", &opcode) != 1) {
fprintf(stderr, BRIGHT_RED_TEXT"无效的操作码\n"RESET_COLOR);
while (getchar() != '\n'); // 清空输入缓冲区
close(client_socket);
continue;
}
while (getchar() != '\n'); // 清空输入缓冲区
if (opcode == 0) {
printf(BRIGHT_GREEN_TEXT"即将退出程序...\n"RESET_COLOR);
close(client_socket); // 关闭套接字
break;
}
if (opcode < 1 || opcode > 3) {
printf(BRIGHT_RED_TEXT"无效的操作码\n"RESET_COLOR);
close(client_socket); // 关闭套接字
continue;
}
// 动态分配线程数据结构的内存,并初始化
thread_data_t *data = (thread_data_t *)malloc(sizeof(thread_data_t));
if (data == NULL) {
perror("malloc");
close(client_socket);
continue;
}
data->client_socket = client_socket;
data->opcode = opcode;
strncpy(data->file_name, file_name, FILE_SIZE);
// 创建线程并处理操作
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, handle_operation, (void *)data) != 0) {
perror("pthread_create");
free(data);
close(client_socket);
continue;
}
// 分离线程,以便线程资源在完成时自动释放
pthread_detach(thread_id);
sleep(0.5);
}
return 0;
}
// 上传图片函数
void upload_image(int client_socket, const char *file_path)
{
char buffer[BUFFER_SIZE];
char file_name[FILE_SIZE];
unsigned int file_size;
int file_fd = open(file_path, O_RDONLY);
if (file_fd < 0)
{
perror("open");
return;
}
// 获取文件名
snprintf(file_name, sizeof(file_name), "%s", strrchr(file_path, '/') ? strrchr(file_path, '/') + 1 : file_path);
buffer[0] = 0x01;
send(client_socket, buffer, 1, 0);
buffer[0] = strlen(file_name);
send(client_socket, buffer, 1, 0);
send(client_socket, file_name, strlen(file_name), 0);
file_size = lseek(file_fd, 0, SEEK_END);
lseek(file_fd, 0, SEEK_SET);
send(client_socket, &file_size, sizeof(file_size), 0);
int read_len;
while ((read_len = read(file_fd, buffer, BUFFER_SIZE)) > 0)
{
send(client_socket, buffer, read_len, 0);
}
close(file_fd);
printf(BRIGHT_RED_TEXT"成功上传【%s】文件\n"RESET_COLOR, file_name);
}
// 下载图片函数
void download_image(int client_socket, const char *file_name)
{
char buffer[BUFFER_SIZE];
unsigned int file_size;
buffer[0] = 0x02;
send(client_socket, buffer, 1, 0);
buffer[0] = strlen(file_name);
send(client_socket, buffer, 1, 0);
send(client_socket, file_name, strlen(file_name) + 1, 0);
recv(client_socket, &file_size, sizeof(file_size), 0);
if (file_size > 0)
{
int file_fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (file_fd < 0)
{
perror("open");
return;
}
int received = 0;
while (received < file_size)
{
int len = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (len < 0)
{
perror("recv");
close(file_fd);
return;
}
else if (len == 0)
{
fprintf(stderr, "服务器意外关闭连接\n");
close(file_fd);
return;
}
if (write(file_fd, buffer, len) != len)
{
perror("write");
close(file_fd);
return;
}
received += len;
}
close(file_fd);
printf(BRIGHT_RED_TEXT"下载文件【%s】成功\n"RESET_COLOR, file_name);
}
else
{
printf(BRIGHT_RED_TEXT"未找到【%s】文件\n"RESET_COLOR, file_name);
}
}
// 查询图片是否存在函数
void query_image(int client_socket, const char *file_name)
{
char buffer[BUFFER_SIZE];
int exists;
buffer[0] = 0x03;
send(client_socket, buffer, 1, 0);
buffer[0] = strlen(file_name);
send(client_socket, buffer, 1, 0);
send(client_socket, file_name, strlen(file_name), 0);
recv(client_socket, &exists, sizeof(exists), 0);
if (exists)
{
printf(BRIGHT_RED_TEXT"文件【%s】存在\n"RESET_COLOR, file_name);
}
else
{
printf(BRIGHT_RED_TEXT"文件【%s】不存在\n"RESET_COLOR, file_name);
}
}
// 信号处理函数,处理 SIGINT 信号
void handle_sigint(int sig)
{
printf("\n接收到中断信号(SIGINT),程序即将退出...\n");
// 在这里可以添加程序退出前的清理代码,例如关闭打开的文件、套接字等
if (client_socket != -1) {
close(client_socket);
}
exit(EXIT_SUCCESS); // 退出程序
}