在线词典 | TCP并发服务器(线程池)

在线词典功能:

1.注册        2.登录        3.查询单词        4.查询历史记录

单词和解释保存在文件中,单词和解释只占一行,一行最多300个字节,单词和解释之间至少有一个空格

功能演示

客户端注册、登陆

在线词典 | TCP并发服务器(线程池)_第1张图片

 客户端查询单词

在线词典 | TCP并发服务器(线程池)_第2张图片

 客户端查询历史记录

在线词典 | TCP并发服务器(线程池)_第3张图片

 

线程池概念

         线程池是一种使用多线程去处理高并发的一种理念。其本身可以自己做到去接收任务,分配线程执行任务,并且可以按照需求量的变化去自动增加工作线程的数量,也可以减少工作线程的数量。并且单个线程线程可以重复使用,也解决了使用线程实现并发时的启动和关闭线程会大量的消耗系统资源的问题。

线程池原理

        线程池被创建并运行后,会有一个任务队列和一个管理者线程和规定数量的执行线程同时启动,管理者线程负责按照固定频率去监控任务队列和执行线程,根据任务量的大小实时改变线程池中执行线程的个数,来实现最高的并发效率。执行线程初期被创建时利用条件变量将自己挂起等待唤醒信号到来,当唤醒信号到来时,去任务队列中读取任务然后去执行,执行完毕后继续挂起等待。任务队列负责对外接收任务,先将任务做一个存储,来唤醒线程去执行。

线程池相关结构体封装

//线程池结构体
typedef struct {
    pthread_mutex_t pool_lock; //互斥锁
    pthread_cond_t cond; //条件变量
    worker* queuehead; //队列头
    int cur_queue_cont; //当前队列中成员个数
    pthread_t* pthread_num; //线程号,指针需要分配内存    //可以写成 pthread_t pthread_num[5];
    int max_thread; //创建的执行者线程个数
    int shutdown; //线程停止标志
} pool;

//任务结构体
typedef struct work {
    void* (process)(void arg); //执行任务的函数指针
    int connectfd; //参数1
    sqlite3* db; //参数2
    struct work* next; //指向队列的下一个成员
} worker;

TCP服务器主函数

#include "server.h"

#define DATABASE "my.db"

int main(int argc, char* argv[])
{
    int connectfd, listenfd;
    sqlite3* db;
    int i, num[10];
    
    worker* mywork = (worker*)malloc(sizeof(worker));

    //参数合理性检查
    if (argc < 3) {
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }

    //创建线程池
    create_poll(10);
    sleep(1); //等待线程池创建成功

    //打开数据库文件
    if (sqlite3_open(DATABASE, &db) != SQLITE_OK) {
        printf("error : %s\n", sqlite3_errmsg(db));
        exit(-1);
    }
    //创建套接字,
    listenfd = inet_init(argv[1], argv[2]); 
    while (1) {
    //等待客户端连接
        if ((connectfd = accept(listenfd, NULL, NULL)) < 0) {
            perror("fail to accept");
            exit(-1);
        }
        printf("客户端[%d]连接成功...\n", connectfd);
        
        //将客户端的加入任务队列
        mywork->connectfd = connectfd;
        mywork->db = db;
        add_work(do_client, (void*)mywork);
    }

    // destory 销毁线程池
    destory_pool();
    free(mypool);
    printf("free resource is ok.\n");

    return 0;
}

服务器.h文件

#ifndef __SERVER_H__
#define __SERVER_H__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//任务
typedef struct work {
    void* (*process)(void* arg); //执行任务的函数指针
    int connectfd; //参数1
    sqlite3* db; //传递的参数2

    struct work* next; //指向队列的下一个成员
} worker;

