C语言 Linux网络编程(C/S架构) 在线词典

项目介绍

描述:
通过C/S架构实现在线词典,用户在客户端可以注册,登陆,然后可以查询单词,并且保存自己的单词查询记录。

知识点:

  • c语言进阶 Linux基础
  • C/S架构
  • 进程
  • sqlite3数据库
  • 时间函数
  • Makefile

效果图:
C语言 Linux网络编程(C/S架构) 在线词典_第1张图片
C语言 Linux网络编程(C/S架构) 在线词典_第2张图片
C语言 Linux网络编程(C/S架构) 在线词典_第3张图片

客户端

创建一个dict_client文件夹,存放客户端代码

client.h

#ifndef CLIENT_H
#define CLIENT_H
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 32

#define R 1 //user - register
#define L 2 //user - login
#define Q 3 //user - query
#define H 4 //user - history

//定义通信双方的信息结构体
typedef struct{
    int type;
    char name[N];
    char data[256]; //password或word信息
}MSG;

int do_register(int sockfd, MSG *msg);
int do_login(int sockfd, MSG *msg);
int do_query(int sockfd, MSG *msg);
int do_history(int sockfd, MSG *msg);
int client_connect(char *ip, int port);
#endif

cli_dict.c

#include "client.h"

int do_register(int sockfd, MSG *msg){
	msg->type = R;
    printf("Input name:");
    scanf("%s", msg->name);
    getchar();
    printf("Input password:");
    scanf("%s",msg->data);

    if(send(sockfd,msg,sizeof(MSG),0)<0)
    {
        printf("fail to send.\n");
        return -1;
    }

    if(recv(sockfd,msg,sizeof(MSG),0)<0)
    {
        printf("Fail to recv.\n");
        return -1;
    }
    //ok or user alread exist
    printf("%s\n", msg->data);
    return 0;
}
int do_login(int sockfd, MSG *msg){
	msg->type = L;
    printf("Input name:");
    scanf("%s", msg->name);
    getchar();
    printf("Input password:");
    scanf("%s",msg->data);
    if(send(sockfd,msg,sizeof(MSG),0) < 0)
    {
        printf("fail to send.\n");
        return -1;
    }
    if(recv(sockfd,msg,sizeof(MSG),0)<0)
    {
        printf("fail to recv.\n");
        return -1;
    }
    if(strncmp(msg->data,"OK",3)==0)
    {
        printf("OK\n");
        return 1;
    }    
    else{
        printf("%s\n", msg->data);
    }
    return 0;
}
int do_query(int sockfd, MSG *msg){
	puts("查询--------------");
    char name[10]={0};
    strcpy(name,msg->name);
    while(1){
        msg->type = Q;
        strcpy(msg->name,name);
        printf("Input word[quit:#]:");
        scanf("%s", msg->data);
        //printf("%s %s\n",msg->name,msg->data);
        //客户端输入# 返回上一级菜单
        if(strncmp(msg->data,"#",1)==0)
            break;
        //将要查询的单词发送给服务器
        if(send(sockfd, msg, sizeof(MSG), 0)<0)
        {
            printf("Fail to send.\n");
            return -2;
        }
        //等待接收服务器传递回来的单词注释信息
        if(recv(sockfd, msg, sizeof(MSG), 0) <  0)
        {
            printf("Fail to recv.\n");
            return -2;
        }
        printf("%s\n", msg->data);
    }
    return 0;
}
int do_history(int sockfd, MSG *msg){
	msg->type = H;
    send(sockfd,msg,sizeof(MSG),0);
    printf("%s的查找记录--------\n", msg->name);
    //接收服务器传递回来的历史记录信息
    while(1)
    {
        recv(sockfd,msg,sizeof(MSG),0);
        if('\0'==msg->data[0])
            break;
        //打印历史记录信息
        printf("%s\n",msg->data);
    }
    return 0;
}
int client_connect(char *ip, int port){
	int sockfd;
    struct sockaddr_in serveraddr;
    //创建流式套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("fail to socket.\n");
        return -1;
    }
    //给服务器地址初始化
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(ip);
    serveraddr.sin_port = htons(port);
    //连接服务器
    if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
        perror("fail to connect");
        return -1;
    }
    return sockfd;
}

main.c

