项目要求:流式套接字实现服务器的并发,连接数据库实现以下功能
客户端:
注册:验证数据库中用户名是否存在,有则提示,无则注册
登录:验证用户名是否正确,登录密码是否正确
查询:发送给服务器要查询的单词,接收服务器发送的单词释义,打印
查看用户历史记录:将用户名发送给服务器,服务器查询返回发送给客户端
注销:退出登录,关闭客户端
服务器端:
并发,这里我用循环多进程实现并发,父进程只负责等待新的客户端连接,子进程负责与每个客户端进行交互
创建数据库-创建/打开表
注册:接收客户端发来的用户名对比数据库
登录:接收客户端发来的用户名,密码对比数据库
查询:打开文件,fgets函数,每次读取一行,判断前单词位数个是否匹配
历史记录:接收客户端发来的用户名,查询数据库并将结果发给客户端
头文件代码
#ifndef _head_h_
#define _head_h_
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/epoll.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
enum type_t
{
non,
logining,
logon,
search,
logined,
found,
loginOut
};
typedef struct msg_t
{
int type;
char name[32];
char pwd[32];
char history[1000];
} User;
int flag;
void quit(int sockfd);
void fun();
void menu(void);
void logon_client(User *user);
void check(int sockfd, char *buf, int len);
void login_client(User *user);
void logon_server(int acceptfd, sqlite3 *db, User *user, int len, char *buf, int len2);
void login_server(int acceptfd, sqlite3 *db, User *user, int len, char *buf, int len2);
void search_client(int sockfd, User *user, int len);
void search_server(int acceptfd, User *user, int len1, char *row, int len2, sqlite3 *db);
void history_client(int sockfd,User *user,int len);
void history_server(int acceptfd,sqlite3 *db,User *user,int len);
#endif
客户端代码
#include "dictionary.h"
int main(int argc, char const *argv[])
{
User user;
int len = sizeof(user);
user.type = non;
strcpy(user.history, "NULL"); //将用户的历史记录初始化为NULL
char name[32];
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("create socket err");
return -1;
}
printf("创建套接字成功\n");
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = inet_addr("192.168.50.69");
printf("绑定端口IP成功\n");
int conectValue = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (conectValue < 0)
{
perror("connect err");
return -1;
}
printf("连接服务器成功\n");
menu(); //菜单
char buf[128] = "";
char command; //命令
while (1)
{
printf(">>>");
scanf("%c", &command);
switch (command)
{
case 'M':
menu();
break;
case 'L':
login_client(&user);
send(sockfd, &user, len, 0); //这里将发送用户名和密码从登录函数中拿出来,更安全,否则的话要时刻注意收发双方的堵塞问题
check(sockfd, buf, sizeof(buf)); //判断服务器与数据库的对比结果
user.type = logined;
break;
case 'N':
logon_client(&user);
send(sockfd, &user, len, 0);
check(sockfd, buf, sizeof(buf));
break;
case 'S':
search_client(sockfd, &user, len);
break;
case 'H':
history_client(sockfd, &user, len);
break;
case 'Q':
quit(sockfd);
return 0;
// default:
// printf("命令输入错误\n");
}
}
return 0;
}
void history_client(int sockfd, User *user, int len)
{
int sendByte, recvByte;
if (user->type != logined)
{
printf("请登录后查看历史记录\n");
return;
}
user->type = found;
sendByte = send(sockfd, user, len, 0); //想服务器发送用户状态
if (sendByte < 0)
{
perror("send err6:");
return;
}
printf("正在获取您的历史查询记录...\n");
sleep(1); //模拟搜索时间
recvByte = recv(sockfd, user, len, 0); //接收服务器发来的user信息
if (recvByte < 0)
{
perror("recv err 5:");
return;
}
printf("%s\n", user->history); //打印用户历史记录
user->type = logined; //退出后返回登录状态
}
//查询
void search_client(int sockfd, User *user, int len)
{
char buf[128];
char row[500]; //存放单词释义,容量要足够打,满足一次读一行
char *p; //指针,打印时用来分开打印单词和释义
int buflen = sizeof(buf);
int sendByte;
if (user->type != logined) //判断登录状态身份时候合法
{
printf("请登录后进行查询操作\n");
return;
}
user->type = search; //改变用户由登录状态为查询
sendByte = send(sockfd, user, len, 0); //告诉服务器 我现在是查询的身份
if (sendByte < 0)
{
perror("send err3");
return;
}
while (1) //客户端循环输入单词查询,直至输入8888
{
printf("请输入您要查询的单词:");
scanf("%s", buf);
sendByte = send(sockfd, buf, buflen, 0); //将单词发送给服务器,服务器作出处理
if (sendByte < 0)
{
perror("send err2");
return;
}
if (strcmp(buf, "8888") == 0) //判断字符串是否为8888
{
printf("正在退出查询中......\n");
printf("退出查询成功\n");
break;
}
recv(sockfd, row, sizeof(row), 0); //d等在服务器发送查询结果
printf("单词:");
p = row;
while (*p != ' ')
{
printf("%c", *p);
p++;
}
putchar(10);
printf("释义:");
printf("%s\n", row);
}
user->type = logined; //查询退出,返回登录状态;
}
//登录
void login_client(User *user)
{
user->type = logining; //改变用户状态为准备登录
printf("请输入登录帐号:");
scanf("%s", user->name);
printf("请输入登录密码:");
scanf("%s", user->pwd);
}
//注册
void logon_client(User *user)
{
user->type = logon; //改变用户状态为注册状态
printf("请输入想要注册的用户名:");
scanf("%s", user->name);
printf("请输入注册的密码:");
scanf("%s", user->pwd);
}
//根据服务器回复的内容智能操作
void check(int sockfd, char *buf, int len)
{
int recvByte;
recvByte = recv(sockfd, buf, len, 0);
if (recvByte < 0)
{
perror("recv err1");
return;
}
if (strcmp(buf, "logon success") == 0) //服务器验证数据库中没有准备注册的用户名
printf("注册成功请登录\n");
else if (strcmp(buf, "username has exist") == 0)
printf("用户名已存在,请重试\n");
else if (strcmp(buf, "username is not exist") == 0) //登录验证,验证用户名是否存在
printf("用户名不存在,请确认后重新输入\n");
else if (strcmp(buf, "login fail") == 0) //用户名存在的情况下,验证密码是否正确
printf("密码错误请重试\n");
else if (strcmp(buf, "login success") == 0)
printf("登录成功,欢迎使用\n");
else if (strcmp(buf, "search success") == 0)
printf("查找成功\n");
else if (strcmp(buf, "search fail") == 0)
printf("查找失败\n");
else if (strcmp(buf, "stop search") == 0)
printf("退出查找\n");
}
void menu(void)
{
printf("*******************************************************\n");
printf("**** 登录选项 ****\n");
printf("**** 菜单(M) ****\n");
printf("**** 登录(L) ****\n");
printf("**** 我没有帐号(N) ****\n");
printf("**** 退出登录(Q) ****\n");
printf("**** 使用词典(S) ****\n");
printf("**** 用户历史查询记录(H) ****\n");
printf("*******************************************************\n");
}
//退出
void quit(int sockfd)
{
close(sockfd);
printf("**********************期待您下次使用***************************\n");
}
服务器端代码
#include "dictionary.h"
int main(int argc, char const *argv[])
{
User user;
int len = sizeof(user);
struct sockaddr_in from;
int fromsize = sizeof(from);
ssize_t recvByte, sendByte;
char buf[128];
int acceptfd;
char row[500];
//1创建连接套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //通信方式,套接字类型,自动匹配
if (sockfd < 0)
{
perror("create socket err");
return -1;
}
printf("socket(%d) create success!\n", sockfd);
//2填充结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
//serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//自动获取ip
/*serveraddr.sin_ */
//printf("......\n");
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//serveraddr.sin_addr.s_addr=INADDR_ANY;//都是0 大小端存储都不需要转化
//serveraddr.sin_addr.s_addr=inet_addr("0.0.0.0");
//3bind 绑定套接字
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind success\n");
//4监听
if (listen(sockfd, 5) < 0) //监听处理的套接字描述符,服务器同时处理的客户端个数
{
perror("listen fail");
return -1;
}
printf("监听成功\n");
sqlite3 *db;
char *errmsg = NULL; //定义指向存放错误信息的指针;
if (sqlite3_open("./USER.db", &db) != SQLITE_OK) //打开数据库,并将句柄指向数据库,判断是否成功,SQLITE_OK是宏定义0
{ /*因为数据库操作的错误信息不会向文件打开一样有perror
要调用函数查找错误的原因,并和提示语句拼接在一起 打印标准错误,因为标准错误没有缓存区,所以会立即在终端打印
*/
fprintf(stderr, "sqlite3_open err :%s", sqlite3_errmsg(db));
return -1;
}
else
printf("数据库打开成功\n");
char createTbale[100] = "create table user(userName char primary key,userPWD char,historySearch char);"; //sql语句,注意分号
if (sqlite3_exec(db, createTbale, NULL, NULL, &errmsg) != SQLITE_OK) //执行sql语句操作
{
if (strcmp(errmsg, "table user already exists") == 0)
printf("您已创建过,欢迎使用\n");
else
{
fprintf(stderr, "create table err :%s\n", errmsg);
return -1;
}
}
else
printf("用户信息表创建成功\n");
int ret;
int pid;
//5创建收发套接字
signal(SIGCHLD, fun);
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&from, &fromsize);
if (acceptfd < 0)
{
perror("create acceptsocket err");
return -1;
}
printf("port = %d, ip = %s\n", ntohs(from.sin_port), inet_ntoa(from.sin_addr));
//把创建的通信描述符放到原表中
pid = fork();
if (pid < 0)
{
perror("create child errr:");
return -1;
}
else if (pid == 0)
{
while (1)
{
close(sockfd);
recvByte = recv(acceptfd, &user, len, 0);
if (recvByte < 0)
{
if (recvByte < 0)
{
perror("recv err1");
return -1;
}
}
else if (recvByte == 0)
{
close(acceptfd);
printf("user退出\n");
return -1;
}
else
switch (user.type)//判断用户状态 响应请求
{
case logon:
logon_server(acceptfd, db, &user, len, buf, sizeof(buf));
break;
case logining:
login_server(acceptfd, db, &user, len, buf, sizeof(buf));
break;
case search:
search_server(acceptfd, &user, len, row, sizeof(row), db);
printf("查询结束\n");
break;
case found:
history_server( acceptfd,db,&user, len);
break;
}
}
}
else
close(acceptfd);
}
close(sockfd);
return 0;
}
//历史记录
void history_server(int acceptfd,sqlite3 *db,User *user,int len)
{ char selectHistory[100];
char **res;
int nrow,ncolumn;
char *errmsg;
int sendByte;
sprintf(selectHistory,"select historySearch from USER where userName = '%s';",user->name);
if(sqlite3_get_table(db, selectHistory, &res, &nrow, &ncolumn, &errmsg)!=SQLITE_OK)
{
fprintf(stderr,"sqlite3 err:%s",errmsg);
return ;
}
printf("用户 %s 的历史记录整理成功\n",user->name);
strcpy(user->history,res[1]);
sendByte=send(acceptfd,user,len,0);
if(sendByte<0)
{
perror("send err7");
return ;
}
printf("历史记录发送成功\n");
}
//查询
void search_server(int acceptfd, User *user, int len1, char *row, int len2, sqlite3 *db)
{
int sendByte, recvByte;
char buf[128];
char *errmsg;
int nrow, ncolumn;
char selectHistory[100] = "";
char updateHistory[100] = "";
char **res;
FILE *fp = fopen("./dict.txt", "r");//打开字典
if (fp == NULL)
{
perror("open dic err:\n");
}
printf("字典初始化成功!\n");
while (1)
{
recvByte = recv(acceptfd, buf, sizeof(buf), 0);//接收用户发来的单词
if (recvByte < 0)
{
perror("recv err4");
return;
}
if (recvByte == 0)
{
printf("客户端异常退出\n");
return;
}
if (strcmp(buf, "8888") == 0)
{
printf("用户停止查询\n");
break;
}
printf("从%s接受到的单词是%s\n", user->name, buf);
int dsize = strlen(buf);//求出单词长度,每次读一行,方便对比
while (1)
{
fgets(row, 500, fp);
if (strncmp(row, buf, dsize) == 0)
{
printf("查找成功\n");
sendByte = send(acceptfd, row, len2, 0); //将查询的结果发送给客户端
if (sendByte < 0)
{
perror("send err5:");
return;
}
printf("发送单词释义成功\n");
/*查询用户历史记录
如果为空则需要覆盖
如果有内容,则需要追加
*/
sprintf(selectHistory, "select historySearch from USER where userName = '%s';", user->name);
if (sqlite3_get_table(db, selectHistory, &res, &nrow, &ncolumn, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "select err:%s\n", errmsg);
return;
}
/* printf("111user->historySearch is %s\n", user->history); //
printf("%s\n",res[0]);
printf("%s\n",res[1]);
用于测试res查询到的内容
*/
strcpy(user->history, res[1]);//查询到的内容只有表头(字段)和一个字段值
printf("++++++\n");
if (strcmp(user->history, "NULL") == 0)
strcpy(user->history, buf);
else
{
strcat(user->history, " ");
strcat(user->history, buf);
}
printf("222user->historySearch is %s\n", user->history);
sprintf(updateHistory, "update USER set historySearch = \"%s\" where userName = '%s';", user->history, user->name);
if (sqlite3_exec(db, updateHistory, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "update user history err:%s", errmsg);
return;
}
else
printf("更新用户查询历史成功\n");
rewind(fp);
break;
}
}
}
fclose(fp);
}
//登录
void login_server(int acceptfd, sqlite3 *db, User *user, int len, char *buf, int len2)
{
char **res;
int nrow, ncolumn;
char *errmsg;
int sendByte;
char selectName[100] = "";
sprintf(selectName, "select * from user where userName is '%s';", user->name);//查询用户名
if (sqlite3_get_table(db, selectName, &res, &nrow, &ncolumn, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "select err:%s\n", errmsg);
return;
}
if (nrow == 0)//验证用户名是否正确,没有数据则用户名不存在
{
strcpy(buf, "username is not exist");
sendByte = send(acceptfd, buf, len2, 0);
if (sendByte < 0)
{
perror("send err1:");
return;
}
printf("用户名(%s)不存在\n", user->name);
}
else if (strcmp(user->pwd, res[4]) == 0)
//反之用户名存在,验证密码是否正确,这里两种方式查询 直接select userpwd from user where..
//select * from user where 查询到的就是限定条件下的所有的数据,以本表为例,密码被存放在res[4]中{username ,userpwd,userhistory,"test","123","null"}
{
strcpy(buf, "login success");
send(acceptfd, buf, len2, 0);
printf("user(%s)登录成功\n", user->name);
}
else
{
strcpy(buf, "login fail");
send(acceptfd, buf, len2, 0);
printf("user(%s)登录密码不正确\n", user->name);
}
}
//注册
void logon_server(int acceptfd, sqlite3 *db, User *user, int len, char *buf, int len2)
{
char **res;
int nrow, ncolumn;
char *errmsg;
char selectName[100] = "";
char insertTable[100] = "";
//strcat(insertTable,user->name);
sprintf(selectName, "select * from user where userName = '%s';", user->name);//插入时字符串用“” 判断字段值时用单引号
if (sqlite3_get_table(db, selectName, &res, &nrow, &ncolumn, &errmsg) != SQLITE_OK)//查询用户名是否存在
{
fprintf(stderr, "select err:%s\n", errmsg);
return;
}
if (nrow == 0)//查询结果为0,只要查询到 数据会被存在res指向的地址中,其中包括字段 字段值
{
sprintf(insertTable, "insert into user values(\"%s\",\"%s\",\"%s\");", user->name, user->pwd, user->history);//数据库中插入数据
sqlite3_exec(db, insertTable, NULL, NULL, &errmsg);
strcpy(buf, "logon success");//插入成功,向客户端发送消息客户端收到后做出相应操作
send(acceptfd, buf, len2, 0);
printf("user(%s)注册成功\n", user->name);
}
else
{ //用户名已存在
strcpy(buf, "username has exist");
send(acceptfd, buf, len2, 0);
printf("user(%s)已经存在注册失败\n", user->name);
}
}
void fun()//子进程退出/状态发生改变就会被回收
{
waitpid(-1, NULL, WNOHANG);
}
/*将所有的命令都放在了一个菜单里如何保证未登录情况下输入命令不被执行?标志位 */
/*.逻辑上的登录成功和真正意义上的登录成功 */
/*.如何将用户和他的历史记录在不同的函数里使用,如何将用户名传递
结构体?
在每个客户端用一个字符数组保存信息传递给服务器
*/
/*u如何查询单词->fgets 一次读一行 */
/*查询时res指向查询到的指针数组,如何将查询内容放到数据库里
拼接?
*/
/*epoll服务器把功能都写在函数里 不能实现并发还会造成客户端连接不上的问题->fork */
/* 查询的单词放到历史记录里(history char ),查两个三个就会满,需要每次重新编译,让user->history为空才能继续查询,但是会覆盖原来的历史记录*/
/*字典文件未关闭,每次都是从前一次查找的文件指针位置以后再找,这样有可能造成单词没查到,堵塞
内层while 打开文件 关闭
*/