typedef struct {

    pthread_mutex_t pool_lock; //互斥锁
    pthread_cond_t cond; //条件变量

    worker* queuehead; //队列头
    int cur_queue_cont; //当前队列中成员个数

    pthread_t* pthread_num; //线程号,指针需要分配内存
    //可以写成 pthread_t pthread_num[5];
    int max_thread; //创建的线程的个数
    int shutdown; //线程停止标志
} pool;

pool* mypool; //定义线程池
worker* mywork;

void* thread_routine(void* args);
void create_poll(int num);
void add_work(void* (*process)(void*), void* args);
void destory_pool();

// server
#define N 16
#define R 1 //  user register
#define L 2 //  user login
#define Q 3 //  query word
#define H 4 //  history record
typedef struct
{
    int type;
    char name[N];
    char data[256]; // password or word
} MSG;

int inet_init();
void do_register(int connectfd, MSG* msg, sqlite3* db);
void do_login(int connectfd, MSG* msg, sqlite3* db);
void do_query(int connectfd, MSG* msg, sqlite3* db);
void do_history(int connectfd, MSG* msg, sqlite3* db);
void* do_client(void* args);
int do_searchword(int connectfd, MSG* msg);
void getdata(char data[]);
int history_callback(void* arg, int f_num, char** f_value, char** f_name);

#endif

服务器功能函数实现

#include "server.h"

int inet_init(char* argv1, char* argv2)
{
    int connectfd, listenfd;
    struct sockaddr_in server_addr;

    if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("fail to socket");
        exit(-1);
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = PF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv1);
    server_addr.sin_port = htons(atoi(argv2));

    if (bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("fail to bind");
        exit(-1);
    }

    if (listen(listenfd, 5) < 0) {
        perror("fail to listen");
        exit(-1);
    }
    return listenfd;
}

void* do_client(void* args)
{
    MSG msg;

    worker* mywork = (worker*)malloc(sizeof(worker));
    mywork = (worker*)args;

    while (recv(mywork->connectfd, &msg, sizeof(MSG), 0) > 0) // receive request
    {
        printf("type = %d\n", msg.type);
        printf("type = %s\n", msg.data);
        switch (msg.type) {
        case R:
            do_register(mywork->connectfd, &msg, mywork->db);
            break;
        case L:
            do_login(mywork->connectfd, &msg, mywork->db);
            break;
        case Q:
            do_query(mywork->connectfd, &msg, mywork->db);
            break;
        case H:
            do_history(mywork->connectfd, &msg, mywork->db);
            break;
        }
    }
    printf("client quit\n");
    return NULL;
}

void do_register(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[512] = { 0 };
    char* errmsg;

    //使用sqlite3_exec函数调用插入函数判断是否能够插入成功
    //由于用户名设置为主键,所以如果用户名已经存在就会报错
    sprintf(sqlstr, "insert into usr values('%s', '%s')", msg->name, msg->data);
    if (sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg) != SQLITE_OK) {
        sprintf(msg->data, "user %s already exist!!!", msg->name);
    } else {
        strcpy(msg->data, "OK");
    }

    send(connectfd, msg, sizeof(MSG), 0);

    return;
}

void do_login(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[512] = { 0 };
    char *errmsg, **result;
    int nrow, ncolumn;

    //通过sqlite3_get_table函数查询记录是否存在
    sprintf(sqlstr, "select * from usr where name = '%s' and pass = '%s'", msg->name, msg->data);
    if (sqlite3_get_table(db, sqlstr, &result, &nrow, &ncolumn, &errmsg) != SQLITE_OK) {
        printf("error : %s\n", errmsg);
    }
    //通过nrow参数判断是否能够查询到疾记录,如果值为0,则查询不到,如果值为非0,则查询到
    if (nrow == 0) {
        strcpy(msg->data, "name or password is wrony!!!");
    } else {
        strncpy(msg->data, "OK", 256);
    }

    send(connectfd, msg, sizeof(MSG), 0);

    return;
}

