实现功能:基于多线程实现多用户同时上传文件
服务器端代码
// 套接字编程——服务器端——多线程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <openssl/sha.h>
#include <dirent.h>
#include <sys/stat.h>
#include <hiredis/hiredis.h>
#define BUFFER_SIZE 1024
#define FILENAME_SIZE 256
#define FILE_DIR "receive_files/" // 存放文件的目录
void *handleClient(void *);
void ensureDirectoryExists(const char *path);
int checkFileExistsInRedis(redisContext *redis, const unsigned char *hash, const char *fileName);
void storeFileInRedis(redisContext *redis, const unsigned char *hash, const char *fileName);
char *hashToHexString(const unsigned char *hash, char *outputBuffer);
int main(int argc, char const *argv[]) {
// 确保文件目录存在
ensureDirectoryExists(FILE_DIR);
// 创建套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("创建套接字失败");
exit(EXIT_FAILURE);
}
// 服务器地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定地址
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("绑定地址失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 9) < 0) {
perror("监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("服务器监听中...\n");
// 客户端地址
struct sockaddr_in addr_client;
socklen_t addr_len = sizeof(addr_client);
while (1) {
// 接收请求,建立 TCP 连接,获得了一个新的客户端套接字
int client_fd = accept(server_fd, (struct sockaddr *)&addr_client, &addr_len);
if (client_fd < 0) {
perror("接受客户端连接失败");
continue;
}
// 创建线程
pthread_t p;
int *client_fd_ptr = malloc(sizeof(int));
if (client_fd_ptr == NULL) {
perror("内存分配失败");
close(client_fd);
continue;
}
*client_fd_ptr = client_fd;
if (pthread_create(&p, NULL, handleClient, (void *)client_fd_ptr) != 0) {
perror("创建线程失败");
free(client_fd_ptr);
close(client_fd);
continue;
}
// 分离线程
pthread_detach(p);
}
// 关闭服务器套接字
close(server_fd);
return 0;
}
void ensureDirectoryExists(const char *path) {
struct stat st = {0};
if (stat(path, &st) == -1) {
if (mkdir(path, 0700) < 0) {
perror("创建目录失败");
exit(EXIT_FAILURE);
}
}
}
void *handleClient(void *arg) {
int client_fd = *((int *)arg);
free(arg);
redisContext *redis = redisConnect("127.0.0.1", 6379);
if (redis == NULL || redis->err) {
if (redis) {
printf("Redis 连接错误: %s\n", redis->errstr);
redisFree(redis);
} else {
printf("Redis 连接错误: 不能分配 redisContext\n");
}
close(client_fd);
pthread_exit(NULL);
}
char fileName[FILENAME_SIZE];
unsigned char hash[SHA256_DIGEST_LENGTH];
// 先接收文件名
if (read(client_fd, fileName, FILENAME_SIZE) <= 0) {
perror("读取文件名失败");
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
// 再接收哈希值
if (read(client_fd, hash, SHA256_DIGEST_LENGTH) != SHA256_DIGEST_LENGTH) {
perror("读取哈希值失败");
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
// 检查文件是否已存在
if (checkFileExistsInRedis(redis, hash, fileName)) {
// 文件已存在且文件名不同,通知客户端终止传输
if (write(client_fd, "EXISTS\n", strlen("EXISTS\n")) < 0) {
perror("通知客户端文件已存在失败");
}
// 存储哈希值和文件名到 Redis
storeFileInRedis(redis, hash, fileName);
} else {
// 文件不存在,通知客户端继续传输
if (write(client_fd, "NOT_EXISTS\n", strlen("NOT_EXISTS\n")) < 0) {
perror("通知客户端文件不存在失败");
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
// 写入文件内容
char filePath[BUFFER_SIZE];
snprintf(filePath, sizeof(filePath), "%s%s", FILE_DIR, fileName);
FILE *f = fopen(filePath, "wb");
if (f == NULL) {
perror("文件写入失败");
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
int size;
char buf[BUFFER_SIZE];
while ((size = read(client_fd, buf, sizeof(buf))) > 0) {
if (fwrite(buf, 1, size, f) != size) {
perror("写入文件失败");
fclose(f);
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
}
if (size < 0) {
perror("读取客户端数据失败");
}
fclose(f);
// 存储哈希值和文件名到 Redis
storeFileInRedis(redis, hash, fileName);
}
close(client_fd);
redisFree(redis);
pthread_exit(NULL);
}
// 将文件的哈希值转换为十六进制字符串
char *hashToHexString(const unsigned char *hash, char *outputBuffer) {
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(outputBuffer + (i * 2), "%02x", hash[i]);
}
return outputBuffer;
}
// 检查文件是否已存在于 Redis
int checkFileExistsInRedis(redisContext *redis, const unsigned char *hash, const char *fileName) {
char hexHash[SHA256_DIGEST_LENGTH * 2 + 1];
hashToHexString(hash, hexHash);
redisReply *reply = redisCommand(redis, "EXISTS %s", hexHash);
int exists = reply->integer;
freeReplyObject(reply);
return exists;
}
void storeFileInRedis(redisContext *redis, const unsigned char *hash, const char *fileName) {
char hexHash[SHA256_DIGEST_LENGTH * 2 + 1];
hashToHexString(hash, hexHash);
// 检查是否存在相同哈希值的文件
redisReply *reply = redisCommand(redis, "EXISTS %s", hexHash);
int hashExists = reply->integer;
freeReplyObject(reply);
if (hashExists) {
// 获取已存在的文件名
reply = redisCommand(redis, "GET %s", hexHash);
char *existingFileName = malloc(strlen(reply->str) + 1);
existingFileName = strdup(reply->str);
freeReplyObject(reply);
// 改变当前工作目录到 FILE_DIR
if (chdir(FILE_DIR) < 0) {
perror("无法进入目标目录");
return;
}
// 创建软链接
if (symlink(existingFileName, fileName) < 0) {
perror("创建软链接失败");
}
free(existingFileName);
} else {
// 添加新哈希和文件名到 Redis
reply = redisCommand(redis, "SET %s %s", hexHash, fileName);
if (reply == NULL) {
printf("存储文件信息到 Redis 失败\n");
}
freeReplyObject(reply);
}
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/sha.h>
#define BUFFER_SIZE 1024
#define FILENAME_SIZE 256
void calculateSHA256(const char *filePath, unsigned char hash[SHA256_DIGEST_LENGTH]);
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <file_path>\n", argv[0]);
exit(EXIT_FAILURE);
}
const char *filePath = argv[1];
char fileName[FILENAME_SIZE];
unsigned char fileHash[SHA256_DIGEST_LENGTH];
// 计算文件的 SHA256 值
calculateSHA256(filePath, fileHash);
// 提取文件名
const char *baseName = strrchr(filePath, '/');
if (baseName)
{
strncpy(fileName, baseName + 1, FILENAME_SIZE);
}
else
{
strncpy(fileName, filePath, FILENAME_SIZE);
}
// 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("创建套接字失败");
exit(EXIT_FAILURE);
}
// 服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 这里填你的服务器公网IP,或主机地址
// 连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("连接到服务器失败");
close(sock);
exit(EXIT_FAILURE);
}
// 发送文件名
if (write(sock, fileName, FILENAME_SIZE) < 0)
{
perror("发送文件名失败");
close(sock);
exit(EXIT_FAILURE);
}
// 发送文件哈希值
if (write(sock, fileHash, SHA256_DIGEST_LENGTH) < 0)
{
perror("发送文件哈希值失败");
close(sock);
exit(EXIT_FAILURE);
}
// 接收服务器的响应
char response[BUFFER_SIZE] = {0};
if (read(sock, response, sizeof(response) - 1) < 0)
{
perror("读取服务器响应失败");
close(sock);
exit(EXIT_FAILURE);
}
// 移除可能存在的换行符或回车符
response[strcspn(response, "\r\n")] = 0;
if (strcmp(response, "EXISTS") == 0)
{
printf("文件已存在,上传成功\n");
close(sock);
return 0;
} else if (strcmp(response, "NOT_EXISTS") == 0)
{
printf("文件不存在,开始上传文件内容...\n");
} else
{
fprintf(stderr, "未知响应: %s\n", response);
close(sock);
exit(EXIT_FAILURE);
}
// 发送文件内容
FILE *file = fopen(filePath, "rb");
if (file == NULL) {
perror("打开文件失败");
close(sock);
exit(EXIT_FAILURE);
}
char buf[BUFFER_SIZE];
int size;
while ((size = fread(buf, 1, sizeof(buf), file)) > 0)
{
if (write(sock, buf, size) < 0) {
perror("发送文件内容失败");
fclose(file);
close(sock);
exit(EXIT_FAILURE);
}
}
fclose(file);
close(sock);
printf("文件上传完成\n");
return 0;
}
void calculateSHA256(const char *filePath, unsigned char hash[SHA256_DIGEST_LENGTH])
{
FILE *file = fopen(filePath, "rb");
if (!file) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}
SHA256_CTX sha256;
SHA256_Init(&sha256);
const int bufSize = BUFFER_SIZE;
unsigned char *buffer = malloc(bufSize);
int bytesRead = 0;
if (!buffer) {
perror("分配缓冲区失败");
fclose(file);
exit(EXIT_FAILURE);
}
while ((bytesRead = fread(buffer, 1, bufSize, file)) > 0)
{
SHA256_Update(&sha256, buffer, bytesRead);
}
SHA256_Final(hash, &sha256);
fclose(file);
free(buffer);
}