文章目录
前言
需要的基础知识:套接字TCP通信(服务器与客户端),多线程进程,sqlite3数据库,标准IO;
如果上面掌握的不太好也没有关系,咱们有操作是不是,细节拉满,也能学会!
套接字通信参考:https://blog.csdn.net/qq_41796226/article/details/126324397
多线程进程参考:https://blog.csdn.net/qq_41796226/article/details/126355786
sqlite3数据库参考:https://blog.csdn.net/qq_34934140/article/details/120361677
咱们做小项目不是为了实现它的功能而拼凑代码,而是为了从中体会编程的逻辑,举一反三,多处应用。
PS:先赞后看好习惯
一 电子词典说明
- 通过构建多线程并发服务器实现一服务器对多客户端;
- 用户信息数据库维护了3个表:
table | 作用 | |
accountInfo | (usr_name varchar(20),password varchar(20)) 记录所有用户的账号和密码 | |
loginLog | (usr_name varchar(20),operation varchar(10),time varchar(40)) 作为用户的登录和退出的日志 | |
queryLog | (usr_name varchar(20),words varchar(20),time varchar(40)) 作为用户的查询日志 |
- 拒绝同一个用户多地同时登录;
- 兼容用户的错误类型输入;
- 运行时服务器端可实时查看客户端的连接或断开信息;
- 逻辑清晰,拓展其他功能非常方便。
- 词典文件、sqlite3及源码下载:https://download.csdn.net/download/qq_41796226/86406019
操作演示:
二 整体思路及框架
1.一目了然示意图
先了解一下大概框架,后面我们再详细对每个功能的实现做详细介绍:
2.客户端框架
#include "dic.h"
int main(int argc,char* argv[])
{
int sockfd = socket(); //创建套接字
int ret_connect = connect();//发起三次握手
MSG_t message = {0,0,0}; //定义收发的数据包
/*
typedef struct //定义数据包结构体类型
{
int type;
char name[20];
char data[1024];
}MSG_t;
*/
while(1)
{
int choose;
scanf("%d", choose); //根据打印出来的功能菜单进行选择
switch (choose)
{
case 1:
do_register(sockfd,&message); //注册账号
break;
case 2:
if(do_login(sockfd,&message)) //登录账号
{
dictionary(sockfd,&message); //对词典的操作:查单词、查历史记录
}
break;
case 3:
do_exit(sockfd,&message); //退出
exit(0);
}
}
}
void dictionary(int sockfd,MSG_t* pbuf)
{
while(1)
{
int choose;
scanf("%d", choose);
switch (choose)
{
case 1:
do_query(sockfd,pbuf); //查单词
break;
case 2:
{
do_history(sockfd,pbuf); //查看历史查询记录
}
break;
case 3:
do_exit(sockfd,pbuf); //退出
exit(0);
}
}
}
3.服务器框架
#include "dic.h"
int main(int argc,char* argv[])
{
createDb_table(); //创建或打开数据库,用户信息表,历史记录表
int listenfd = socket(AF_INET,SOCK_STREAM,0); //创建监听套接字
int ret_bind = bind(); //绑定监听套接字与主机网络地址信息
int ret_listen = listen(listenfd,10); //设置监听队列的大小
while(1)
{
int newconfd = accept(listenfd,(struct sockaddr*)&buf_addr,&buf_addrlen); //监听等待连接
pthread_create(&tid,NULL,dealClient,&newconfd); //创建线程处理新的连接
}
}
void* dealClient(void* p) //线程函数入口
{
pthread_detach(pthread_self()); //分离线程,线程结束自动释放资源
int newconfd = *(int*)p;
MSG_t message = {0,0,0};
while(1)
{
ssize_t ret_read = read(newconfd,&message,sizeof(MSG_t)); //循环读取客户端发来的数据
switch(message.type) //根据数据的 .type 执行相应函数
{
case R:
do_register(newconfd,&message); //注册账号
break;
case L:
do_login(newconfd,&message); //登录账号
break;
case Q:
do_query(newconfd,&message); //查单词
break;
case H:
do_history(newconfd,&message); //查历史记录
break;
case E:
do_exit(newconfd,&message); //退出
break;
}
}
}
三 手撕代码
1.头文件及主函数
1.1头文件
#ifndef DIC_H
#define DIC_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sqlite3.h>
#define R 1 // user register
#define L 2 // user login
#define Q 3 // query word
#define H 4 // history record
#define E 5 // exit
typedef struct
{
int type;
char name[20];
char data[1024];
}MSG_t; //统一服务器与客户端通信时收发数据的格式
typedef struct
{
int fd;
char IP[20];
}ThreadParam_t; //创建线程时给线程的参数类型
#endif
1.2客户端
/************************************************************************************************/
/* Electronic Dictionary Client */
/************************************************************************************************/
#include "dic.h"
void error_Handling(char* func,int retval); //本猿封装的错误处理函数,为了方便不是很严谨
void do_register(int sockfd,MSG_t* pbuf);
int do_login(int sockfd,MSG_t* pbuf);
void do_query(int sockfd,MSG_t* pbuf);
void do_history(int sockfd,MSG_t* pbuf);
void do_exit(int sockfd,MSG_t* pbuf);
void dictionary(int sockfd,MSG_t* pbuf);
int transform(char* choose); //switch case时兼容用户的错误类型输入
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("%s--IP--Port\n",argv[0]); //命令行传参传入服务器的IP和端口号
exit(1);
}
/*创建套接字*/
int sockfd = socket(AF_INET,SOCK_STREAM,0); //TCP类型的套接字
error_Handling("socket",sockfd);
/*连接*/
struct sockaddr_in serverAddr; //填充地址信息
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(atoi(argv[2]));
serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
int ret_connect = connect(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));//发起三次握手
error_Handling("connect",ret_connect);
MSG_t message = {0,0,0}; //定义并初始化结构体数据
while(1)
{
system("clear"); //清屏,使界面更加优雅
printf("**********************************************************************\n");
printf("* NewFuture Electronic Dictionary *\n");
printf("* Version : 0.0.1 *\n");
printf("* 1: register 2: login 3: exit *\n");
printf("**********************************************************************\n");
printf("please choose a mode: ");
char choose[100];
scanf("%s", choose); //没有定义成int类型是因为用户输入字符或者字符串时会出错
switch (transform(choose)) //transform函数把用户输入转换成int类型
{
case 1:
do_register(sockfd,&message); //注册账号
break;
case 2:
if(do_login(sockfd,&message)) //登录账号
{
dictionary(sockfd,&message);//登录成功则进入词典操作界面
}
break;
case 3:
do_exit(sockfd,&message); //退出
exit(0);
default:
{
printf("Message:无效的输入\n");
break;
}
}
sleep(1);
}
}
int transform(char* choose) //把用户输入的字符串转换成switch语句使用的整型
{
if(!strcmp(choose,"1"))
return 1;
else if(!strcmp(choose,"2"))
return 2;
else if(!strcmp(choose,"3"))
return 3;
else
return 0;
}
void error_Handling(char* func,int retval) //不太严谨的通用函数错误处理
{
if(retval == -1)
{
perror(func);
exit(1);
}
}
void createDb_table() //创建或打开1库3表
{
ret = sqlite3_open("usrInfo.db",&db); //创建或打开数据库
if(ret != 0)
{
perror("sqlite3_open");
exit(1);
}
char* sql = "create table accountInfo(usr_name varchar(20),password varchar(20))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL); //创建账号信息表
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
sql = "create table queryLog(usr_name varchar(20),words varchar(20),time varchar(40))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL); //创建查询历史记录表
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
sql = "create table loginLog(usr_name varchar(20),operation varchar(10),time varchar(40))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL); //创建登录/退出记录表
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
printf("Server_Infomation: The database and tables are ready\n");
}
1.3服务器
/************************************************************************************************/
/* Electronic Dictionary Server */
/************************************************************************************************/
#include "dic.h"
void error_Handling(char* func,int retval);
void break_handling(int retval,char* IP,int fd,MSG_t* pbuf); //客户端断开连接处理函数
void do_register(int newconfd,MSG_t* pbuf);
void do_login(int newconfd,MSG_t* pbuf);
void do_query(int newconfd,MSG_t* pbuf);
void do_history(int newconfd,MSG_t* pbuf);
void do_exit(int newconfd,MSG_t* pbuf);
void recordInQueryLog(MSG_t* pbuf); //记录到查询日志
void recordInloginLog(MSG_t* pbuf,char* operation); //记录到登录日志
void createDb_table(); //创建1库3表
int checkLogout(MSG_t* pbuf,int flag); //检查退出时用户的登录信息,或者做多地登录检查
int count,row,col,ret; //1:当前连接的客户端数量;2、3:sqlite3_get_table()的参数;4:通用返回值
sqlite3* db = NULL; //多处操作数据库,定义为全局变量
char sql[100] = {}; //数据库接口函数的sql语句
char** result; //sqlite3_get_table()出参的结果集
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("%s--Port\n",argv[0]); //命令行传参传入设定的端口号
exit(1);
}
createDb_table(); //创建数据库,账号信息表,查询记录表,登录退出记录表
int listenfd = socket(AF_INET,SOCK_STREAM,0);//创建监听套接字
error_Handling("socket",listenfd);
int on = 1;
int ret_set = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));//开启地址复用
error_Handling("setsockopt",ret_set);
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; //IPV4
serverAddr.sin_port = htons(atoi(argv[1]));
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret_bind = bind(listenfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));//绑定监听套接字与主机网络地址信息
error_Handling("bind",ret_bind);
int ret_listen = listen(listenfd,10); //设置监听队列的大小
error_Handling("listen",ret_listen);
struct sockaddr_in buf_addr; //用于获取客户端的地址信息
socklen_t buf_addrlen = sizeof(buf_addr);
pthread_t tid; //用于获取线程ID
printf(" -------------The server is running-------------\n\n");
while(1)
{
int newconfd = accept(listenfd,(struct sockaddr*)&buf_addr,&buf_addrlen); //阻塞监听等待连接
error_Handling("accept",newconfd);
//accept返回时,获得一个新的与客户端连接的套接字,创建线程去处理这个连接
ThreadParam_t param;
param.fd = newconfd;
strcpy(param.IP,inet_ntoa(buf_addr.sin_addr));
pthread_create(&tid,NULL,dealClient,¶m); //创建线程并传入参数
count++; //客户端连接数+1
}
}
void* dealClient(void* p) //线程函数入口
{
pthread_detach(pthread_self());
int newconfd = (*(ThreadParam_t*)p).fd;
time_t t = time(NULL);
printf(" -------------New connection information-------------\n\n");
printf(" System time : %s\n",ctime(&t)); //当前系统时间
printf(" Establish a connection with the IP : %s\n",(*(ThreadParam_t*)p).IP);//建立连接的IP
printf(" New connected sockfd : %d\n",newconfd); //新建连接的套接字
printf(" Processing ThreadID : %lu\n",pthread_self()); //处理连接的线程ID
printf(" The number of currently connected : %d\n\n\n",count); //当前客户端连接数
MSG_t message = {0,0,0}; //定义并初始化结构体数据
while(1)
{
ssize_t ret_read = read(newconfd,&message,sizeof(MSG_t));//循环读取客户端发来的数据
break_handling(ret_read,(*(ThreadParam_t*)p).IP,newconfd,&message);
//read返回值为0时,说明客户端断开连接
switch(message.type)
{
case R:
do_register(newconfd,&message);
break;
case L:
do_login(newconfd,&message);
break;
case Q:
do_query(newconfd,&message);
break;
case H:
do_history(newconfd,&message);
break;
case E:
do_exit(newconfd,&message);
break;
}
}
}
void error_Handling(char* func,int retval) //不太严谨的通用函数错误处理
{
if(retval == -1)
{
perror(func);
exit(1);
}
}
void break_handling(int retval,char* IP,int fd,MSG_t* pbuf)//处理断开连接
{
if(retval<0)
{
perror("read");
}
if(retval == 0)
{
checkLogout(pbuf,0); //检查日志中最后一次操作是登录还是退出,如果是登录,则把退出操作写入日志
count--;
time_t t = time(NULL);
printf(" -------------New Disconnect information-------------\n\n");
printf(" System time : %s\n",ctime(&t)); //当前系统时间
printf(" Disconnected to IP : %s\n",IP); //断开连接的IP
printf(" The socket is closed : %d\n",fd); //关闭的套接字
printf(" ThreadID is terminated : %lu\n",pthread_self()); //结束的线程ID
printf(" The number of currently connected : %d\n\n",count); //当前客户端连接数
pthread_exit(NULL); //线程退出
}
}
2.注册、登录账号
2.1客户端
void do_register(int sockfd,MSG_t* pbuf)
{
pbuf->type = R; //数据类型为R,告诉服务器这是一个注册请求
printf("\nInput User Name :");
scanf("%s",pbuf->name);
printf("Input Password :");
scanf("%s",pbuf->data);
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t)); //阻塞等待服务器做注册账号的相关处理
error_Handling("read",ret_read);
puts(pbuf->data); //read函数返回时,服务器已经完成相关操作并把返回信息放在pbuf->data里
}
int do_login(int sockfd,MSG_t* pbuf) //登录成功返回1,失败返回0
{
pbuf->type = L; //数据类型为L,告诉服务器这是一个登录请求
printf("\nInput User Name :");
scanf("%s",pbuf->name);
printf("Input Password :");
scanf("%s",pbuf->data);
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
puts(pbuf->data);
sleep(1);
if(pbuf->type == -1) //如果账号或密码错误,服务器把type置为-1
{
return 0;
}
return 1;
}
2.2服务器
void do_register(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from accountInfo where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row > 0) //对用户输入的名字进行查找,已存在则不允许注册
{
strcpy(pbuf->data,"Message:账号已存在,重新注册!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
else //不存在,则把用户输入的账号和密码写入数据库
{
bzero(sql,sizeof(sql));
sprintf(sql,"insert into accountInfo values('%s','%s')",pbuf->name,pbuf->data);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
strcpy(pbuf->data,"Message:注册成功!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
}
void do_login(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from accountInfo where usr_name = '%s' and p-a-s-s-w-o-r-d = '%s'",pbuf->name,pbuf->data); //注意,由于网页发布文章检测的原因,自行将上面password中短线去掉
sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row == 0) //对用户输入的账号密码进行查找匹配,匹配失败则不允许登录
{
strcpy(pbuf->data,"Message:账号不存在或密码错误!");
pbuf->type = -1;
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
else if(row == 1) //匹配成功
{
if( checkLogout(pbuf,1) ) //检查该账号是否已经登陆
{
strcpy(pbuf->data,"Message:该账号已在另外一台设备上登录!");
pbuf->type = -1;
}
else
{
recordInloginLog(pbuf,"login"); //将此次登录操作写入用户登录日志
strcpy(pbuf->data,"Message:登录成功!");
}
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
}
int checkLogout(MSG_t* pbuf,int flag) // flag=1:登陆时调用,检查账号是否重复登录
{ // flag=0:退出或断开连接时调用,检查上次操作是登录还是退出
if(strlen(pbuf->name) == 0) //客户端没有尝试登录过
return 0;
sprintf(sql,"select * from loginLog where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row == 0) //没有登录成功时退出,且之前没有记录过这个名字
return 0;
if(strcmp(result[(row+1)*col-2],"login") == 0)
{
if(flag)
return 1;
recordInloginLog(pbuf,"logout"); //将此次退出操作写入日志
}
return 0;
}
void recordInloginLog(MSG_t* pbuf,char* operation)
{
char time_q[40];
time_t t = time(NULL);
sprintf(time_q,"%s",ctime(&t));
sprintf(sql,"insert into loginLog values('%s','%s','%s')",pbuf->name,operation,time_q);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
}
3.查单词、历史记录
3.1客户端
void dictionary(int sockfd,MSG_t* pbuf)
{
while(1)
{
system("clear"); //清屏使界面更加优雅
printf("**********************************************************************\n");
printf("* NewFuture Electronic Dictionary *\n");
printf("* Version : 0.0.1 *\n");
printf("* 1: Words_Query 2: History_Reco 3: exit *\n");
printf("**********************************************************************\n");
printf("please choose a mode: ");
char choose[1000];
scanf("%s", choose); //没有定义成int类型是因为用户输入字符或者字符串时会出错
switch (transform(choose)) //transform函数把用户输入转换成int类型
{
case 1:
do_query(sockfd,pbuf);
break;
case 2:
{
do_history(sockfd,pbuf);
}
break;
case 3:
do_exit(sockfd,pbuf);
exit(0);
default:
{
printf("Message:无效的输入\n");
sleep(1);
}
break;
}
}
}
void do_query(int sockfd,MSG_t* pbuf)
{
pbuf->type = Q; //数据类型为Q,告诉服务器这是一个查单词请求
while(1)
{
printf("Input words (quit by '#') :");
scanf("%s",pbuf->data); //把输入的单词传给服务器
if(strcmp(pbuf->data,"#") == 0) //输入"#"表示结束查单词
break;
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
putchar('\n');
puts(pbuf->data);
}
}
void do_history(int sockfd,MSG_t* pbuf)
{
pbuf->type = H; //数据类型为H,告诉服务器这是一个查历史记录请求
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read;
int i = 0;
while( ret_read = read(sockfd,pbuf,sizeof(MSG_t)) ) //循环读
{
error_Handling("read",ret_read);
if( strcmp(pbuf->data,"end") == 0) //服务器最后发一个"end"作为结尾
break;
printf("%s ",pbuf->data);
i++;
if(i%3 == 0) //换行使界面更加优雅
printf("\n");
}
printf("Message:按回车返回上一层\n");
getchar(); //先获取上次scanf没有读走的回车
getchar(); //获取回车,结束历史记录的查看
}
3.2服务器
void do_query(int newconfd,MSG_t* pbuf)
{
FILE *fp = fopen("dict.txt","r"); //打开词典文件
if(fp == NULL)
{
perror("open");
exit(-1);
}
char word[50] = "\0";
char buf[100] = "\0";
int flag = 0;
while(fgets(buf,sizeof(buf),fp)!=NULL) //查询算法这里用的是顺序查找,可以升级为其他高效的算法
{ //应当根据文件内容的特点制定合适的查询算法
bzero(word,sizeof(word));
int i = 0;
while(buf[i]>='a'&&buf[i]<='z')
{
word[i] = buf[i++];
}
if(strcmp(word,pbuf->data)==0)
{
flag = 1;
break;
}
}
if(!flag) //没有查询到
{
strcpy(pbuf->data,"Message:Sorry,Word Not found");
}
else //查询到
{
recordInQueryLog(pbuf); //写入查询历史记录
strcpy(pbuf->data,buf);
}
fclose(fp);
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
void recordInQueryLog(MSG_t* pbuf)
{
char time_q[40];
time_t t = time(NULL);
sprintf(time_q,"%s",ctime(&t));
sprintf(sql,"insert into queryLog values('%s','%s','%s')",pbuf->name,pbuf->data,time_q);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
}
void do_history(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from queryLog where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL); //查找出该用户的所有查询记录
int i;
for(i=0;i<(row+1)*col;i++) //row和col不包含字段,但结果集result中包含了字段
{
strcpy(pbuf->data,result[i]);
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
strcpy(pbuf->data,"end"); //最后发送"end"作为结尾表示发送完毕
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
4.退出
4.1客户端
void do_exit(int sockfd,MSG_t* pbuf)
{
pbuf->type = E; //数据类型为E,告诉服务器这是一个退出请求
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
puts(pbuf->data);
}
4.2服务器
void do_exit(int newconfd,MSG_t* pbuf)
{ //这里没有操作登录日志,因为客户端退出时即断开连接,在断开连接函数中也会写入日志
strcpy(pbuf->data,"Message:退出成功!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
四 完整源码
/**********************************dic.h**********************************/
#ifndef DIC_H
#define DIC_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sqlite3.h>
#define R 1 // user register
#define L 2 // user login
#define Q 3 // query word
#define H 4 // history record
#define E 5 // exit
typedef struct
{
int type;
char name[20];
char data[1024];
}MSG_t;
typedef struct
{
int fd;
char IP[20];
}ThreadParam_t;
#endif
/**********************************client.c**********************************/
/************************************************************************************************/
/* Electronic Dictionary Client */
/************************************************************************************************/
#include "dic.h"
void error_Handling(char* func,int retval);
void do_register(int sockfd,MSG_t* pbuf);
int do_login(int sockfd,MSG_t* pbuf);
void do_query(int sockfd,MSG_t* pbuf);
void do_history(int sockfd,MSG_t* pbuf);
void do_exit(int sockfd,MSG_t* pbuf);
void dictionary(int sockfd,MSG_t* pbuf);
int transform(char* choose);
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("%s--IP--Port\n",argv[0]);
exit(1);
}
/*创建套接字*/
int sockfd = socket(AF_INET,SOCK_STREAM,0);
error_Handling("socket",sockfd);
/*连接*/
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(atoi(argv[2]));
serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
int ret_connect = connect(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
error_Handling("connect",ret_connect);
MSG_t message = {0,0,0};
while(1)
{
system("clear");
printf("**********************************************************************\n");
printf("* NewFuture Electronic Dictionary *\n");
printf("* Version : 0.0.1 *\n");
printf("* 1: register 2: login 3: exit *\n");
printf("**********************************************************************\n");
printf("please choose a mode: ");
char choose[100];
scanf("%s", choose);
switch (transform(choose))
{
case 1:
do_register(sockfd,&message);
break;
case 2:
if(do_login(sockfd,&message))
{
dictionary(sockfd,&message);
}
break;
case 3:
do_exit(sockfd,&message);
exit(0);
default:
{
printf("Message:无效的输入\n");
break;
}
}
sleep(1);
}
}
void do_register(int sockfd,MSG_t* pbuf)
{
pbuf->type = R;
printf("\nInput User Name :");
scanf("%s",pbuf->name);
printf("Input Password :");
scanf("%s",pbuf->data);
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
puts(pbuf->data);
}
int do_login(int sockfd,MSG_t* pbuf)
{
pbuf->type = L;
printf("\nInput User Name :");
scanf("%s",pbuf->name);
printf("Input Password :");
scanf("%s",pbuf->data);
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
puts(pbuf->data);
sleep(1);
if(pbuf->type == -1)
{
return 0;
}
return 1;
}
void dictionary(int sockfd,MSG_t* pbuf)
{
while(1)
{
system("clear");
printf("**********************************************************************\n");
printf("* NewFuture Electronic Dictionary *\n");
printf("* Version : 0.0.1 *\n");
printf("* 1: Words_Query 2: History_Reco 3: exit *\n");
printf("**********************************************************************\n");
printf("please choose a mode: ");
char choose[1000];
scanf("%s", choose);
switch (transform(choose))
{
case 1:
do_query(sockfd,pbuf);
break;
case 2:
{
do_history(sockfd,pbuf);
}
break;
case 3:
do_exit(sockfd,pbuf);
exit(0);
default:
{
printf("Message:无效的输入\n");
sleep(1);
}
break;
}
}
}
void do_query(int sockfd,MSG_t* pbuf)
{
pbuf->type = Q;
while(1)
{
printf("Input words (quit by '#') :");
scanf("%s",pbuf->data);
if(strcmp(pbuf->data,"#") == 0)
break;
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
putchar('\n');
puts(pbuf->data);
}
}
void do_history(int sockfd,MSG_t* pbuf)
{
pbuf->type = H;
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read;
int i = 0;
while( ret_read = read(sockfd,pbuf,sizeof(MSG_t)) )
{
error_Handling("read",ret_read);
if( strcmp(pbuf->data,"end") == 0)
break;
printf("%s ",pbuf->data);
i++;
if(i%3 == 0)
printf("\n");
}
printf("Message:按回车返回上一层\n");
getchar();
getchar();
}
int transform(char* choose)
{
if(!strcmp(choose,"1"))
return 1;
else if(!strcmp(choose,"2"))
return 2;
else if(!strcmp(choose,"3"))
return 3;
else
return 0;
}
void error_Handling(char* func,int retval)
{
if(retval == -1)
{
perror(func);
exit(1);
}
}
void do_exit(int sockfd,MSG_t* pbuf)
{
pbuf->type = E;
ssize_t ret_write = write(sockfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
ssize_t ret_read = read(sockfd,pbuf,sizeof(MSG_t));
error_Handling("read",ret_read);
puts(pbuf->data);
}
/**********************************server.c**********************************/
/************************************************************************************************/
/* Electronic Dictionary Server */
/************************************************************************************************/
#include "dic.h"
void error_Handling(char* func,int retval);
void break_handling(int retval,char* IP,int fd,MSG_t* pbuf);
void do_register(int newconfd,MSG_t* pbuf);
void do_login(int newconfd,MSG_t* pbuf);
void do_query(int newconfd,MSG_t* pbuf);
void do_history(int newconfd,MSG_t* pbuf);
void do_exit(int newconfd,MSG_t* pbuf);
void recordInQueryLog(MSG_t* pbuf);
void recordInloginLog(MSG_t* pbuf,char* operation);
void createDb_table();
int checkLogout(MSG_t* pbuf,int flag);
int count,row,col,ret;
sqlite3* db = NULL;
char sql[100] = {};
char** result;
void* dealClient(void* p)
{
pthread_detach(pthread_self());
int newconfd = (*(ThreadParam_t*)p).fd;
time_t t = time(NULL);
printf(" -------------New connection information-------------\n\n");
printf(" System time : %s\n",ctime(&t));
printf(" Establish a connection with the IP : %s\n",(*(ThreadParam_t*)p).IP);
printf(" New connected sockfd : %d\n",newconfd);
printf(" Processing ThreadID : %lu\n",pthread_self());
printf(" The number of currently connected : %d\n\n\n",count);
MSG_t message = {0,0,0};
while(1)
{
ssize_t ret_read = read(newconfd,&message,sizeof(MSG_t));
break_handling(ret_read,(*(ThreadParam_t*)p).IP,newconfd,&message);
switch(message.type)
{
case R:
do_register(newconfd,&message);
break;
case L:
do_login(newconfd,&message);
break;
case Q:
do_query(newconfd,&message);
break;
case H:
do_history(newconfd,&message);
break;
case E:
do_exit(newconfd,&message);
break;
}
}
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("%s--Port\n",argv[0]);
exit(1);
}
/*创建数据库,用户信息表,历史记录表*/
createDb_table();
/*创建套接字——监听套接字*/
int listenfd = socket(AF_INET,SOCK_STREAM,0);
error_Handling("socket",listenfd);
/*绑定监听套接字与主机网络地址信息*/
int on = 1;
int ret_set = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
error_Handling("setsockopt",ret_set);
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(atoi(argv[1]));
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret_bind = bind(listenfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
error_Handling("bind",ret_bind);
/*设置监听队列的大小*/
int ret_listen = listen(listenfd,10);
error_Handling("listen",ret_listen);
/*监听等待连接*/
struct sockaddr_in buf_addr;
socklen_t buf_addrlen = sizeof(buf_addr);
pthread_t tid;
printf(" -------------The server is running-------------\n\n");
while(1)
{
int newconfd = accept(listenfd,(struct sockaddr*)&buf_addr,&buf_addrlen);
error_Handling("accept",newconfd);
ThreadParam_t param;
param.fd = newconfd;
strcpy(param.IP,inet_ntoa(buf_addr.sin_addr));
pthread_create(&tid,NULL,dealClient,¶m);
count++;
}
}
void createDb_table()
{
/*创建库*/
ret = sqlite3_open("usrInfo.db",&db);
if(ret != 0)
{
perror("sqlite3_open");
exit(1);
}
/*创建表*/
char* sql = "create table accountInfo(usr_name varchar(20),password varchar(20))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
sql = "create table queryLog(usr_name varchar(20),words varchar(20),time varchar(40))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
sql = "create table loginLog(usr_name varchar(20),operation varchar(10),time varchar(40))";
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
if(ret != 0)
{
printf("Server_Infomation: %s\n",sqlite3_errmsg(db));
}
printf("Server_Infomation: The database and tables are ready\n");
}
void do_register(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from accountInfo where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row > 0)
{
strcpy(pbuf->data,"Message:账号已存在,重新注册!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
else
{
bzero(sql,sizeof(sql));
sprintf(sql,"insert into accountInfo values('%s','%s')",pbuf->name,pbuf->data);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
strcpy(pbuf->data,"Message:注册成功!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
}
void do_login(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from accountInfo where usr_name = '%s' and p-a-s-s-w-o-r-d = '%s'",pbuf->name,pbuf->data);
//注意,由于网页发布文章检测的原因,自行将上面password中短线去掉
sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row == 0)
{
strcpy(pbuf->data,"Message:账号不存在或密码错误!");
pbuf->type = -1;
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
else if(row == 1)
{
if( checkLogout(pbuf,1) )
{
strcpy(pbuf->data,"Message:该账号已在另外一台设备上登录!");
pbuf->type = -1;
}
else
{
recordInloginLog(pbuf,"login");
strcpy(pbuf->data,"Message:登录成功!");
}
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
}
void do_query(int newconfd,MSG_t* pbuf)
{
FILE *fp = fopen("dict.txt","r");
if(fp == NULL)
{
perror("open");
exit(-1);
}
char buf[100] = "\0";
char word[50] = "\0";
int flag = 0;
while(fgets(buf,sizeof(buf),fp)!=NULL)
{
bzero(word,sizeof(word));
int i = 0;
while(buf[i]>='a'&&buf[i]<='z')
{
word[i] = buf[i++];
}
if(strcmp(word,pbuf->data)==0)
{
flag = 1;
break;
}
}
if(!flag)
{
strcpy(pbuf->data,"Message:Sorry,Word Not found");
}
else
{
recordInQueryLog(pbuf);
strcpy(pbuf->data,buf);
}
fclose(fp);
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
void do_history(int newconfd,MSG_t* pbuf)
{
sprintf(sql,"select * from queryLog where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL);
int i;
for(i=0;i<(row+1)*col;i++)
{
strcpy(pbuf->data,result[i]);
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
strcpy(pbuf->data,"end");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
void recordInloginLog(MSG_t* pbuf,char* operation)
{
char time_q[40];
time_t t = time(NULL);
sprintf(time_q,"%s",ctime(&t));
sprintf(sql,"insert into loginLog values('%s','%s','%s')",pbuf->name,operation,time_q);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
}
void recordInQueryLog(MSG_t* pbuf)
{
char time_q[40];
time_t t = time(NULL);
sprintf(time_q,"%s",ctime(&t));
sprintf(sql,"insert into queryLog values('%s','%s','%s')",pbuf->name,pbuf->data,time_q);
ret = sqlite3_exec(db,sql,NULL,NULL,NULL);
}
int checkLogout(MSG_t* pbuf,int flag)
{
if(strlen(pbuf->name) == 0)
return 0;
sprintf(sql,"select * from loginLog where usr_name = '%s'",pbuf->name);
ret = sqlite3_get_table(db,sql,&result,&row,&col,NULL);
if(row == 0)
return 0;
if(strcmp(result[(row+1)*col-2],"login") == 0)
{
if(flag)
return 1;
recordInloginLog(pbuf,"logout");
}
return 0;
}
void break_handling(int retval,char* IP,int fd,MSG_t* pbuf)
{
if(retval<0)
{
perror("read");
}
if(retval == 0)
{
checkLogout(pbuf,0);
count--;
time_t t = time(NULL);
printf(" -------------New Disconnect information-------------\n\n");
printf(" System time : %s\n",ctime(&t));
printf(" Disconnected to IP : %s\n",IP);
printf(" The socket is closed : %d\n",fd);
printf(" ThreadID is terminated : %lu\n",pthread_self());
printf(" The number of currently connected : %d\n\n",count);
pthread_exit(NULL);
}
}
void do_exit(int newconfd,MSG_t* pbuf)
{
strcpy(pbuf->data,"Message:退出成功!");
ssize_t ret_write = write(newconfd,pbuf,sizeof(MSG_t));
error_Handling("write",ret_write);
}
void error_Handling(char* func,int retval)
{
if(retval == -1)
{
perror(func);
exit(1);
}
}
五 完结
高端IT培训来华清远见 www.hqyj.com