void do_query(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[128], *errmsg;
    int found = 0;
    char date[128], word[128];

    strcpy(word, msg->data);

    //通过found保存查询结果
    found = do_searchword(connectfd, msg);

    //如果执行成功,还需要保存历史记录
    if (found == 1) {
        //获取时间
        getdata(date);
        //通过sqlite3_exec函数插入数据
        sprintf(sqlstr, "insert into record values('%s', '%s', '%s')", msg->name, date, word);
        if (sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg) != SQLITE_OK) {
            printf("error : %s\n", errmsg);
        }
    }

    send(connectfd, msg, sizeof(MSG), 0);

    return;
}

int do_searchword(int connectfd, MSG* msg)
{
    FILE* fp;
    char temp[300];
    char* p;
    int len, result;
    //保存单词的长度
    len = strlen(msg->data);
    //打开保存单词的文件
    if ((fp = fopen("dict.txt", "r")) == NULL) {
        strcpy(msg->data, "dict can not open");
        send(connectfd, msg, sizeof(MSG), 0);
    }
    // printf("query word is %s len = %d\n", msg->data, len);

    //每次读取一行内容
    int flags = 0;
    while (fgets(temp, 300, fp) != NULL) {
        //比较单词
        result = strncmp(msg->data, temp, len);

        if (result == 0 && temp[len] == ' ') {
            // p保存单词后面第一个空格的首地址
            p = temp + len;

            //移动p,让p保存解释的第一个字符的首地址
            while (*p == ' ') {
                p++;
            }

            //将解释保存在data里面
            strcpy(msg->data, p);

            fclose(fp);
            return 1;
        }
    }

    strcpy(msg->data, "not found");
    fclose(fp);
    return 0;
}

void getdata(char* data)
{
    time_t t;
    struct tm* tp;
    time(&t);
    tp = localtime(&t);
    sprintf(data, "%d-%d-%d %d:%d:%d", 1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday,
        tp->tm_hour, tp->tm_min, tp->tm_sec);
}

void do_history(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[128], *errmsg;

    //查询历史表
    sprintf(sqlstr, "select * from record where name = '%s'", msg->name);
    if (sqlite3_exec(db, sqlstr, history_callback, (void*)&connectfd, &errmsg) != SQLITE_OK) {
        printf("error : %s\n", errmsg);
        sqlite3_free(errmsg);
    }

    //发送结束标志
    strcpy(msg->data, "**OVER**");
    send(connectfd, msg, sizeof(MSG), 0);

    return;
}

//通过回调函数发送时间和单词
int history_callback(void* arg, int f_num, char** f_value, char** f_name)
{
    int connectfd;
    MSG msg;
    connectfd = *(int*)arg;
    sprintf(msg.data, "%s : %s", f_value[1], f_value[2]);
    send(connectfd, &msg, sizeof(msg), 0);
    return 0;
}

void create_poll(int num)
{
    int i, ret;
    //申请线程池空间
    mypool = (pool*)malloc(sizeof(*mypool));
    if (mypool == NULL) {
        printf("malloc pool memory is fail.\n");
        return;
    }

    //初始化线程池

    //初始化互斥锁
    pthread_mutex_init(&mypool->pool_lock, NULL);

    //初始化一个条件变量
    pthread_cond_init(&mypool->cond, NULL);

    // pthread_cond_t cond= PTHREAD_COND_INITIALIZER;
    //  参数 :
    //      @cond:条件变量
    //      @attr : NULL(linux 线程中忽略)
    //  返回值 : 函数成功返回0;
    //           任何其他返回值都表示错误

    //初始化
    mypool->queuehead = NULL; //队列头
    mypool->shutdown = 0; //线程停止标志
    mypool->max_thread = num; //创建的线程个数

    //分配存储线程号的内存
    mypool->pthread_num = (pthread_t*)malloc(num * sizeof(pthread_t));

    //创建执行者线程,分配线程号
    for (i = 0; i < num; i++) {
        // 1.指向线程的指针,2.线程属性,3.线程处理函数的首地址,4.运行函数的参数
        ret = pthread_create(&(mypool->pthread_num[i]), NULL, thread_routine, (void*)&(mypool->pthread_num[i]));
        if (ret != 0) {
            printf("create thread is fail.\n");
        }
    }
}

