基于TCP/IP的文件传输实现

功能包括

使用HASH加密防止sql注入

获取文件列表,选择文件上传or下载

登录注册、传输异常处理,客户端掉线记录断点,实现断点重传的功能。

多线程支持。

上下载进度条等。

客户端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <openssl/sha.h>
#include <pthread.h>
#include <poll.h>

#define SIZE 64
#define connect_init_error_code -1
#define quit_code 1
#define up_end_code 2
#define password_error_code 3;
#define name_exit_code 4
#define register_successful_code 5
#define register_failue_code 6
#define login_successfully_code 7
#define login_failue_code 8
#define unrefister_code 9
#define upload_statu_code 10
#define download_statu_code 11
#define exit_upload_statu_code 12
#define exit_download_statu_code 13
#define end_get_file_code 14
#define download_file_not_exist_code 15
#define begin_download_code 16

#define HEARTBEAT_CODE 20

#define have_up_exception_code 22
#define have_down_exception_code 21

int is_data_transferring = 0; // 全局变量,表示是否正在传输数据

// 定义一个状态机的菜单结构体,用于描述用户当前所处于的状态
// 枚举类型
typedef enum
{
    STATE_START_MENU,         // 开始界面
    STATE_LOGIN,              // 登录
    STATE_REGISTER,           // 注册
    STATE_FUCTION_MENU,       // 功能菜单
    STATE_UPLOAD,             // 上传
    STATE_DOWNLOAD,           // 下载
    STATE_UPLOAD_EXCEPTION,   // 上传异常
    STATE_DOWNLOAD_EXCEPTION, // 下载异常

    STATE_QUIT // 退出

} State;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int check_args(int argc);
int create_socket_and_connect(char *argv[], int *cfd);
int open_send_file(int *cfd, int *fd);

void send_file_content(int *cfd, int *fd);

int upload_file(int *cfd, int *fd);
// 定义一个菜单函数,用于实现界面跳转
int client_menu(int *cfd, int *fd);
// 定义一个函数,用于登录 ,发送用户名和密码,根据服务器决定返回值,成功返回1,未注册返回一个值,密码错误一个值
int login(int *cfd);
// 定义一个函数,用于注册,,有返回值,根据服务器那边数据库比对,如果没有该用户,返回成功,已经有该用户,返回失败,注册函数执行完毕,去登录界面
int Register_login(int *cfd, int ROL);
int send_up_down_statue(int *cfd, int status);
void sha256_hash_string(unsigned char hash[SHA256_DIGEST_LENGTH], char outputBuffer[65]);
void sha256_string(char *string, char outputBuffer[65]);
int send_file_content_exception(int *cfd, char *filename, off_t start_pos);

int main(int argc, char *argv[])
{
    int cfd, fd;
    int choice;
    if (check_args(argc) == connect_init_error_code)
        return connect_init_error_code;

    if (create_socket_and_connect(argv, &cfd) == connect_init_error_code)
        return connect_init_error_code;
    // 选择执行上传还是下载,上传就进入上传函数,下载先获取服务器列表
    client_menu(&cfd, &fd);

    return 0;
}

// 将SHA-256哈希值转换为十六进制字符串
void sha256_hash_string(unsigned char hash[SHA256_DIGEST_LENGTH], char outputBuffer[65])
{
    int i = 0;

    // 遍历每个字节的哈希值
    for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
    { //%02x表示将一个整数转换为两位的十六进制数,如果不足两位则在前面补0
        sprintf(outputBuffer + (i * 2), "%02x", hash[i]);
    }
    outputBuffer[64] = 0;
}

void sha256_string(char *string, char outputBuffer[65])
{
    // 定义一个数组,用于存储SHA-256哈希值
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;

    SHA256_Init(&sha256);
    SHA256_Update(&sha256, string, strlen(string));
    SHA256_Final(hash, &sha256);

    // 将计算出的哈希值转换为一个十六进制字符串,并将这个字符串存储在outputBuffer中
    sha256_hash_string(hash, outputBuffer);
}

int check_args(int argc)
{
    if (3 > argc)
    {
        printf("USLIKE:IP+PORT\n");
        return connect_init_error_code;
    }
    return 0;
}

int create_socket_and_connect(char *argv[], int *cfd)
{
    // 创建socket
    *cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == *cfd)
    {
        perror("socket");
        return connect_init_error_code;
    }

    // 开启keepalive选项
    int enable = 1;
    int idle = 10;     // 空闲时间10秒
    int interval = 15; // 每15秒发送一次
    int count = 3;     // 发送3次后仍无响应则断开连接

    setsockopt(*cfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable));
    setsockopt(*cfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
    setsockopt(*cfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
    setsockopt(*cfd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));

    struct sockaddr_in sddr;
    sddr.sin_family = AF_INET; // IPV4
    sddr.sin_port = htons(atoi(argv[2]));
    sddr.sin_addr.s_addr = inet_addr(argv[1]);
    // 连接到服务器
    if (-1 == connect(*cfd, (void *)&sddr, sizeof(sddr)))
    {
        perror("connect");
        return connect_init_error_code;
    }

    return 0;
}

