Bootstrap

电子词典C语言实现详解(基于linux系统构建服务器、客户端、sqlite3数据库)

前言

需要的基础知识:套接字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:先赞后看好习惯

一 电子词典说明

  1. 通过构建多线程并发服务器实现一服务器对多客户端;
  2. 用户信息数据库维护了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)) 作为用户的查询日志

  1. 拒绝同一个用户多地同时登录;
  2. 兼容用户的错误类型输入;
  3. 运行时服务器端可实时查看客户端的连接或断开信息;
  4. 逻辑清晰,拓展其他功能非常方便。
  5. 词典文件、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,&param); //创建线程并传入参数
		
		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,&param);

		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
在这里插入图片描述

在这里插入图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;