//销毁线程池
void destory_pool()
{
    int i;

    //标识线程停止
    mypool->shutdown = 1;

    //唤醒所有线程
    pthread_cond_broadcast(&mypool->cond);
    for (i = 0; i < mypool->max_thread; i++) {
        //等待线程结束,回收线程资源
        pthread_join(mypool->pthread_num[i], NULL);
    }
    //释放线程所使用空间
    free(mypool->pthread_num);

    //
    worker* tmp;
    while (mypool->queuehead) {
        tmp = mypool->queuehead;
        mypool->queuehead = mypool->queuehead->next;
        free(tmp);
    }
    //销毁互斥锁
    pthread_mutex_destroy(&mypool->pool_lock);
    //销毁条件变量
    pthread_cond_destroy(&mypool->cond);
}

//向任务队列中添加任务
void add_work(void* (*process)(void*), void* args)
{
    worker* mywork = (worker*)malloc(sizeof(worker));
    mywork = (worker*)args;
    //申请任务节点空间
    worker* work = (worker*)malloc(sizeof(worker));
    //初始化任务节点
    work->process = process;
    work->db = mywork->db;
    work->connectfd = mywork->connectfd;
    work->next = NULL;

    //申请互斥锁
    pthread_mutex_lock(&mypool->pool_lock);

    //任务队列头
    worker* pwork = mypool->queuehead;

    //向队列中添加成员
    if (pwork != NULL) {
        while (pwork->next != NULL)
            pwork = pwork->next;
        pwork->next = work;
    } else {
        mypool->queuehead = work;
    }
    mypool->cur_queue_cont++;

    //释放互斥锁
    pthread_mutex_unlock(&mypool->pool_lock);

    //唤醒线程池中的一个线程,执行线程处理函数do_client
    pthread_cond_signal(&mypool->cond);
    //    int pthread_cond_broadcast(pthread_cond_t *cond);
    //    int pthread_cond_signal(pthread_cond_t * cond);
    // 功能:
    //     唤醒pthread_cond_wait阻塞的线程
    // 区别:
    //     pthread_cond_signal: 唤醒条件变量上的一个线程
    //     pthread_cond_broadcast : 唤醒条件变量上所有的线程
}

//参数为执行线程的首地址
void* thread_routine(void* args)
{
    //获取当前线程号
    pthread_t* tid = (pthread_t*)args;
    printf("start thread id = %ld.\n", *tid);

    while (1) {
        //添加互斥锁
        pthread_mutex_lock(&mypool->pool_lock);

        if (mypool->cur_queue_cont == 0 && !mypool->shutdown) {
            //如果队列中没有任务
            //使线程阻塞休眠
            pthread_cond_wait(&mypool->cond, &mypool->pool_lock);
        }

        if (mypool->shutdown) {
            //释放互斥锁
            pthread_mutex_unlock(&mypool->pool_lock);
            //退出线程,回收资源
            pthread_exit(NULL);
        }

        //队列中成员-1
        mypool->cur_queue_cont--;
        //队列头
        worker* work = mypool->queuehead;
        //队列头指向队列的下一个任务
        mypool->queuehead = work->next;
        //释放互斥锁
        pthread_mutex_unlock(&mypool->pool_lock);

        work->process((void*)work);
    }
}

客户端实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 16
#define R 1 //  user register
#define L 2 //  user login
#define Q 3 //  query word
#define H 4 //  history record

#define DATABASE "my.db"

typedef struct
{
    int type;
    char name[N];
    char data[256]; // password or word or remark
} MSG;