void send_file_content(int *cfd, int *fd)
{
    char buf[SIZE];
    int count;
    // 获取文件大小
    struct stat st;
    fstat(*fd, &st);
    int file_size = st.st_size;
    int sent_size = 0; // 已发送的数据大小
    write(*cfd, &file_size, sizeof(int));
    while ((count = read(*fd, buf, SIZE)) > 0)
    {
        write(*cfd, buf, count);
        sent_size += count;
        // 计算并打印上传进度
        float progress = (float)sent_size / file_size * 100;
        printf("Upload progress: %.2f%%\r", progress);
        fflush(stdout); // 刷新输出,使进度条能够实时更新
    }
    puts("\nDone");
    close(*fd);
}

int send_file_content_exception(int *cfd, char *filename, off_t start_pos)
{

    int fd;

    // 打开文件
    fd = open(filename, O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return 0;
    }

    char buf[SIZE];
    int count;
    // 获取文件大小
    struct stat st;
    fstat(fd, &st);
    int file_size = st.st_size;
    int sent_size = start_pos;
    write(*cfd, &file_size, sizeof(int));
    lseek(fd, start_pos, SEEK_SET); // 设置文件偏移量
    while ((count = read(fd, buf, SIZE)) > 0)
    {
        write(*cfd, buf, count);
        sent_size += count;
        // 计算并打印上传进度
        float progress = (float)sent_size / file_size * 100;
        printf("Upload progress: %.2f%%\r", progress);
        fflush(stdout); // 刷新输出,使进度条能够实时更新
    }
    puts("Done");
    close(fd);
    return 1;
}

int open_send_file(int *cfd, int *fd)
{
    // 从键盘获取文件名:
    char filename[30];
    printf("please enter the filename\n");
    fgets(filename, 30, stdin);
    filename[strlen(filename) - 1] = '\0';
    int file_len = strlen(filename) + 1;
    // 如果文件名为quit,则退出
    if (0 == strncmp("quit", filename, 4))
    {
        write(*cfd, &file_len, sizeof(int));
        write(*cfd, filename, file_len);
        return quit_code;
    }
    *fd = open(filename, O_RDONLY);
    if (-1 == *fd)
    {
        perror("open");
        return -1;
    }

    write(*cfd, &file_len, sizeof(int));
    printf("%d", file_len);
    write(*cfd, filename, file_len);
    send_file_content(cfd, fd);
    return 0;
}

// 封装一个函数,将上传的代码封装为一个函数

int upload_file(int *cfd, int *fd)
{
    while (1)
    {
        int ret = open_send_file(cfd, fd);
        if (ret == -1)
            return -1;
        if (ret == quit_code)
            break;
    }
    // close(*cfd);
    return exit_upload_statu_code;
}

// 封装一个函数用于接收来自服务器的文件

void *recv_file(void *pnfd, char *filename)
{

    int nfd = *(int *)pnfd;
    printf("downloading file %s\n", filename);
    // 创建文件
    int fd = open(filename, O_WRONLY | O_CREAT, 0664);
    if (-1 == fd)
    {
        perror("open");
        exit(-1);
    }
    int file_size;
    read(nfd, &file_size, sizeof(int));

    // 接收文件内容并写入到文件中
    char buf[SIZE];
    int count;
    int size_judge = 0;
    while ((count = read(nfd, buf, SIZE)) > 0)
    {
        write(fd, buf, count);
        size_judge = size_judge + count;
        float progress = (float)size_judge / file_size * 100;
        printf("Upload progress: %.2f%%\r", progress);
        if (size_judge == file_size)
        {
            close(fd);
            puts("Done");
            break;
        }
    }

    return NULL;
}

