前言
在学习网络编程时,我们已了解到TCP并发服务器既可以保证多个客户端同时访问服务器,同时也保证了数据传输的完整和准确,大大提高了服务器实用性。而我作为初学网络编程的小白,和大家分享一个IO多路复用实现在字典文件中查询单词的程序。不足之处,欢迎各位指正!
题目要求:
IO多路复用实现在字典文件中查询单词
注意事项:
1、服务器程序中注意文件描述符集合的及时更新,包括新增文件描述符和剔除;
2、服务器程序中用到sqlite3数据库,操作数据库时注意sql语句的正确性;
3、服务器程序中执行单词查询操作时,注意光标的偏移位置,否则会造成单词查询不到的情况;
4、服务器程序中操作sqlite3数据库时,若调用sqlite3_get_table函数,需调用sqlite3_free_table进行释放,避免内存泄露。
流程图:
代码实现:
服务器:
server.h
#ifndef __SERVER_H__
#define __SERVER_H__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
#define N_SQL 1024
#define N_name 32
#define N_txt 512
// 声明数据包类型:
typedef struct _MSG
{
char type; // 类型( R 注册 L 登录 Q 退出 W 单词查询 H 历史记录 )
char name[N_name]; // 用户名
char txt[N_txt]; // 密码、单词、单词解释
} msg_t;
// 函数声明:
void deal_register(sqlite3 *db, int acceptfd, msg_t msg);
void deal_login(sqlite3 *db, int acceptfd, msg_t msg);
void deal_query(FILE *fp, int acceptfd, msg_t msg, sqlite3 *db);
void deal_record(sqlite3 *db, int acceptfd, msg_t msg);
int callback_record(void *arg, int column, char **f_value, char **f_name);
#endif
server.c
#include "server.h"
// 标志位:用于标识客户端是否存在查询记录。if(count==0)说明无查询记录,else说明有查询记录:
int flag = 0;
// 处理用户注册
void deal_register(sqlite3 *db, int acceptfd, msg_t msg)
{
// printf("msg.type = [%c]\n", msg.type);
char sql[N_SQL] = {0};
int ret = 0;
char **result = NULL;
int nrow = 0;
int ncolumn = 0;
// 组装sql语句:查询用户名是否存在
sprintf(sql, "SELECT * FROM usermsg WHERE name='%s'", msg.name);
// printf("sql = [%s]\n", sql);
ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_get_table (SELECT *FROM usermsg) error\n");
}
// printf("nrow = %d\n",nrow);
if (0 == nrow)
{
// 说明用户不存在,直接注册:
// 组装注册的sql语句
sprintf(sql, "INSERT INTO usermsg VALUES('%s','%s')", msg.name, msg.txt);
// printf("sql = [%s]\n", sql);
ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_exec (INSERT INTO usermsg VALUES) error\n");
}
strcpy(msg.txt, "Register Success!,Please Login...");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
}
else
{
// 说明用户存在,提示用户登录
strcpy(msg.txt, "User Exist,Please Login!...");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
}
// 使用sqlite3_get_table函数时需对资源进行释放
sqlite3_free_table(result);
return;
}
// 处理用户登录
void deal_login(sqlite3 *db, int acceptfd, msg_t msg)
{
char sql[N_SQL] = {0};
int ret = 0;
char **result = NULL;
int nrow = 0;
int ncolumn = 0;
组装sql语句:查询用户名是否存在
sprintf(sql, "SELECT *FROM usermsg WHERE name='%s'", msg.name);
// printf("sql = [%s]\n", sql);
ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_get_table error\n");
}
if (0 == nrow)
{ // 说明用户不存在,需要注册
strcpy(msg.txt, "User Not Exist,Please Register!...");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
}
else
{
// 用户存在,检验用户名和密码是否一致
// 组装sql语句:
sprintf(sql, "SELECT *FROM usermsg WHERE name='%s' and passwd='%s'", msg.name, msg.txt);
// printf("sql = [%s]\n", sql);
ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_get_table error\n");
}
if (0 == nrow)
{ // 用户名和密码不一致
strcpy(msg.txt, "Username or Passwd Error! Please Try Agin...");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
}
else
{ // 登录成功
strcpy(msg.txt, "Login Success!");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
}
}
// 调用sqlite3_get_table函数需进行资源释放
sqlite3_free_table(result);
return;
}
// 处理用户单词查询(文件中查询)
void deal_query(FILE *fp, int acceptfd, msg_t msg, sqlite3 *db)
{
char buff[512] = {0};
char sql[N_SQL] = {0};
char sys_time[32] = {0};
int i = 0;
int j = 0;
int ret = 0;
// printf("recv:msg.txt = [%s]\n", msg.txt);
while (NULL != fgets(buff, sizeof(buff), fp))
{
//查到该单词
if ((!strncmp(buff, msg.txt, strlen(msg.txt))) && (buff[strlen(msg.txt)] == ' '))
{
// 查询到单词的同时 先将用户名、查询时间和单词插入到数据库record表中
time_t tm = 0;
struct tm *nowtm = NULL;
if (-1 == (tm = time(NULL)))
{
ERRLOG("time error\n");
}
if (NULL == (nowtm = localtime(&tm)))
{
ERRLOG("localtime error\n");
}
// printf("%4d-%02d-%02d %02d:%02d:%02d\n", nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday,
// nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec);
sprintf(sys_time, "%4d-%02d-%02d %02d:%02d:%02d", nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday,
nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec);
// 组装sql语句
sprintf(sql, "INSERT INTO record VALUES('%s','%s','%s')", msg.name, sys_time, msg.txt);
// printf("sql = %s\n", sql);
// 执行sql语句
ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_exec error\n");
}
while (buff[i] != ' ')
{
i++;
}
while (buff[i] == ' ')
{
i++;
}
while (buff[i] != '\0')
{
msg.txt[j++] = buff[i++];
}
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("%s", msg.txt);
fseek(fp, 0, SEEK_SET);
return;
}
}
// 未查到要查询的单词:直接提示单词未找到!
strcpy(msg.txt, "Sorry,Word Not Exist!,Please Input Again...");
fseek(fp, 0, SEEK_SET);
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("[%s]\n", msg.txt);
return;
}
// 处理用户查询记录
void deal_record(sqlite3 *db, int acceptfd, msg_t msg)
{
char buff[256] = {0};
char sql[N_SQL] = {0};
int i = 0;
int ret = 0;
// 组装sql语句
sprintf(sql, "SELECT *FROM record WHERE name='%s'", msg.name);
// printf("sql = [%s]\n", sql);
// 执行sql语句
ret = sqlite3_exec(db, sql, callback_record, (void *)&acceptfd, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_exec error\n");
}
if (flag == 0)
{
strcpy(msg.txt, "No record to query...");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
}
flag = 0;
strcpy(msg.txt, "---over---");
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
// printf("msg.txt = %s\n",msg.txt);
return;
}
// 查询历史记录回调函数
int callback_record(void *arg, int column, char **f_value, char **f_name)
{
int acceptfd;
msg_t msg;
acceptfd = *(int *)arg;
sprintf(msg.txt, "%s %s", f_value[1], f_value[2]);
flag++;
// printf("flag = %d\n", flag);
if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
// printf("msg.txt = %s\n",msg.txt);
return 0;
}
main.c
// 创建单词查询的“服务器”程序
#include "server.h"
extern int flag;
int main(int argc, const char *argv[])
{
int sockfd;
//入参合理性检查
if (3 != argc)
{
printf("Input error,Please try again!...\n");
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
// 1.创建流式套接字
if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
{
ERRLOG("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr = {
.sin_family = AF_INET,
.sin_port = htons(atoi(argv[2])),
.sin_addr.s_addr = inet_addr(argv[1])};
socklen_t serveraddr_len = sizeof(serveraddr);
// 3.绑定网络信息结构体和套接字
if (-1 == bind(sockfd, (const struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("bind error");
}
// 4.设置流式套接字为被监听状态
if (-1 == listen(sockfd, 10))
{
ERRLOG("listen error");
}
printf("服务器已启动\n");
// 关注客户端网络信息结构体信息
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
int acceptfd;
int ret = 0;
int i = 0;
int nbytes = 0;
msg_t msg;
memset(&msg, 0, sizeof(msg_t));
// 创建文件描述符集合
int max_fd = 0;
fd_set readfds;
FD_ZERO(&readfds);
fd_set readfds_temp;
FD_ZERO(&readfds_temp);
// 将文件描述符添加到集合中,并更新最大的文件描述符
FD_SET(sockfd, &readfds);
max_fd = max_fd > sockfd ? max_fd : sockfd;
// 打开数据库文件并创建usermsg表、record表
sqlite3 *db = NULL;
char sql[N_SQL] = {0};
ret = sqlite3_open("./dict_db", &db);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_open error");
}
// printf("打开数据库文件成功...\n");
// 设置主键 用户名是唯一的
sprintf(sql, "CREATE TABLE if not exists usermsg(name text primary key,passwd text)");
ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_exec (CREATE TABLE usermsg) error");
}
// printf("创建数据库文件的usermsg表成功...\n");
sprintf(sql, "CREATE TABLE if not exists record(name text,time text,word text)");
ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
if (SQLITE_OK != ret)
{
ERRLOG("sqlite3_exec (CREATE TABLE record) error");
}
// printf("创建数据库文件的record表成功...\n");
// 以 只读 的方式打开词典文件
FILE *fp = NULL;
if (NULL == (fp = fopen("./dict.txt", "r")))
{
ERRLOG("fopen error");
}
while (1)
{
readfds_temp = readfds;
ret = select(max_fd + 1, &readfds_temp, NULL, NULL, NULL);
if (-1 == ret)
{
ERRLOG("select error");
}
for (i = 3; i <= max_fd && ret != 0; i++)
{
if (FD_ISSET(i, &readfds_temp))
{
if (i == sockfd)
{
// 新用户链接服务器
acceptfd = accept(i, (struct sockaddr *)&clientaddr, &clientaddr_len);
if (-1 == acceptfd)
{
ERRLOG("accept error");
}
printf("客户端[%d]已链接\n", acceptfd);
// 将新产生的文件描述符添加到集合中,并更新最大文件描述符
FD_SET(acceptfd, &readfds);
max_fd = max_fd > acceptfd ? max_fd : acceptfd;
}
else
{
// 客户端发来数据
memset(&msg, 0, sizeof(msg_t));
nbytes = recv(i, &msg, sizeof(msg_t), 0);
if (-1 == nbytes)
{
ERRLOG("Recv error\n");
}
else if (0 == nbytes)
{
printf("客户端[%d]断开了链接...\n", i);
FD_CLR(i, &readfds);
close(i);
continue;
}
// 处理客户端的数据
printf("客户端[%d]发来请求...\n", acceptfd);
switch (msg.type)
{
case 'R': //处理注册
deal_register(db, i, msg);
break;
case 'L': //处理登录
deal_login(db, i, msg);
break;
case 'Q': //处理退出
printf("客户端[%d]已退出...\n", i);
FD_CLR(i, &readfds);
close(i);
continue;
case 'W': //处理单词查询
deal_query(fp, i, msg, db);
break;
case 'H': //处理历史查询
deal_record(db, i, msg);
break;
}
}
ret--;
}
}
}
// 关闭文件
fclose(fp);
// 关闭数据库文件
ret = sqlite3_close(db);
// 7.关闭套接字
close(sockfd);
return 0;
}
客户端:
client.h
#ifndef __CLIENT_H__
#define __CLIENT_H__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
#define N_name 32
#define N_txt 512
// 声明数据包类型:
typedef struct _MSG
{
char type; // 类型(1 注册/单词查询 2 登录/历史记录 3 退出 )
char name[N_name]; // 用户名
char txt[N_txt]; // 密码、单词、单词解释
} msg_t;
// 函数声明:
void print_men_one(void);
void print_men_two(void);
void deal_register(int sockfd, msg_t msg);
void deal_login(int sockfd, msg_t msg);
void deal_query(int sockfd, msg_t msg);
void deal_record(int sockfd, msg_t msg);
#endif
client.c
#include "client.h"
// 定义标志位:用来标志注册、登录的状态:
int flag = 0;
// 打印菜单(注册、登录或退出)
void print_men_one(void)
{
printf("-------------------------------\n");
printf("| 1.Register 2.Login 3.Quit |\n");
printf("-------------------------------\n");
return;
}
// 打印菜单(查询、历史记录或退出)
void print_men_two(void)
{
printf("-----------------------------\n");
printf("| 1.Query 2.Record 3.Quit |\n");
printf("-----------------------------\n");
return;
}
// 用户注册函数:
void deal_register(int sockfd, msg_t msg)
{
msg.type = 'R';
// printf("msg.type = %c\n", msg.type);
printf("Please input username:");
scanf("%s", msg.name);
printf("Please input passwd:");
scanf("%s", msg.txt);
// 发送消息
if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
// printf("Send register success!\n");
// 接收消息
int nbytes = recv(sockfd, &msg, sizeof(msg_t), 0);
if (-1 == nbytes)
{
ERRLOG("Recv error");
}
printf("[%s]\n", msg.txt);
return;
}
// 登录函数
void deal_login(int sockfd, msg_t msg)
{
msg.type = 'L';
// printf("msg.type = %c\n", msg.type);
if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
// printf("Send dogin success!\n");
// 接收消息
int nbytes = recv(sockfd, &msg, sizeof(msg_t), 0);
if (-1 == nbytes)
{
ERRLOG("Recv error");
}
printf("[%s]\n", msg.txt);
if (!strcmp(msg.txt, "User Not Exist,Please Register!..."))
{
flag = 1;
}
if (!strcmp(msg.txt, "Username or Passwd Error! Please Try Agin..."))
{
flag = 2;
}
// printf("flag = %d\n",flag);
return;
}
// 用户查询单词函数
void deal_query(int sockfd, msg_t msg)
{
msg.type = 'W';
// printf("msg.type = %c\n", msg.type);
// 循环查询单词
while (1)
{
printf("Please input word:");
scanf("%s", msg.txt);
// 从终端输入'#',跳出循环
if (!strcmp(msg.txt, "#"))
{
break;
}
// 发送消息
if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
// printf("%s\n", msg.txt);
// 接收消息
if (-1 == recv(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Recv error");
}
printf("Result: %s", msg.txt);
}
return;
}
// 查询历史记录
void deal_record(int sockfd, msg_t msg)
{
msg.type = 'H';
// printf("msg.type = %c\n", msg.type);
if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error");
}
// printf("%s\n", msg.txt);
// 循环接收消息
while (1)
{
if (-1 == recv(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Recv error");
}
// 判断是否接收数据完毕
if (!strcmp(msg.txt, "---over---"))
{
break;
}
printf("[%s]\n", msg.txt);
}
return;
}
main.c
// 创建单词查询的“客户端”程序
#include "client.h"
extern int flag;
int main(int argc, const char *argv[])
{
//入参合理性检查
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
// 创建流式套接字
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
ERRLOG("socket error");
}
// 填充服务器网络信息结构体
struct sockaddr_in serveraddr = {
.sin_family = AF_INET,
.sin_port = htons(atoi(argv[2])),
.sin_addr.s_addr = inet_addr(argv[1])};
socklen_t serveraddr_len = sizeof(serveraddr);
// 链接服务器
if (-1 == connect(sockfd, (const struct sockaddr *)&serveraddr, serveraddr_len))
{
printf("connect error");
exit(-1);
}
printf("链接[%s %d] 的服务器成功...\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
int choose_one = 0;
int choose_two = 0;
msg_t msg;
memset(&msg, 0, sizeof(msg));
while (1)
{
// 打印选项菜单(注册、登录或退出)
print_men_one();
printf("Please choose:");
scanf("%d", &choose_one);
switch (choose_one)
{
case 1: // 注册
RE_REDISTER:
deal_register(sockfd, msg);
// break;
case 2: // 登录
RE_LOGIN:
printf("Please input username:");
scanf("%s", msg.name);
printf("Please input passwd:");
scanf("%s", msg.txt);
deal_login(sockfd, msg);
// printf("flag = %d\n",flag);
// flag == 1 说明用户未注册,返回至“注册”界面,客户端直接输入用户名、密码进行注册
if (flag == 1)
{
flag = 0;
goto RE_REDISTER;
}
// flag == 2 说明用户名与密码不一致,返回至“登录”界面,客户端直接输入用户名、密码进行登录
if (flag == 2)
{
flag = 0;
goto RE_LOGIN;
}
while (1)
{
print_men_two();
printf("Please choose:");
scanf("%d", &choose_two);
switch (choose_two)
{
case 1: // 查询单词
deal_query(sockfd, msg);
break;
case 2: // 查询历史记录
deal_record(sockfd, msg);
break;
}
if (3 == choose_two)
{
break;
}
}
break;
}
if (3 == choose_one)
{
msg.type = 'Q';
// printf("msg.type = %c\n", msg.type);
if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
{
ERRLOG("Send error\n");
}
printf("Welcome back...\n");
break;
}
}
// 关闭
close(sockfd);
return 0;
}