项目:电子词典项目需求
项目要求:
1、登录注册功能,不能重复登录,重复注册
2、单词查询功能
3、历史记录功能,存储单词,意思,以及查询时间
4、基于TCP,支持多客户端连接
5、采用数据库保存用户信息与历史记录
6、将dict.txt的数据导入到数据库中保存。
7、按下ctrl+c退出客户端后,注销该客户端的登录信息
格式要求:
main函数只跑逻辑,不允许跑功能代码
功能代码封装成函数
服务器端代码
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#define ERR_MSG(msg) \
do \
{ \
fprintf(stderr, "line:%d ", __LINE__); \
perror(msg); \
} while (0)
#define PORT 6666 //1024~49151
#define IP "192.168.31.115" //IP地址,本机IP ifconfig
//信息传递协议
struct communication
{
char mode; //功能模式
char name[32]; //用户名
char msg[512]; //查询的单词,返回的释义,历史记录等
};
//僵尸进程的回收
void zombie_callBack(int sig)
{
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
//单词文档导入到数据库中
int word_import()
{
//读取txt文件到数据库
FILE *fp = fopen("./dict.txt", "r"); //只读方式打开txt文件
if (NULL == fp)
{
perror("fopen");
return -1;
}
//创建并打开数据库
sqlite3 *db = NULL;
if (sqlite3_open("./dict.db", &db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_open sucess __%d__\n", __LINE__);
//创建表格,[数据库中代码怎么写,此处就怎么写]
char sql[128] = "create table if not exists dictionary (word char, mean char);";
char *errmsg = NULL;
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
// printf("table dictionary create sucess __%d__ \n", __LINE__);
printf("数据库写入中......\n");
//读取txt文件中的字符串
char buf[128] = "";
char buf_word[32] = ""; //英文单词
char buf_prop[32] = ""; //词性
char buf_mean[32] = ""; //单词释义
char *p_temp = NULL;
while (1)
{
bzero(buf, sizeof(buf));
bzero(buf_word, sizeof(buf_mean));
bzero(buf_mean, sizeof(buf_mean));
if (fgets(buf, sizeof(buf), fp) == NULL) //遇到换行会停止读取,并自动补\0
{
break; //读取结束,fgets返回值为NULL
}
//写入到数据库中
p_temp = buf;
int i = 0;
int j = 0;
//将读取到的一整串字符串分开
while (1)
{
if (*p_temp == ' ' && *(p_temp + 1) == ' ') //满足此条件停止单词的读取
{
//词性的寻找
while (1)
{
p_temp++;
if (*p_temp != ' ')
break;
}
break;
}
buf_word[i] = *p_temp;
i++;
p_temp++;
}
strcpy(buf_mean, p_temp);
buf_mean[strlen(buf_mean) - 1] = 0;
//此时已经可以循环分解开单词和词性以及释义
//写入到数据库中
char temp[128] = "";
sprintf(temp, "insert into dictionary values (\"%s\", \"%s\");", buf_word, buf_mean);
char *errmsg = NULL;
sqlite3_exec(db, temp, NULL, NULL, &errmsg);
}
printf("数据库写入成功\n");
//关闭数据库,释放内存空间
if (sqlite3_close(db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
//关闭fopen打开的英文单词只读文件
fclose(fp);
return 0;
}
//单词数据库文件的初始化
int word_init()
{
if (access("./dict.txt", F_OK) == -1)
{
printf("文本文档文件不存在\n");
}
else
{
if (access("./dict.db", F_OK) == 0)
{
printf("数据库文件已经存在\n");
}
else
{
word_import();
}
}
}
//注册信息的处理
int do_sign_up(int newfd, struct sockaddr_in cin, struct communication rcv_msg)
{
ssize_t res = -1;
//创建并打开用户信息数据库[查询单词和时间] //如果存在则直接打开,不存在会创建
sqlite3 *db2 = NULL;
if (sqlite3_open("./user.db", &db2) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db2), sqlite3_errmsg(db2));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//创建所有的用户信息表格,存储所有用户和密码,用户名为主键
char sql2[1512] = "";
sprintf(sql2, "create table if not exists all_msg (name char primary key, password char, status char);");
char *errmsg = NULL;
if (sqlite3_exec(db2, sql2, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
// printf("table all_msg create sucess __%d__ \n", __LINE__);
//插入信息的调用,如果插入失败,则表明重复注册
bzero(sql2, sizeof(sql2));
sprintf(sql2, "insert into all_msg values (\"%s\", \"%s\", \"N\");", rcv_msg.name, rcv_msg.msg);
strcpy(rcv_msg.msg, "用户注册成功");
if (sqlite3_exec(db2, sql2, NULL, NULL, &errmsg) != SQLITE_OK)
{
if (sqlite3_errcode(db2) == 19)
{
strcpy(rcv_msg.msg, "该用户重复注册");
}
}
//查询完毕,发送查询后的信息给客户端
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
//注册完毕后,应该再创建一个记录查询历史记录的表格[单词, 查询时间]
sprintf(sql2, "create table if not exists %s (word char, time char);", rcv_msg.name);
if (sqlite3_exec(db2, sql2, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
//关闭用户信息数据库,释放内存空间
if (sqlite3_close(db2) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db2), sqlite3_errmsg(db2));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
return 0;
}
//数据库的查看遍历
int do_select(sqlite3 *db, struct communication rcv_msg)
{
char sql[128] = "select name,password from all_msg";
char **pres = NULL; //遇到三级指针,在外面定义一个二级指针;函数运行结束,该指针存储最终查询的结果
int row, column; //结果的行数 和 结果的列数
char *errmsg = NULL;
//查询函数的调用
if (sqlite3_get_table(db, sql, &pres, &row, &column, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_get_table: %s\n", __LINE__, errmsg);
return -1;
}
// printf("sqlite3_get_table success __%d__\n", __LINE__);
//printf("row=%d column=%d\n", row, column); //查询出来整个table的行数和列数
//row中只有记录行数,没有表头
//而pres中包含表头的那一行,所以打印的时候需要将表头的那一行加上
char name[32] = "";
char password[32] = "";
int flag = 0;
for (int i = 1; i < (row + 1) * column; i++)
{
//printf("%s\t", pres[i]);
if (i % column == column - 1)
{
strcpy(name, pres[i - 1]);
strcpy(password, pres[i]);
// printf("=== %s %s", name, password);
// putchar(10); //此时name和password都遍历结束,可以在这里判断
//判断用户名和密码是否正确
if (strcmp(name, rcv_msg.name) == 0) //用户名存在数据库中,可以进一步执行登录操作
{
if (strcmp(password, rcv_msg.msg) == 0) //密码也和数据库中数据相同
{
strcpy(rcv_msg.msg, "登录成功");
flag = 1; //如果登陆成功,返回值为1
}
else
{
strcpy(rcv_msg.msg, "用户存在,但是密码不正确");
flag = 2;
}
break;
}
}
}
//printf("==========%d========%s\n", __LINE__, rcv_msg.msg);
//释放查询到的结果
sqlite3_free_table(pres);
pres = NULL;
return flag;
}
int is_online(sqlite3 *db, struct communication rcv_msg)
{
char sql[128] = "select name,status from all_msg";
char **pres = NULL;
int row, column;
char* errmsg = NULL;
//查询函数的调用
if (sqlite3_get_table(db, sql, &pres, &row, &column, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_get_table: %s\n", __LINE__, errmsg);
return -1;
}
// printf("sqlite3_get_table success __%d__\n", __LINE__);
for(int i = 0; i < (row+1)*column; i++)
{
if(i % column == column - 1)
{
if(strcmp(pres[i], "Y") == 0) //表示在线
{
//释放查询到的结果
sqlite3_free_table(pres);
pres = NULL;
return 1;
}
}
}
//释放查询到的结果
sqlite3_free_table(pres);
pres = NULL;
return 0;
}
//登录信息的处理
int do_log_on(int newfd, struct sockaddr_in cin, struct communication rcv_msg)
{
ssize_t res = -1;
//创建并打开用户信息数据库,所有用户名和密码
sqlite3 *db = NULL;
if (sqlite3_open("./user.db", &db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//循环遍历数据库,查看是否可以登录
if (do_select(db, rcv_msg) == 1)
{
// printf("==========%d=====%s===%s\n", __LINE__, rcv_msg.name, rcv_msg.msg); //输出结果为ff和gg,形参未改变实参
//查询完毕,发送查询后的信息给客户端
strcpy(rcv_msg.msg, "登录成功");
//可以登录成功的话,先查询是否重复登录,如果不重复,再实行后续语句
if(is_online(db,rcv_msg) == 1) //表示该用户在线
{
strcpy(rcv_msg.msg, "重复登录");
}
else
{
//登录成功后,修改用户的在线状态,更新为在线[没有必要再创建一个新的表格存储状态信息]
char sql[128] = "";
sprintf(sql, "update all_msg set status=\"Y\" where name=\"%s\";", rcv_msg.name);
char *errmsg = NULL;
//执行状态更新语句
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
}
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
}
else if (do_select(db, rcv_msg) == 2)
{
strcpy(rcv_msg.msg, "用户存在,但是密码不正确");
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
}
else if (do_select(db, rcv_msg) == 0)
{
strcpy(rcv_msg.msg, "用户不存在,请先注册");
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
}
//关闭用户信息数据库,释放内存空间
if (sqlite3_close(db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
return 0;
}
//记录查询时间
char *time_look(char *buf_time)
{
time_t time_now;
time(&time_now); //获取当前时间的秒数
//传入的形参为当前文件的修改秒数
struct tm * info_time = localtime(&time_now);
sprintf(buf_time, "%d-%d-%d %d:%d:%d", info_time->tm_year+1900, info_time->tm_mon+1,\
info_time->tm_mday, info_time->tm_hour, info_time->tm_min, info_time->tm_sec);
return buf_time;
}
//查询单词的处理
int look_up_word(int newfd, struct sockaddr_in cin, struct communication rcv_msg)
{
ssize_t res = -1;
//打开词典信息数据库
sqlite3 *db = NULL;
if (sqlite3_open("./dict.db", &db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//查询遍历字典库,是否存在的数据库中,存在的话,返回单词释义
char sql[128] = "select * from dictionary";
char **pres = NULL; //遇到三级指针,在外面定义一个二级指针;函数运行结束,该指针存储最终查询的结果
int row, column; //结果的行数 和 结果的列数
char *errmsg = NULL;
//查询函数的调用
if (sqlite3_get_table(db, sql, &pres, &row, &column, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_get_table: %s\n", __LINE__, errmsg);
return -1;
}
// printf("sqlite3_get_table success __%d__\n", __LINE__);
char temp[128] = "";
strcpy(temp, rcv_msg.msg);
//row中只有记录行数,没有表头
//而pres中包含表头的那一行,所以打印的时候需要将表头的那一行加上
char word[128] = "";
char mean[128] = "";
int flag = 0;
for (int i = 1; i < (row + 1) * column; i++)
{
if (i % column == column - 1)
{
bzero(word, sizeof(word));
bzero(mean, sizeof(mean));
strcpy(word, pres[i - 1]);
strcpy(mean, pres[i]);
// putchar(10); //此时name和password都遍历结束,可以在这里判断
//判断查询的单词是否在数据库中
if (strcmp(word, temp) == 0)
{
strcat(temp, " ");
strcat(temp, mean);
flag = 1;
/****************************查询时间的写入*********************************/
//打开对应的数据库
//创建并打开用户信息数据库[查询单词和时间] //如果存在则直接打开,不存在会创建
sqlite3 *db1 = NULL;
if (sqlite3_open("./user.db", &db1) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db1), sqlite3_errmsg(db1));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//查询单词和查询时间的写入
char sql3[128] = "";
char buf_time[32] = "";
time_look(buf_time);
sprintf(sql3, "insert into \"%s\" values (\"%s\", \"%s\");", rcv_msg.name, temp, buf_time);
strcpy(rcv_msg.msg, "用户注册成功");
if (sqlite3_exec(db1, sql3, NULL, NULL, &errmsg) != SQLITE_OK)
{
if (sqlite3_errcode(db1) == 19)
{
strcpy(rcv_msg.msg, "该用户重复注册");
}
}
//关闭数据库
if (sqlite3_close(db1) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db1), sqlite3_errmsg(db1));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
/**************************************************************************/
strcpy(rcv_msg.msg, temp);
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0) //发送给客户端查询到的结果
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
break;
}
else
{
flag = 0;
}
}
}
if(flag == 0)
{
strcat(temp, " 查询的单词不存在");
strcpy(rcv_msg.msg, temp);
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0) //发送给客户端查询到的结果
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
}
//释放查询到的结果
sqlite3_free_table(pres);
pres = NULL;
//关闭词典信息数据库,释放内存空间
if (sqlite3_close(db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
}
//历史记录查询
int history_check(int newfd, struct sockaddr_in cin, struct communication rcv_msg)
{
//打开对应的数据库,//如果存在则直接打开,不存在会创建,查询该数据库下对应的表格文件
sqlite3 *db1 = NULL;
if (sqlite3_open("./user.db", &db1) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db1), sqlite3_errmsg(db1));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//遍历历史记录的表格,然后输出发送
char sql1[128] = "";
sprintf(sql1, "select * from \"%s\"", rcv_msg.name);
char **pres = NULL; //遇到三级指针,在外面定义一个二级指针;函数运行结束,该指针存储最终查询的结果
int row, column; //结果的行数 和 结果的列数
char *errmsg = NULL;
//查询函数的调用
if (sqlite3_get_table(db1, sql1, &pres, &row, &column, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_get_table: %s\n", __LINE__, errmsg);
return -1;
}
// printf("sqlite3_get_table success __%d__\n", __LINE__);
//row中只有记录行数,没有表头
//而pres中包含表头的那一行,所以打印的时候需要将表头的那一行加上
char buf_word[40] = "";
char buf_time[20] = "";
char temp[64] = "";
char buf_snd[500] = "";
int flag = 0;
for (int i = 2; i < (row + 1) * column; i++)
{
if (i % column == column - 1)
{
bzero(buf_word,sizeof(buf_word));
bzero(buf_time,sizeof(buf_time));
bzero(temp, sizeof(temp));
strcpy(buf_word, pres[i - 1]);
strcpy(buf_time, pres[i]);
sprintf(temp, "%s %s\n",buf_word,buf_time);
strcat(buf_snd,temp);
}
}
//遍历查询结果结束,所有历史记录遍历完毕,将信息发送到客户端
strcpy(rcv_msg.msg, buf_snd);
if (send(newfd, &rcv_msg, sizeof(rcv_msg), 0) < 0) //发送给客户端查询到的结果
{
ERR_MSG("send");
return -1;
}
// printf("send success\n");
//释放查询到的结果
sqlite3_free_table(pres);
pres = NULL;
//关闭数据库
if (sqlite3_close(db1) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db1), sqlite3_errmsg(db1));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
}
//退出登录,对应客户的在线状态修改为下线
int log_out(int newfd, struct sockaddr_in cin, struct communication rcv_msg)
{
//打开用户信息数据库,修改用户在线状态为下线
sqlite3 *db = NULL;
if (sqlite3_open("./user.db", &db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//修改用户下线状态
char sql[128] = "";
sprintf(sql, "update all_msg set status=\"N\" where name=\"%s\";", rcv_msg.name);
char *errmsg = NULL;
//执行状态更新语句
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
//关闭用户信息数据库,释放内存空间
if (sqlite3_close(db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
return 0;
}
//处理客户端信息
int deal_cli_msg(int newfd, struct sockaddr_in cin)
{
struct communication rcv_msg;
while (1)
{
ssize_t res = -1;
//接收客户端发送过来消息,然后判断消息类型,做出相应的处理方式
res = recv(newfd, &rcv_msg, sizeof(rcv_msg), 0);
if (res < 0)
{
ERR_MSG("recv");
return -1;
}
else if (0 == res)
{
fprintf(stderr, "[%s : %d] newfd=%d 客户端下线\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);
//创建并打开用户信息数据库,所有用户名和密码
sqlite3 *db = NULL;
if (sqlite3_open("./user.db", &db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_open : %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("user_sqlite3_open sucess __%d__\n", __LINE__);
//修改用户下线状态
char sql[128] = "";
sprintf(sql, "update all_msg set status=\"N\" where name=\"%s\";", rcv_msg.name);
char *errmsg = NULL;
//执行状态更新语句
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec : %s\n", __LINE__, errmsg);
return -1;
}
//关闭用户信息数据库,释放内存空间
if (sqlite3_close(db) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_close: %d | %s\n", __LINE__, sqlite3_errcode(db), sqlite3_errmsg(db));
return -1;
}
// printf("sqlite3_close sucess __%d__\n", __LINE__);
return -1;
}
printf("%d---[%s : %d] newfd=%d : %s--%s\n", __LINE__,inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, rcv_msg.name, rcv_msg.msg);
//判断客户端消息类型,然后做出相应的处理操作
switch (rcv_msg.mode)
{
case 'S':
//客户端注册
do_sign_up(newfd, cin, rcv_msg);
break;
case 'L':
//客户端登录
do_log_on(newfd, cin, rcv_msg);
break;
case 'C':
//客户端查询单词 [查询单词的时候就记录查询时间]
look_up_word(newfd, cin, rcv_msg);
break;
case 'H':
//查询历史记录
history_check(newfd, cin, rcv_msg);
break;
case 'E':
//退出登录
log_out(newfd, cin, rcv_msg);
break;
default:
printf("客户端信息错误");
break;
}
}
return 0;
}
//主函数框架
int main(int argc, const char *argv[])
{
//捕获17号信号
__sighandler_t s = signal(SIGCHLD, zombie_callBack);
if (SIG_ERR == s)
{
ERR_MSG("signal");
return -1;
}
//创建流式套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd = %d\n", sfd);
//设置允许端口快速被重用
int resue = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充服务器的地址信息结构体
//真实的地址信息结构体根据地址族执行,AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //IP地址的网络字节序,ifconfig查看
//绑定---必须绑定
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success __%d__\n", __LINE__);
//将套接字设置为被动监听状态
if (listen(sfd, 128) < 0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n", __LINE__);
struct sockaddr_in cin; //存储连接成功的客户端的地址信息
socklen_t addrlen = sizeof(cin);
int newfd = -1;
pid_t cpid = 0;
struct communication rcv_msg; //结构体格式的通信协议
while (1)
{
//父进程专门负责连接
newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
if (newfd < 0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s : %d] newfd=%d 客户端连接成功\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);
//创建一个子进程,用于与客户端交互
cpid = fork();
if (0 == cpid) //子进程运行
{
//--->专门负责与客户端交互,所以客户端必须先连接成功,才能创建子进程
close(sfd); //对于子进程而言,sfd没有用
deal_cli_msg(newfd, cin); //处理客户端信息
close(newfd);
exit(0); //退出子进程,因为子进程只负责交互
}
else if (cpid > 0)
{
close(newfd); //对于父进程而言,newfd没用
}
else
{
ERR_MSG("fork");
return -1;
}
}
//关闭所有套接字文件描述符
close(sfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define ERR_MSG(msg) \
do \
{ \
fprintf(stderr, "line:%d ", __LINE__); \
perror(msg); \
} while (0)
#define PORT 6666 //1024 -- 49151
#define IP "192.168.31.115" //IP地址,本机IP,ifconfig查看
int function();
//信息传递协议
struct communication
{
char mode; //功能模式
char name[32]; //用户名
char msg[512]; //查询的单词,返回的释义,历史记录等
};
int menu(int num)
{
if (1 == num) //一级菜单
{
printf("---------------------------\n");
printf("----------1.注册-----------\n");
printf("----------2.登录-----------\n");
printf("-------3.退出客户端---------\n");
printf("---------------------------\n");
}
else if (2 == num)
{
printf("---------------------------\n");
printf("----------1.查询-----------\n");
printf("----------2.历史-----------\n");
printf("---3.返回上一级,退出登录 --\n");
printf("---------------------------\n");
}
return 0;
}
//注册功能
int sign_up(int cfd)
{
struct communication snd_msg;
char name[32] = "";
char msg[128] = "";
printf("请输入用户名>>>");
scanf("%s", name);
printf("请输入密码>>>");
scanf("%s", msg);
while (getchar() != 10);
snd_msg.mode = 'S';
strcpy(snd_msg.name, name);
strcpy(snd_msg.msg, msg);
//向服务器发送注册信息,由服务器确认是否有重复
if (send(cfd, &snd_msg, sizeof(snd_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
//接收服务器的查询注册结果信息
ssize_t res = 0;
res = recv(cfd, &snd_msg, sizeof(snd_msg), 0); //阻塞方式接收,等价于read函数
if (res < 0)
{
ERR_MSG("recv");
return -1;
}
else if (0 == res)
{
fprintf(stderr, "服务器下线\n");
//break;
}
printf("[%s]: %s\n", snd_msg.name, snd_msg.msg);
return 0;
}
//登录功能
int log_on(int cfd)
{
struct communication snd_msg;
char name[32] = "";
char msg[128] = "";
printf("请输入用户名>>>");
scanf("%s", name);
printf("请输入密码>>>");
scanf("%s", msg);
while (getchar() != 10);
snd_msg.mode = 'L';
strcpy(snd_msg.name, name);
strcpy(snd_msg.msg, msg);
//向服务器发送登录信息,由服务器确认是否存在,存在的话登录,密码不正确时提示密码错误
if (send(cfd, &snd_msg, sizeof(snd_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
//接收服务器的登录返回信息
ssize_t res = 0;
res = recv(cfd, &snd_msg, sizeof(snd_msg), 0); //阻塞方式接收,等价于read函数
if (res < 0)
{
ERR_MSG("recv");
return -1;
}
else if (0 == res)
{
fprintf(stderr, "服务器下线\n");
//break;
}
printf("[%s]: %s\n", snd_msg.name, snd_msg.msg);
//判断登录后是否成功,然后进行后续操作
if (strcmp(snd_msg.msg, "登录成功") == 0)
{
function(cfd, snd_msg); //二级菜单的功能
return 0;
}
return 0;
}
//查询单词
int look_word(int cfd, struct communication snd_msg)
{
snd_msg.mode = 'C';
char msg[128] = "";
printf("请输入要查询的单词>>>");
scanf("%s", msg);
while (getchar() != 10);
strcpy(snd_msg.msg, msg);
//向服务器发送查询信息,由服务器返回查询的单词的含义
if (send(cfd, &snd_msg, sizeof(snd_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
//接收服务器的登录返回信息
ssize_t res = 0;
res = recv(cfd, &snd_msg, sizeof(snd_msg), 0); //阻塞方式接收,等价于read函数
if (res < 0)
{
ERR_MSG("recv");
return -1;
}
else if (0 == res)
{
fprintf(stderr, "服务器下线\n");
//break;
}
printf("[%s]:查询单词及释义: %s\n",snd_msg.name, snd_msg.msg);
return 0;
}
//查询历史记录
int look_history(int cfd, struct communication snd_msg)
{
snd_msg.mode = 'H'; //查询历史记录
//根据当前用户的用户名查询对应的历史记录
//向服务器发送历史查询信息,由服务器返回历史记录
if (send(cfd, &snd_msg, sizeof(snd_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
//接收服务器发送过来的历史记录信息
ssize_t res = 0;
res = recv(cfd, &snd_msg, sizeof(snd_msg), 0); //阻塞方式接收,等价于read函数
if (res < 0)
{
ERR_MSG("recv");
return -1;
}
else if (0 == res)
{
fprintf(stderr, "服务器下线\n");
//break;
}
printf("[%s]用户下查询到的历史记录:\n", snd_msg.name);
printf("%s\n", snd_msg.msg);
return 0;
}
//返回上一级,退出登录
int log_out(int cfd, struct communication snd_msg)
{
snd_msg.mode = 'E'; //当前用户退出登录信息
//向服务器发送退出登录信息
if (send(cfd, &snd_msg, sizeof(snd_msg), 0) < 0)
{
ERR_MSG("send");
return -1;
}
return 0;
}
//二级菜单功能函数选择
int function(int cfd, struct communication snd_msg)
{
char choose = 0;
while (1)
{
menu(2);
printf("请输入您需要的功能>>>");
choose = getchar();
while (getchar() != 10); //吸收垃圾字符
switch (choose)
{
case '1':
//查询单词
look_word(cfd, snd_msg);
break;
case '2':
//查询的历史记录
look_history(cfd, snd_msg);
break;
case '3':
//返回上一级
log_out(cfd, snd_msg);
return 0;
default:
printf("输入错误,请重新输入");
}
}
}
int main(int argc, const char *argv[])
{
//创建流式套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create sucess cfd = %d __%d__\n", cfd, __LINE__);
//填充服务器的地址信息结构体---给connect函数使用,指定要连接的服务器
//bind参数为struct sockaddr *addr,通用结构体指针,
//真实的地址信息结构体根据地址族执行,AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填写AF_INET
sin.sin_port = htons(PORT); //端口号的网络字节序,1024 - 49151
sin.sin_addr.s_addr = inet_addr(IP); //IP地址的网络字节序,ifconfig可查看
//连接服务器
if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
ERR_MSG("connect");
}
printf("connect sucess \n");
char buf[128] = "";
ssize_t res = 0;
struct communication snd_msg; //结构体格式的通信协议
char choose = 0;
while (1)
{
//菜单选择功能:注册,登录,退出
//system("clear");
menu(1);
printf("请输入>>>");
choose = getchar();
while (getchar() != 10); //吸收垃圾字符
switch (choose)
{
case '1':
//注册功能
sign_up(cfd);
break;
case '2':
//登录
log_on(cfd);
break;
case '3':
//退出
exit(0);
default:
printf("输入错误,请重新输入\n");
}
//printf("请输入任意字符清屏>>> ");
// while (getchar() != 10); //还留着注释,因为这里有一个故事
}
//关闭套接字文件描述符
close(cfd);
return 0;
}