void do_register(int socketfd, MSG* msg);
int do_login(int socketfd, MSG* msg);
void do_query(int socketfd, MSG* msg);
void do_history(int socketfd, MSG* msg);

int main(int argc, char* argv[])
{
    int socketfd;
    struct sockaddr_in server_addr;
    MSG msg;
    if (argc < 3) {
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }
    if (-1 == (socketfd = socket(PF_INET, SOCK_STREAM, 0))) {
        perror("fail to socket");
        exit(-1);
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = PF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(atoi(argv[2]));

    if (-1 == connect(socketfd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
        perror("fail to connect");
        exit(-1);
    }

    int choose = 0;
    while (1) {
        printf("************************************\n");
        printf("* 1: register   2: login   3: quit *\n");
        printf("************************************\n");
        printf("please choose : ");

        if (scanf("%d", &choose) <= 0) {
            perror("scanf");
            exit(-1);
        }

        switch (choose) {
        case 1:
            do_register(socketfd, &msg);
            break;
        case 2:
            //执行登录函数,执行完毕后通过返回值决定是否要跳转到下一个菜单
            if (do_login(socketfd, &msg) == 1) {
                goto next;
            }
            break;
        case 3:
            close(socketfd);
            exit(0);
        }
    }
next:
    while (1) {
        printf("************************************\n");
        printf("* 1: query   2: history   3: quit  *\n");
        printf("************************************\n");
        printf("please choose : ");

        if (scanf("%d", &choose) <= 0) {
            perror("scanf");
            exit(-1);
        }
        switch (choose) {
        case 1:
            do_query(socketfd, &msg);
            break;
        case 2:
            do_history(socketfd, &msg);
            break;
        case 3:
            close(socketfd);
            exit(0);
        }
    }
    return 0;
}

void do_register(int socketfd, MSG* msg)
{
    //指定操作码
    msg->type = R;
    //输入用户名
    printf("input your name:");
    scanf("%s", msg->name);
    //输入密码
    printf("input your password:");
    scanf("%s", msg->data);
    //发送数据
    send(socketfd, msg, sizeof(MSG), 0);
    //接收数据并输出
    recv(socketfd, msg, sizeof(MSG), 0);
    printf("register : %s\n", msg->data);

    return;
}

int do_login(int socketfd, MSG* msg)
{
    //设置操作码
    msg->type = L;
    //输入用户名
    printf("input your name:");
    scanf("%s", msg->name);
    //输入密码
    printf("input your password:");
    scanf("%s", msg->data);
    //发送数据给服务器
    send(socketfd, msg, sizeof(MSG), 0);
    //接收服务器发送的数据
    recv(socketfd, msg, sizeof(MSG), 0);

    //判断是否登录成功
    if (strncmp(msg->data, "OK", 3) == 0) { //用3  可以防止  OK 和 OKkshdfkj
        //登录成功返回1
        printf("login : OK\n");
        return 1;
    }

    //登录失败返回0
    printf("login : %s\n", msg->data);
    return 0;
}

void do_query(int socketfd, MSG* msg)
{
    msg->type = Q;
    puts("-----------------------------");

    while (1) {
        printf("input word (if # is end): ");
        scanf("%s", msg->data);

        //如果输入的是#,返回上一级
        if (strcmp(msg->data, "#") == 0) {
            break;
        }

        send(socketfd, msg, sizeof(MSG), 0);

        recv(socketfd, msg, sizeof(MSG), 0);
        printf(">>> %s\n", msg->data);
    }
    return;
}

void do_history(int socketfd, MSG* msg)
{
    msg->type = H;
    send(socketfd, msg, sizeof(MSG), 0);

    while (1) {
        recv(socketfd, msg, sizeof(MSG), 0);

        if (strcmp(msg->data, "**OVER**") == 0) {
            break;
        }

        printf("%s\n", msg->data);
    }

    return;
}

你可能感兴趣的:(网络编程,c语言,tcp/ip,sqlite)