int client_menu(int *cfd, int *fd)
{
    int choice;
    int log_status;
    int fun_status;
    int up_status;
    pthread_t tid_heatbeat_send;
    State current_state = STATE_START_MENU;
    // 状态机思想
    while (current_state != STATE_QUIT)
    {
        switch (current_state)
        {
        case STATE_START_MENU:
            printf("请输入你的选择:1.登录 2.注册 3.退出\n");
            scanf("%d", &choice);
            getchar(); // scanf不会处理换行符/n吃掉换行符避免后面的fets获取到\n
            if (choice == 1)
            {
                // 处理登录
                current_state = STATE_LOGIN;
            }
            else if (choice == 2)
            {
                // 处理注册
                current_state = STATE_REGISTER;
            }
            else if (choice == 3)
            {
                // 退出
                current_state = STATE_QUIT;
            }

            break;
        case STATE_REGISTER:
            // 注册
            log_status = Register_login(cfd, 0);
            if (log_status == register_successful_code)
            {
                current_state = STATE_START_MENU;
            }
            else if (log_status == login_failue_code)
            {
                current_state = STATE_REGISTER;
            }

            break;

        case STATE_LOGIN:
            // 登录

            log_status = Register_login(cfd, 1);

            switch (log_status)
            {
            case login_successfully_code:
                current_state = STATE_FUCTION_MENU;
                break;
            case login_failue_code:
                current_state = STATE_START_MENU;
                break;
            case have_down_exception_code:
                current_state = STATE_DOWNLOAD_EXCEPTION;
                break;
            case have_up_exception_code:
                current_state = STATE_UPLOAD_EXCEPTION;
                break;
            default:
                break;
            }

            break;
        case STATE_UPLOAD_EXCEPTION:
            printf("有数据未上传完毕,开始断点重传\n");
            // int send_type = 22;
            // write(*cfd, &send_type, sizeof(int));
            int message_len;
            char message_expection[128];
            char filename[256];
            char type[256];
            int transferred;
            int read_ret = read(*cfd, &message_len, sizeof(int));
            if (read_ret <= 0)
            {
                printf("read eception error\n");
            }
            read(*cfd, message_expection, message_len);
            sscanf(message_expection, "Filename: %s Type: %s Transferred: %d", filename, type, &transferred);
            printf("%s\n", message_expection);
            printf("%s\n", filename);

            int exception_judge = send_file_content_exception(cfd, filename, transferred);
            if (exception_judge == 1)
            {
                printf("断点重传成功\n");
            }
            current_state = STATE_FUCTION_MENU;
            break;
        case STATE_FUCTION_MENU:
            printf("请输入你的选择:1.上传 2.下载 3.退出\n");
            scanf("%d", &choice);
            getchar(); // scanf不会处理换行符/n吃掉换行符避免后面的fets获取到\n
            if (choice == 1)
            {
                // 处理上传
                current_state = STATE_UPLOAD;
            }
            else if (choice == 2)
            {
                // 处理下载
                current_state = STATE_DOWNLOAD;
            }
            else if (choice == 3)
            {
                // 处理下载
                current_state = STATE_QUIT;
            }
            break;
        case STATE_UPLOAD:
            fun_status = upload_statu_code;
            printf("enter quit to exit upload state\n");
            write(*cfd, &fun_status, sizeof(int));
            up_status = upload_file(cfd, fd);
            if (exit_upload_statu_code == up_status)
            {
                write(*cfd, &up_status, sizeof(int));
                current_state = STATE_FUCTION_MENU;
            }

            break;
        case STATE_DOWNLOAD:
            // 处理下载
            fun_status = download_statu_code;
            int while_status;
            int server_response;
            int filename_buffer_size = 128;
            char buffer[128];
            char download_filenaeme_buffer[128];
            int ret;
            puts("**************************");
            printf("enter quit to exit down state\n");
            puts("**************************");
            // 发送下载状态指令
            write(*cfd, &fun_status, sizeof(int));

            while ((ret = read(*cfd, buffer, sizeof(buffer))) > 0)
            {
                if (0 == strncmp(buffer, "END_SEND_FILENAME", 17))
                {

                    puts("**************************");
                    break;
                }
                printf("%s\n", buffer);
            }
            puts("please enter filename you want to download");
            while (1)
            {
                fgets(download_filenaeme_buffer, filename_buffer_size, stdin);
                // 发送文件名
                download_filenaeme_buffer[strlen(download_filenaeme_buffer) - 1] = '\0';
                int file_len = strlen(download_filenaeme_buffer) + 1;
                // 发送文件名长度
                write(*cfd, &file_len, sizeof(int));
                write(*cfd, download_filenaeme_buffer, filename_buffer_size);
                if (0 == strncmp(download_filenaeme_buffer, "quit", 4))
                {
                    current_state = STATE_FUCTION_MENU;
                    break;
                }
                // 获取文件名的服务器结果
                read(*cfd, &server_response, sizeof(int));
                switch (server_response)
                {
                case download_file_not_exist_code:
                    puts("file not exist");
                    current_state = STATE_FUCTION_MENU;
                    break;
                case begin_download_code:
                    recv_file(cfd, download_filenaeme_buffer);
                    current_state = STATE_DOWNLOAD;
                    break;
                default:
                    break;
                }
                break;
            }
            break;
        }
    }
}
// 注册登录函数
int Register_login(int *cfd, int ROL)
{
    char username_buffer[15];
    char password_buffer[11];
    char password_hash_buffer[65];
    int server_response;
    puts("**************************");
    printf("pelease enter the name: \n");
    puts("**************************");
    fgets(username_buffer, 15, stdin);
    puts("**************************");
    printf("pelease enter the Password: \n");
    puts("**************************");
    fgets(password_buffer, 11, stdin);
    // 将数据组合为一个注册包发送给服务器,注册包的开头为0,登录包的开头为1
    sha256_string(password_buffer, password_hash_buffer);
    char register_pack[100];
    if (0 == ROL)
    {
        sprintf(register_pack, "0%s%s", username_buffer, password_hash_buffer);
    }
    else if (1 == ROL)
    {
        sprintf(register_pack, "1%s%s", username_buffer, password_hash_buffer);
    }

    //  发送到服务器
    write(*cfd, register_pack, strlen(register_pack));
    read(*cfd, &server_response, sizeof(int));

    switch (server_response)
    {
    case name_exit_code:
        puts("**************************");
        printf("Username already exists.\n");
        puts("**************************");
        return login_failue_code;
        break;
    case register_successful_code:
        puts("**************************");
        printf("Register success.\n");
        puts("**************************");
        return register_successful_code;
        break;
    case register_failue_code:
        puts("**************************");
        printf("Register fail\n ");
        puts("**************************");
        return login_failue_code;
        break;
    case login_successfully_code:
        puts("**************************");
        printf("login successful.\n");
        puts("**************************");
        return login_successfully_code;
        break;
    case login_failue_code:
        puts("**************************");
        printf("login failue.\n");
        puts("**************************");
        return login_failue_code;
    case have_down_exception_code:
        puts("**************************");
        printf("down_exception.\n");
        puts("**************************");
        return have_down_exception_code;
    case have_up_exception_code:
        puts("**************************");
        printf("up_exception.\n");
        puts("**************************");
        return have_up_exception_code;
        break;

    default:
        break;
    }
}