#include "client.h"
int main(int argc, char *argv[])
{ 
    int n,m;
    char dart1[32],dart2[32];
    MSG msg;
    if(argc != 3){
        printf("Usage:%s serverip port\n", argv[0]);
        return -1;
    }
    int sockfd = client_connect(argv[1], atoi(argv[2]));
    //一级菜单
    while(1){
    	
        printf("##################################################\n");
        printf("*1.register         2.login               3.quit*\n");
        printf("##################################################\n");
        printf("Please choose:");
        scanf("%d", &n);
        getchar();
        switch(n){
            case 1:
                do_register(sockfd, &msg);
                break;
            case 2:
                if(do_login(sockfd, &msg) == 1){
                    goto next;
                }
                break;
            case 3:
                close(sockfd);
                exit(0);
                break;
            default:
                printf("Invalid data cmd.\n");
                n = 0;
                break;	//若没有break,输入aaa会执行3次switch
        }
    }
//二级菜单
next:  
    while(1){
    	
        printf("###########################################\n");
        printf("*1.query        2.history           3.quit*\n");
        printf("###########################################\n");
        printf("Please choose:");
        
        getchar();
        scanf("%d", &m);
        switch(m){
            case 1:
                do_query(sockfd, &msg);
                break;
            case 2:
                do_history(sockfd, &msg);
                break;
            case 3:
                close(sockfd);
                exit(0);
                break;
            default:
                printf("Invalid data cmd.\n");
                break;//若没有break,输入aaa会执行3次switch
        }
    }
    return 0;
} 

Makefile

创建Makefile 文件,也可以不做这步,只是多敲几串命令而已,Makefile最大优点就是直接输入make命令就能编译文件,也挺方便。

cli: main.c cli_dict.c
	gcc *.c -o cli

服务器端

sqlite3

需要在linux中安装sqlite3,没有安装的输入这两条命令

sudo apt-get install sqlite3
sudo apt-get install libsqlite3-dev

然后创建数据库 sqlite3 dictionaryOL.db;
在sqlite3 shell中创建表

# 改变user表结构
CREATE TABLE user(id INTEGER PRIMARY KEY,name char unique,pwd char);
# 创建dict 存放字典内容(单词,含义)
CREATE TABLE dict(dict_id int primary key,word char,mean char);
# 统计字典里有多个意思的单词
select word from dict group by word having count(*)>=2;
# 创建record 记录用户所查询过的单词(姓名,时间,单词)
CREATE TABLE record(name char,date char,word char);

dict表需要自己插入单词和含义。

server.h

#ifndef SERVER_H
#define SERVER_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//时间函数
#include 
#include 
#define N 32

#define R 1 //user - register
#define L 2 //user - login
#define Q 3 //user - query
#define H 4 //user - history

//数据库
#define DATABASE "dictionaryOL.db"

//定义通信双方的信息结构体
typedef struct{
    int type;
    char name[N];
    char data[256]; //password或word信息
}MSG;
int do_client(int acceptfd,sqlite3 *db);
int do_register(int acceptfd, MSG *msg,sqlite3 *db);
int do_login(int acceptfd, MSG *msg,sqlite3 *db);
int do_query(int acceptfd, MSG *msg,sqlite3 *db);
int do_history(int acceptfd, MSG *msg,sqlite3 *db);
int search_callback(void *para,int f_num,char **f_value,char **f_name);
int history_callback(void *para,int f_num,char **f_value,char **f_name);
void get_date(char *date);
int do_client(int acceptfd,sqlite3 *db);
int server_init(char *ip, int port);

#endif

ser_dict.c

#include "server.h"

