服务器文件上传

实现功能:基于多线程实现多用户同时上传文件

服务器端代码

// 套接字编程——服务器端——多线程
#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);
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值