Linux套接字+Sqlite实例:客户端-服务器应用程序教程
本文将详细介绍如何创建一个基于客户端-服务器架构的应用程序,实现用户注册、登录、单词查询以及历史记录查询。该应用通过TCP套接字进行客户端和服务器之间的通信,并通过SQLite数据库进行用户和查询记录的管理。教程会逐步解析客户端和服务器端的实现,特别是与SQLite数据库的交互部分。
1. 系统概述
本系统包括两部分:客户端和服务器端。客户端通过与服务器通信,执行不同的操作,如注册、登录、查询单词和查询历史记录。服务器端负责接收和处理这些请求,操作数据库(SQLite)来管理用户信息和查询记录。
1.1 功能列表
- 用户注册:客户端提供用户名和密码,服务器将其存储到数据库中。
- 用户登录:客户端提供用户名和密码,服务器验证其正确性。
- 单词查询:客户端输入查询单词,服务器返回单词的释义。
- 历史记录查询:客户端查看之前查询过的单词。
2. 服务器端实现
2.1 初始化与数据库连接
服务器端负责接收来自客户端的请求并处理相关操作。我们首先需要打开数据库并创建服务器套接字。
2.1.1 打开SQLite数据库
在服务器端,使用 sqlite3_open
打开一个名为 my.db
的SQLite数据库:
if(sqlite3_open(DATABASE, &db) != SQLITE_OK)
{printf("%s\n", sqlite3_errmsg(db));return -1;
}
else
{printf("open DATABASE success!\n");
}
这里我们定义了一个常量 DATABASE
,它指定了数据库的文件名(my.db
)。如果数据库文件不存在,SQLite会自动创建一个新的数据库。
2.2 服务器主程序
服务器端通过 socket()
创建一个TCP套接字,然后绑定端口并监听客户端连接。
2.2.1 创建套接字并绑定端口
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{perror("fail to socket.\n");return -1;
}bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]); // 服务器 IP
serveraddr.sin_port = htons(atoi(argv[2])); // 服务器端口号if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{perror("fail to bind.\n");return -1;
}if(listen(sockfd, 5) < 0)
{perror("fail to listen");
}
socket()
创建套接字。bind()
绑定IP地址和端口号。listen()
使套接字处于监听状态,准备接收客户端连接。
2.2.2 处理客户端请求
服务器使用 accept()
接受客户端的连接请求并为每个客户端创建一个新的子进程来处理请求。子进程调用 do_client()
函数来处理客户端的请求。
if((acceptfd = accept(sockfd, NULL, NULL)) < 0)
{perror("fail to accept");return -1;
}if((pid = fork()) < 0)
{perror("fail to fork!");return -1;
}
else if(pid == 0) // 子进程
{close(sockfd); // 子进程关闭监听套接字do_client(acceptfd, db); // 处理客户端请求
}
else // 父进程
{close(acceptfd); // 父进程关闭连接套接字
}
在 fork()
子进程中,关闭监听套接字,并调用 do_client()
函数进行实际处理。
2.3 处理具体操作
服务器通过 do_client()
根据消息类型来分发任务(注册、登录、查询单词、查询历史记录)。
2.3.1 处理注册请求
服务器接收到注册请求后,将客户端传来的用户名和密码插入到数据库 usr
表中。
void do_register(int acceptfd, MSG *msg, sqlite3 *db)
{char sql[512];char *errmsg;snprintf(sql, sizeof(sql), "insert into usr values('%s', '%s');", msg->name, msg->data);if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){strcpy(msg->data, "usr name already exist.");}else{strcpy(msg->data, "OK!");}send(acceptfd, msg, sizeof(MSG), 0);
}
- 如果用户名已存在,返回
"usr name already exist."
。 - 否则,插入新用户信息,并返回
"OK!"
表示成功。
2.3.2 处理登录请求
登录请求的处理方式是检查数据库中是否存在匹配的用户名和密码。如果存在,返回 "OK"
;否则返回 "usr/passwd wrong."
。
int do_login(int acceptfd, MSG *msg , sqlite3 *db)
{char sql[512] = {};char *errmsg;int nrow, ncloumn;char **resultp;sprintf(sql, "select * from usr where name = '%s' and pass = '%s';", msg->name, msg->data);if(sqlite3_get_table(db, sql, &resultp, &nrow, &ncloumn, &errmsg) != SQLITE_OK){return -1;}if(nrow == 1){strcpy(msg->data, "OK");send(acceptfd, msg, sizeof(MSG), 0);return 1;}else{strcpy(msg->data, "usr/passwd wrong.");send(acceptfd, msg, sizeof(MSG), 0);}return 0;
}
2.3.3 处理单词查询请求
服务器根据客户端请求的单词进行查询。如果找到了单词,就返回其释义;如果没有找到,返回 "Not found!"
。
int do_query(int acceptfd, MSG *msg , sqlite3 *db)
{char word[64];int found = 0;char date[128] = {};char sql[512] = {};strcpy(word, msg->data); // 获取要查询的单词found = do_searchword(acceptfd, msg, word);if(found == 1){get_date(date); // 获取当前时间sprintf(sql, "insert into record values('%s', '%s', '%s')", msg->name, date, word);if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){return -1;}}else{strcpy(msg->data, "Not found!");}send(acceptfd, msg, sizeof(MSG), 0);return 0;
}
2.3.4 处理历史记录查询
服务器从 record
表中获取客户端的查询历史记录,并逐条发送给客户端。
int do_history(int acceptfd, MSG *msg, sqlite3 *db)
{char sql[512] = {};sprintf(sql, "select * from record where name = '%s'", msg->name);if(sqlite3_exec(db, sql, history_callback, (void *)&acceptfd, &errmsg) != SQLITE_OK){printf("%s\n", errmsg);}msg->data[0] = '\0'; // 发送结束标志send(acceptfd, msg, sizeof(MSG), 0);return 0;
}
3. 客户端实现
客户端通过TCP套接字连接到服务器,发送请求并接收响应。
3.1 客户端功能
3.1.1 用户注册
客户端通过输入用户名和密码,发送注册请求到服务器,服务器返回注册结果。
int do_register(int sockfd, MSG *msg)
{msg->type = R;printf("Input name:"); scanf("%s", msg->name);getchar();printf("Input passwd:"); scanf("%s", msg->data);if(send(sockfd, msg, sizeof(MSG), 0) < 0){printf("fail to send.\n");return -1;}if(recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("Fail to recv.\n");return -1;}printf("%s\n", msg->data);return 0;
}
3.1.2 用户登录
登录功能要求用户输入用户名和密码,客户端向服务器发送登录请求,服务器返回验证结果。
int do_login(int sockfd, MSG *msg)
{msg->type = L;printf("Input name:"); scanf("%s", msg->name);getchar();printf("Input passwd:"); scanf("%s", msg->data);if(send(sockfd, msg, sizeof(MSG), 0) < 0){printf("fail to send.\n");return -1;}if(recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("Fail to recv.\n");return -1;}if(strncmp(msg->data, "OK", 3) == 0){printf("Login ok!\n");return 1;}else {printf("%s\n", msg->data);}return 0;
}
3.2 其他功能实现
类似地,客户端实现了查询单词和查询历史记录的功能。通过输入单词或历史记录命令,客户端发送请求并接收服务器返回的结果。
4. 总结
通过这篇教程,我们介绍了如何实现一个基于TCP套接字的客户端-服务器应用,并结合SQLite数据库来存储用户信息和查询历史。关键的实现步骤包括:
- 使用TCP套接字进行客户端和服务器之间的通信。
- 使用SQLite数据库管理用户注册、登录、单词查询和历史记录。
- 客户端和服务器通过结构化的消息(
MSG
结构体)进行数据交换。
通过这种方式,我们构建了一个简单但功能完善的客户端-服务器系统。