int do_register(int acceptfd, MSG *msg,sqlite3 *db){
	char *errmsg;
    char sql[512];
    sprintf(sql,"insert into user values(null,\"%s\",\"%s\");",msg->name,msg->data);
    printf("%s\n", sql);
    if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK)
    {
        printf("%s\n", errmsg);
        strcpy(msg->data, "usr name already exist.");
    }
    else{
        printf("client register ok!\n");
        strcpy(msg->data,"OK!");
    }
    if(send(acceptfd,msg,sizeof(MSG),0)<0)
    {
        perror("fail to send");
        return -1;
    }
    return 0;
}
int do_login(int acceptfd, MSG *msg,sqlite3 *db){
	char sql[512] = {0};
    char *errmsg;
    int nrow;
    int ncloumn;
    char **resultp;
    sprintf(sql,
    "select * from user where name='%s' and pwd='%s';",
    msg->name,msg->data);
    printf("%s\n", sql);
    if(sqlite3_get_table(db,sql,&resultp,
                &nrow,&ncloumn,&errmsg))
    {
        printf("%s\n", errmsg);
        return -1;
    }
    if(nrow == 1)
    {
        //查询成功,有此用户
        strcpy(msg->data,"OK");
        send(acceptfd,msg,sizeof(MSG),0);
        return 1;
    }
    else
    {
        //密码或者用户名错误
        strcpy(msg->data,"user/password/worng");
        send(acceptfd,msg,sizeof(MSG),0);
        return 0;
    }
    return 1;
}
int do_query(int acceptfd, MSG *msg,sqlite3 *db){
	printf("正在查询....");
    char word[256]={0};
    char mean[100]={0};
    int found=0;
    char date[255]={0};
    char sql[1024]={0};
    char *errmsg;
    //msg结构体中要查询的单词
    strcpy(word,msg->data);
    printf("%s\n",msg->data);
    //把单词存在数据库 找到返回1
    sprintf(sql,"select mean from dict where word=\'%s\'",word);
    printf("%s\n", sql);
    int rc=sqlite3_exec(db,sql,search_callback,mean,&errmsg);
    if(rc!=SQLITE_OK){
        fprintf(stderr,"失败:%s\n",sqlite3_errmsg(db));
        return -1;
    }
    if(strlen(mean)!=0){
        strcpy(msg->data,mean);
        found = 1;
    }
    printf("查询一个单词完毕\n");
    
    if(1==found)//找到单词,同时将用户名,时间,单词,放到历史记录表中
    {
        memset(sql,0,sizeof(sql));
        //需要获取系统时间
        get_date(date);
        
        sprintf(sql,"insert into record values( '%s','%s',\"%s\")",msg->name,date,word);
        printf("%s\n", sql);
        if(SQLITE_OK!=sqlite3_exec(db,sql,NULL,NULL,&errmsg))
        {
            printf("%s\n",errmsg);
            return -1;
        }
        else
        {
        	printf("Insert record done\n");
        }

    }
    else //没有找到
    {
        strcpy(msg->data,"not found");    
    }

    //将查询结果,发送给客户端
    send(acceptfd,msg,sizeof(MSG),0);
    return 0;
}
int do_history(int acceptfd, MSG *msg,sqlite3 *db){
	char sql[128]={0};
    char *errmsg;
    sprintf(sql,"select * from record where name = '%s'",msg->name);

    //查询数据库
    if(SQLITE_OK!=sqlite3_exec(db,sql,history_callback,(void *)&acceptfd,&errmsg))
    {
        printf("%s\n",errmsg);
    }
    else
    {
        printf("已查看历史\n");
    }
    //所有的记录查询发送完毕后给客户端发送结束信息
    msg->data[0]='\0';
    send(acceptfd,msg,sizeof(MSG),0);
    return 0;
}
int search_callback(void *para,int f_num,char **f_value,char **f_name){
	strcat((char*)para,f_value[0]);
    printf("mean: %s\n",(char*)para);
    return 0;
}
int history_callback(void *para,int f_num,char **f_value,char **f_name){
	int acceptfd =*(int*)para;
    MSG msg;
    sprintf(msg.data,"%s: %s",f_value[2],f_value[1]);
    send(acceptfd,&msg,sizeof(MSG),0);
    return 0;
}
void get_date(char *date){
	//获取时间
    time_t t;
    struct tm *p;
    time(&t);
    p = localtime(&t);
    sprintf(date,"%d-%d-%d  %d:%d:%d",
            p->tm_year+1900,p->tm_mon+1,p->tm_mday,
            p->tm_hour,p->tm_min,p->tm_sec);
}
int do_client(int acceptfd,sqlite3 *db){
	MSG msg;
    while(recv(acceptfd, &msg, sizeof(msg),0)>0)
    {
        printf("type:%d\n", msg.type);
        switch(msg.type)
        {
            case R:
                do_register(acceptfd,&msg,db);
                break;
            case L:
                do_login(acceptfd,&msg,db);
                break;
            case Q:
                do_query(acceptfd,&msg,db);
                break;
            case H:
                do_history(acceptfd,&msg,db);
                break;
            default:
                printf("Invalid  data msg.\n");
        }
    }
    close(acceptfd);
    exit(0);

    return 0;
}
int server_init(char *ip, int port){
	int sockfd;
    struct sockaddr_in serveraddr;
     //创建流式套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("fail to socket.\n");
        return -1;
    }
    //给服务器地址初始化
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    serveraddr.sin_port = htons(8008);
    //绑定客户端
    if(bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("fail to bind.\n");
        return -1;
    }
    //将套接字设为监听模式
    if(listen(sockfd, 5) < 0)
    {
        printf("Fail to listen.\n");
        return -1;
    }
    //处理僵尸进程
    signal(SIGCHLD, SIG_IGN);
    printf("服务器成功运行--------\n");
    return sockfd;
}