服务器:

// 空了实现一下防止sql注入,保证数据完整传输的两种方法、解决一下套接字复用的问题(使用poll模型),让多文件多线程接收实现真并发
// 完善一下文件存在的问题设定一个数据库,写入文件名,设定返回值用于返回文件上传的结果
// 接收完毕,向客户端发送发送完毕的码,表示服务器以及正确接收文件

/*
 * **************************************************************************
 * ********************                                  ********************
 * ********************      COPYRIGHT INFORMATION       ********************
 * ********************                                  ********************
 * **************************************************************************
 *                                                                          *
 *                                   _oo8oo_                                *
 *                                  o8888888o                               *
 *                                  88" . "88                               *
 *                                  (| -_- |)                               *
 *                                  0\  =  /0                               *
 *                                ___/'==='\___                             *
 *                              .' \\|     |// '.                           *
 *                             / \\|||  :  |||// \                          *
 *                            / _||||| -:- |||||_ \                         *
 *                           |   | \\\  -  /// |   |                        *
 *                           | \_|  ''\---/''  |_/ |                        *
 *                           \  .-\__  '-'  __/-.  /                        *
 *                         ___'. .'  /--.--\  '. .'___                      *
 *                      ."" '<  '.___\_<|>_/___.'  >' "".                   *
 *                     | | :  `- \`.:`\ _ /`:.`/ -`  : | |                  *
 *                     \  \ `-.   \_ __\ /__ _/   .-` /  /                  *
 *                 =====`-.____`.___ \_____/ ___.`____.-`=====              *
 *                                   `=---=`                                *
 * **************************************************************************
 * ********************                                  ********************
 * ********************      				             ********************
 * ********************     佛祖保佑 永无BUG 永不宕机      ********************
 * ********************                                  ********************
 * **************************************************************************
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <dirent.h>
#include <netinet/tcp.h>
#include <errno.h>

#define SIZE 64
#define PORT 8888
#define quit_code 1
#define error_code 2
#define password_error_code 3
#define name_exit_code 4
#define register_successful_code 5
#define register_fali_code 6
#define login_successfully_code 7
#define login_failue_code 8
#define unrefister_code 9
#define upload_statu_code 10
#define download_statu_code 11
#define exit_upload_or_download_statu_code 12
#define end_download_statu_code 13
#define end_send_file_code 14
#define download_file_not_exist_code 15
#define begin_download_code 16
#define end 17

#define have_up_exception_code 21
#define have_down_exception_code 22

#define HEARTBEAT_CODE 20

typedef struct sockaddr_in sock;

typedef struct user
{
    char passwords[11];
    char name[15];
} usr;

typedef struct client
{
    char IP[15];
    char username[15];
    int nfd;
} client;

// 创建套接字,返回创建成功的套接字
int socket_creat()
{
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sfd)
    {
        perror("socket");
        return -1;
    }
    // 开启keepalive选项
    int enable = 1;
    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable));

    // 设置keepalive参数
    int idle = 15;     // 空闲时间15秒
    int interval = 15; // 每5秒发送一次
    int count = 3;     // 发送3次后仍无响应则断开连接
    setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
    setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
    setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));

    return sfd;
}
// 设置套接字选项,保证端口在断开后能立即使用
int set_socker_options(int sfd)
{
    int ok = 1;
    if (-1 == setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok)))
    {
        return -1;
    }
    return 0;
}
// 绑定套接字
int bind_socket(int sfd, sock *sddr)
{
    if (-1 == bind(sfd, (void *)sddr, sizeof(*sddr)))
    {
        perror("bind");
        return -1;
    }
    return 0;
}
// 监听套接字
int listen_socket(int sfd)
{
    if (-1 == listen(sfd, 20))
    {
        perror("listen");
    }

    puts("listen...");
}
// 接受连接
// 返回结构体
client accept_connection(int sfd, sock *cddr)
{
    int len1 = sizeof(*cddr);
    int nfd = accept(sfd, (void *)cddr, &len1);
    printf("IP:%s PORT:%d connected!\n", inet_ntoa(cddr->sin_addr), ntohs(cddr->sin_port));
    client client_message;
    strcpy(client_message.IP, inet_ntoa(cddr->sin_addr));
    client_message.nfd = nfd;
    return client_message;
}
// 接受文件
int recv_file(void *pnfd)
{
    int type; //
    client client_message = *(client *)pnfd;
    int nfd = client_message.nfd;
    printf("%s\n", client_message.username);
    while (1)
    {
        int file_name_len;

        int ret = read(nfd, &file_name_len, sizeof(int));
        if (ret <= 0) // 客户端已经断开连接或读取完一个文件
        {
            close(nfd);
            pthread_exit(NULL);
        }

        // 接收文件名
        char filename[file_name_len];
        read(nfd, filename, file_name_len);
        // 如果文件名为quit,则退出
        if (0 == strncmp("quit", filename, 4))
        {

            printf("客户端退出下载\n");
            return 0;
        }
        printf("uploading file %s\n", filename);
        // 创建文件
        int fd = open(filename, O_WRONLY | O_CREAT, 0664);
        if (-1 == fd)
        {
            perror("open");
            exit(error_code);
        }
        int file_size;
        read(nfd, &file_size, sizeof(int));
        // 接收文件内容并写入到文件中
        char buf[SIZE];
        int count;
        int size_judge = 0;
        while ((count = read(nfd, buf, SIZE)) > 0)
        {
            write(fd, buf, count);
            size_judge = size_judge + count;

            float progress = (float)size_judge / file_size * 100;
            printf("Upload progress: %.2f%%\r", progress);
            fflush(stdout);

            if (size_judge == file_size)
            {
                close(fd);
                puts("Done");
                break;
            }
        }

        if (size_judge < file_size)
        {
            printf("文件未传输完毕,记录信息至数据库\n");
            sqlite3 *db;
            char *errmsg;
            type = have_down_exception_code;

            if (sqlite3_open("Exception.db", &db) != SQLITE_OK)
            {
                printf("Can't open database: %s\n", sqlite3_errmsg(db));
            }

            char sql_command_insert[256];
            sprintf(sql_command_insert, "INSERT INTO exception (ip_username, filename, type, transferred) VALUES('%s_%s', '%s', '%d', %d);", client_message.IP, client_message.username, filename, type, size_judge);

            if (sqlite3_exec(db, sql_command_insert, NULL, NULL, &errmsg) != SQLITE_OK)
            {
                printf("SQL error: %s\n", errmsg);
                sqlite3_free(errmsg);
            }

            sqlite3_close(db);
            close(nfd);
            printf("客户端断开连接,线程退出\n");
            pthread_exit(NULL);
            return 1;
        }
    }
}

int exception_recv_file(void *pnfd, char *filename, int offset)
{
    int type = have_down_exception_code;
    client client_message = *(client *)pnfd;
    int nfd = client_message.nfd;
    while (1)
    {
        printf("开始接收未传输完毕的文件: %s\n", filename);
        // 创建文件
        int fd = open(filename, O_WRONLY | O_CREAT, 0664);
        if (-1 == fd)
        {
            perror("open");
            exit(error_code);
        }
        // 移动文件指针到指定的偏移量
        lseek(fd, offset, SEEK_SET);
        int file_size;
        read(nfd, &file_size, sizeof(int));
        // 接收文件内容并写入到文件中
        char buf[SIZE];
        int count;
        int size_judge = offset;
        printf("从%d字节开始传输,文件总大小:%d\n", size_judge, file_size);
        while ((count = read(nfd, buf, SIZE)) > 0)
        {
            write(fd, buf, count);
            size_judge += count;
            float progress = (float)size_judge / file_size * 100;
            printf("Upload progress: %.2f%%\r", progress);
            fflush(stdout);
            if (size_judge == file_size)
            {
                close(fd);
                puts("Done");
                break;
            }
        }

        if (size_judge < file_size)
        {
            printf("文件未传输完毕,记录信息至数据库\n");
            sqlite3 *db;
            char *errmsg;
            type = have_down_exception_code;
            if (sqlite3_open("Exception.db", &db) != SQLITE_OK)
            {
                printf("Can't open database: %s\n", sqlite3_errmsg(db));
            }

            char sql_command_insert[256];
            sprintf(sql_command_insert, "INSERT INTO exception (ip_username, filename, type, transferred) VALUES('%s_%s', '%s', '%d', %d);", client_message.IP, client_message.username, filename, type, size_judge);

            if (sqlite3_exec(db, sql_command_insert, NULL, NULL, &errmsg) != SQLITE_OK)
            {
                printf("SQL error: %s\n", errmsg);
                sqlite3_free(errmsg);
            }
            printf("客户端已断开连接\n");
            close(fd);
            sqlite3_close(db);
            pthread_exit(NULL);
            return 1;
        }
        else if (size_judge == file_size)
        {
            printf("传输完毕!\n");
            // 打开数据库
            sqlite3 *db;
            char *errmsg;
            if (sqlite3_open("Exception.db", &db) != SQLITE_OK)
            {
                printf("Can't open database: %s\n", sqlite3_errmsg(db));
                return -1;
            }

            // 创建SQL DELETE语句
            char sql_command_delete[256];
            sprintf(sql_command_delete, "DELETE FROM exception WHERE ip_username = '%s_%s' AND filename = '%s';", client_message.IP, client_message.username, filename);

            // 执行SQL DELETE语句
            if (sqlite3_exec(db, sql_command_delete, NULL, NULL, &errmsg) != SQLITE_OK)
            {
                printf("SQL error: %s\n", errmsg);
                sqlite3_free(errmsg);
            }

            // 关闭数据库
            sqlite3_close(db);
            return 1;
        }
    }
}

// 总初始化函数
int srver_fd_init(sock *sddr, int *sfd)
{
    sddr->sin_family = AF_INET;
    sddr->sin_port = htons(PORT);
    sddr->sin_addr.s_addr = INADDR_ANY;
    *sfd = socket_creat();
    set_socker_options(*sfd);
    bind_socket(*sfd, sddr);
    listen_socket(*sfd);

    sqlite3 *db; // database
    char *errmsg;

    if (SQLITE_OK != sqlite3_open("Exception.db", &db))
    {
        fprintf(stderr, "%s", sqlite3_errmsg(db));
        return -1;
    }

    char *sql_command_create = {"create table if not exists exception(ip_username text PRIMARY KEY, filename text, type text, transferred integer);"};

    if (SQLITE_OK != sqlite3_exec(db, sql_command_create, NULL, NULL, &errmsg))
    {
        printf(" errmsg:%s", errmsg);
        return -1;
    }

    sqlite3_close(db);
}

// 定义一个函数,用于获取从服务器端来的注册信息

int get_Register(char *usr, char *psd, int *response_code)
{
    sqlite3 *db; // database
    char *errmsg;

    if (SQLITE_OK != sqlite3_open("User.db", &db))
    {
        fprintf(stderr, "%s", sqlite3_errmsg(db));
        return -1;
    }

    // AUTOINCREMENT表示自动生成id,INCREMENT表示自动增加
    char *sql_command_create = {"create table if not exists logfile(id INTEGER PRIMARY KEY AUTOINCREMENT, name text,  password text);"};
    // 创建表:表不存在则创建
    if (SQLITE_OK != sqlite3_exec(db, sql_command_create, NULL, NULL, &errmsg))
    {
        printf(" errmsg:%s", errmsg);
        return -1;
    }
    const char *sql_id_get = "SELECT id FROM logfile ORDER BY id DESC LIMIT 1;";

    char sql_command_insert[356];
    sprintf(sql_command_insert, "INSERT INTO logfile (name, password) VALUES('%s', '%s');", usr, psd);

    // 查询用户是否存在
    char sql_command_check[256];
    sprintf(sql_command_check, "SELECT * FROM logfile WHERE name = '%s';", usr);
    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, sql_command_check, -1, &stmt, NULL) == SQLITE_OK)
    {
        if (sqlite3_step(stmt) == SQLITE_ROW)
        {

            *response_code = name_exit_code;
            sqlite3_finalize(stmt);
            sqlite3_close(db);
            return name_exit_code; // 返回一个错误值
        }
        else
        {
            if (SQLITE_OK != sqlite3_exec(db, sql_command_insert, NULL, NULL, &errmsg))
            {
                printf(" Insert:%s", errmsg);
                return register_fali_code;
            }
            *response_code = register_successful_code;
            return register_successful_code;
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);
}
// 定义一个函数用于处理登录验证
int login_check(char *usr, char *psd, int *response_code)
{
    sqlite3 *db; // database
    int Back_val;
    if (SQLITE_OK != sqlite3_open("User.db", &db))
    {
        fprintf(stderr, "%s", sqlite3_errmsg(db));
        return -1;
    }

    char sql_command_check[256];
    sprintf(sql_command_check, "SELECT * FROM logfile WHERE name = '%s' AND password = '%s';", usr, psd);

    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, sql_command_check, -1, &stmt, NULL) == SQLITE_OK)
    {

        int ret = sqlite3_step(stmt);
        sqlite3_reset(stmt);

        if (ret == SQLITE_ROW)
        {
            printf("login successfully.\n");
            *response_code = login_successfully_code;
            Back_val = login_successfully_code; // 返回
        }
        else
        {
            printf("login fail.\n");
            *response_code = login_failue_code;
            Back_val = login_failue_code;
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return Back_val;
}

// 定义一个函数,用于回复客户端的下载指令
// 文件发送函数:

int open_send_file(int *nfd, char *filename)
{
    int file_download_status;
    int fd;
    // 如果文件名为quit,则退出
    if (0 == strncmp("quit", filename, 4))
    {
        return end_download_statu_code;
    }
    fd = open(filename, O_RDONLY);
    if (-1 == fd)
    {
        perror("open");
        return download_file_not_exist_code;
    }
    else
    {
        file_download_status = begin_download_code;
        write(*nfd, &file_download_status, sizeof(int));
    }

    char buf[SIZE];
    int count;
    // 获取文件大小
    struct stat st;
    fstat(fd, &st);
    int file_size = st.st_size;
    printf("%d\n", file_size);
    write(*nfd, &file_size, sizeof(int));
    while ((count = read(fd, buf, SIZE)) > 0)
    {
        write(*nfd, buf, count);
    }
    puts("Done");
    close(fd);
    return end;
}

int send_file(int *pnfd)
{

    int nfd = *pnfd;
    DIR *d;
    struct dirent *dir;
    char send_status_buffer[128] = {"END_SEND_FILENAME"}; // 17
    char download_filenaeme_buffer[128] = {0};
    int file_download_status;
    int filename_buffer_size = 128;
    // char filename_buffer[128];
    d = opendir(".");
    if (d)
    {
        while ((dir = readdir(d)) != NULL)
        {
            if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0)
            {
                continue;
            }
            write(nfd, dir->d_name, filename_buffer_size);
            //   write(nfd, "\n", 1); // 换行,以便客户端可以区分不同的文件名
        }
        // 发送完毕发送结束字符
        write(nfd, send_status_buffer, filename_buffer_size);
        closedir(d);
    }
    else
    {
        perror("opendir");
    }
    // 开始接收函数
    while (1)
    {
        int file_name_len;
        int ret = read(nfd, &file_name_len, sizeof(int));
        if (ret <= 0) // 客户端已经断开连接或读取完一个文件
        {
            return 0;
        }
        read(nfd, download_filenaeme_buffer, file_name_len);
        if (0 != strncmp("quit", download_filenaeme_buffer, 4))
            printf("doloding_file:%s", download_filenaeme_buffer);
        file_download_status = open_send_file(&nfd, download_filenaeme_buffer);
        if (end_download_statu_code == file_download_status)
        {
            puts("Download status quit");
            //  write(nfd, &file_download_status, sizeof(int));
            return end_download_statu_code;
        }
        else if (download_file_not_exist_code == file_download_status)
        {
            write(nfd, &file_download_status, sizeof(int));
            return download_file_not_exist_code;
        }
        else if (end == file_download_status)
        {
            break;
        }
    }
}

// 定义一个函数用于解析登录注册包
int analysis(char *client_comond_buf, void *nfds, int *is_logged_in_addr)
{

    int response_code;
    int is_logged_in = *is_logged_in_addr;
    char *p = client_comond_buf;
    char username_buffer[15] = {0};
    char password_buffer[65] = {0};
    char *usr = username_buffer;
    char *psd = password_buffer;

    client client_message = *(client *)nfds;
    int *nfd = &client_message.nfd;

    char *k = p + 1;
    // 解析用户名和密码
    while (*k != '\n')
    {
        *usr = *k;
        usr++;
        k++;
    }
    k = k + 1;
    while (*k)
    {
        *psd = *k;
        psd++;
        k++;
    }
    printf("analysis success\n");

    strcpy((*(client *)nfds).username, username_buffer);

    // 如果是注册包:
    if ('0' == *p)
    {
        printf("Register ....\n");
        int register_result = get_Register(username_buffer, password_buffer, &response_code);

        // 如果用户名以及存在,则返回客户端,用户已存在
        if (name_exit_code == register_result)
        {
            printf("Username already exists.\n");
            write(*nfd, &response_code, sizeof(int));
        }
        else if (register_successful_code == register_result)
        {
            write(*nfd, &response_code, sizeof(int));
            printf("Register success.\n");
        }
        else
        {
            response_code = register_fali_code;
            printf("Register fail.\n");
            write(*nfd, &response_code, sizeof(int));
        }
    }
    if ('1' == *p)
    {
        // 登录验证代码
        int login_result = login_check(username_buffer, password_buffer, &response_code);
        if (login_successfully_code == login_result)
        {

            // 查询异常数据库
            sqlite3 *db;
            char *errmsg;
            printf("开始查询异常数据库\n");
            if (sqlite3_open("Exception.db", &db) != SQLITE_OK)
            {
                printf("Can't open database: %s\n", sqlite3_errmsg(db));
                return -1;
            }
            char sql_command_check[256];
            sprintf(sql_command_check, "SELECT * FROM exception WHERE ip_username = '%s_%s';", client_message.IP, (*(client *)nfds).username);
            printf("%s\n", sql_command_check);
            sqlite3_stmt *stmt;
            if (sqlite3_prepare_v2(db, sql_command_check, -1, &stmt, NULL) == SQLITE_OK)
            {
                if (sqlite3_step(stmt) == SQLITE_ROW)
                {
                    // 有异常信息,返回给客户端
                    printf("有未接收完毕的文件\n");
                    char *filename = (char *)sqlite3_column_text(stmt, 1); // 此指针由sqlite3管理。关闭即释放
                    char *type = (char *)sqlite3_column_text(stmt, 2);
                    int send_type = 22;
                    write(*nfd, &send_type, sizeof(int));
                    // sleep(5);
                    int transferred = sqlite3_column_int(stmt, 3);
                    char message[256];
                    sprintf(message, "Filename: %s Type: %s Transferred: %d", filename, type, transferred);
                    printf("%s\n", message);
                    int message_len = strlen(message);
                    write(*nfd, &message_len, sizeof(int));
                    write(*nfd, message, message_len);

                    char my_filename[128];
                    strcpy(my_filename, filename);
                    printf("copy filename %s\n", my_filename);
                    sqlite3_finalize(stmt);
                    sqlite3_close(db);

                    exception_recv_file(&client_message, my_filename, transferred);
                }
                else
                {
                    // 无异常
                    printf("无异常\n");
                    write(*nfd, &response_code, sizeof(int));
                }
            }

            sqlite3_close(db);

            is_logged_in = 1; // 设置登录状态为已登录

            return login_successfully_code; // 返回一个成功值
        }
        else if (login_failue_code == login_result)
        {
            write(*nfd, &response_code, sizeof(int));
            printf("login failue.\n");
            return login_failue_code; // 返回一个失败值
        }
    }
}

// 定义一个回调函数,用于新线程解析并处理来自客户端的命令
void *analysis_connection(void *pnfd)
{
    int ret;
    int log_status;
    client client_message = *(client *)pnfd;
    int nfd = client_message.nfd; // 将nfd的值复制到新的int中
    char client_comond_buf[100];
    int is_logged_in = 0; // 登录状态局部变量
    while (1)
    {
        if (!is_logged_in) // 如果客户端还没有登录
        {
            // 从客户端获取信息,看是登录包还是注册包,登陆包完毕后,再执行接下来的操作
            ret = read(nfd, client_comond_buf, sizeof(client_comond_buf));
            if (ret <= 0) // 客户端已经断开连接或读取完一个文件
            {
                close(nfd);
                pthread_exit(NULL);
            }

            // 解析包
            log_status = analysis(client_comond_buf, &client_message, &is_logged_in);
            if (log_status == login_successfully_code)
            {

                is_logged_in = 1; // 设置登录状态为已登录
            }
        }

        if (is_logged_in) // 如果客户端已经登录
        {
            int should_continue = 1;
            while (should_continue) // 等待新指令的循环
            {
                // 接收指令

                struct timeval timeout;
                timeout.tv_sec = 3600; // 设置超时时间为3600秒
                timeout.tv_usec = 0;
                setsockopt(nfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

                int up_down_exit_heart_status;
                ret = recv(nfd, &up_down_exit_heart_status, sizeof(int), 0);
                if (ret <= 0) // 客户端已经断开连接或读取完一个文件
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK)
                    {
                        printf("客户端超时!t\n");
                    }
                    else
                    {
                        printf("客户端退出!\n");
                    }
                    is_logged_in = 0;
                    close(nfd);
                    pthread_exit(NULL);
                }

                switch (up_down_exit_heart_status)
                {
                case upload_statu_code:
                    puts("uploading.....");
                    recv_file(&client_message);
                    printf("接收状态退出\n");
                    break;
                case download_statu_code:
                    puts("downloading.....");
                    send_file(&nfd);
                    break;
                case exit_upload_or_download_statu_code:
                    // is_logged_in = 0;
                    should_continue = 0;
                    break;
                default:
                    // 处理未知的状态
                    break;
                }
            }
        }
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    char client_comond_buf[64];
    sock sddr, cddr;
    int sfd;
    // 初始化
    srver_fd_init(&sddr, &sfd);

    while (1)
    {
        // 此函数的accept函数会阻塞
        client client_message = accept_connection(sfd, &cddr);
        // 开线程,用于解析来自客户端的连接指令
        pthread_t tid_client_anlysis;
        if (0 != pthread_create(&tid_client_anlysis, NULL, analysis_connection, &client_message))
        {
            perror("pthread_creat");
            pthread_exit(NULL);
        }
        pthread_detach(tid_client_anlysis); // 分离线程,使其在结束时自动回收资源
    }

    close(sfd);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值