main.c

#include "server.h"


int main(int argc, char *argv[])
{ 
    int sockfd;
    MSG msg;
    sqlite3 *db;
    pid_t pid;
    int acceptfd;
    struct sockaddr_in cddr={0};
    socklen_t len = sizeof(cddr);
    //打开数据库
    if(sqlite3_open(DATABASE, &db)!=SQLITE_OK)
    {
        printf("%s\n", sqlite3_errmsg(db));
        return -1;
    }
   sockfd = server_init("0.0.0.0", 8008);
    while(1){
        //套接字 客户端地址 客户端大小
        if((acceptfd = accept(sockfd,(void *)&cddr,&len))<0)
        {
            perror("fail to accept");
            return -1;
        }
        //解析客户端ip 和 port 需要用到inet_ntoa() ntohs()转换
    	printf("IP:%s PORT:%d connected.\n",inet_ntoa(cddr.sin_addr),
            ntohs(cddr.sin_port));
        pid = fork();
        if(pid < 0){
            perror("fail to fork");
            return -1;
        }
        else if(pid == 0)
        {
            //处理客户端具体的消息
            close(sockfd);
            do_client(acceptfd, db);
        }
        else
        {
            //父进程,用来接收客户端请求
            close(acceptfd);
        }
    }

    return 0;
} 

Makefile

ser: main.c ser_dict.c
	gcc main.c ser_dict.c -o ser -lsqlite3

一定要注意gcc前面是Tab键,否则会报错,记得加上 -lsqlite3 连接一下库

编译运行

直接make就能编译,真的方便。若报警告不管,报错不能生成 cli 执行文件 或 ser 执行文件

# 先把服务器端打开 
 ./ser
# 再把客户端打开,如果你有多台电脑互联也可以试试其他电脑ip
./cli 127.0.0.1 8008
# 若不想注册可用   用户名:bob  密码:123	登录。

思考与总结

服务器端代码步骤:
1.建立套接字 2.设置ip和端口号 3.绑定套接字 4.监听 5.接收客户端请求 6.创建子进程进行通信
客户端代码步骤:
1.建立套接字 2.设置ip和端口号 3.连接服务器 4.与服务器进行通信
数据库:
sqlite3_op()打开数据库 这里有一个 sqlite3 *db数据库操作句柄
sqlite3_exec()用来执行sql语句,里面有一个回调函数,基本上只有查询的时候用到。
sqlite3_get_table()参数char **resultp获取的就是他的结果 用resultp[1]这种形式访问,就像二维数组用一维的方式访问一样。

在客户端菜单界面用到switch语句,在输入scanf之后需要用getchar()带走脏字符,这是一个循环,若不这样做,下一次会读取到这个脏字符,但是还是没有完全解决。fgets也不好。

可以同时登陆一个账号,未解决变量作用域问题。

sprintf()会输入无线长度的字符串,不得直接使用无长度限制的字符拷贝函数。不应直接使用legacy的字符串拷贝、输入函数,如strcpy、strcat、sprintf、wcscpy、mbscpy等,这些函数的特征是:可以输出一长串字符串,而不限制长度。如果环境允许,应当使用其_s安全版本替代,或者使用n版本函数(如:snprintf,vsnprintf)。

你可能感兴趣的:(linux,c语言,网络,sqlite3,服务器)