1. 整体功能概述
实现了一个简单的用户注册和登录系统,采用客户端 - 服务器(C/S)架构。
客户端可以选择注册或登录操作,将用户名和密码发送给服务器,服务器接收请求后处理并返回相应的结果给客户端。
服务器使用 SQLite 数据库来存储用户信息。
2. 客户端功能实现分析
- 功能:客户端程序,允许用户选择注册或登录操作,输入用户名和密码,将请求发送给服务器,并接收服务器的响应结果。
- 输入:用户选择(1 表示注册,2 表示登录)、用户名、密码。
- 输出:服务器返回的处理结果(如注册成功、用户名已存在、登录成功等)。
1.1. 详细步骤
1.1.1. 包含必要的头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
这些头文件提供了标准输入输出、内存管理、字符串处理、套接字编程和 IP 地址转换等功能。
1.1.2. 定义协议包结构体
#define BUF_SIZE 1024typedef struct {uint8_t op; // 操作码,1表示注册,2表示登录uint16_t data_len; // 数据长度(网络字节序)char data[BUF_SIZE];// 存储用户名、密码等数据
} ProtocolPack;
该结构体用于封装客户端和服务器之间传输的协议包,包含操作码、数据长度和数据内容。
1.1.3. 创建套接字并连接服务器
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(8888)
};
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
使用 socket
函数创建一个 TCP 套接字,设置服务器地址和端口,然后使用 connect
函数连接到服务器。
1.1.4. 获取用户输入
int choice;
printf("1. 注册\n2. 登录\n选择: ");
scanf("%d", &choice);char username[32], password[32];
printf("用户名: ");
scanf("%31s", username);
printf("密码: ");
scanf("%31s", password);
提示用户选择操作类型(注册或登录),并输入用户名和密码。
1.1.5. 构建协议包并发送
ProtocolPack pack = {0};
pack.op = (uint8_t)choice;
sprintf(pack.data, "%s %s", username, password);
pack.data_len = htons(strlen(pack.data));
send(sock, &pack, sizeof(ProtocolPack), 0);
将用户选择的操作码、用户名和密码封装到协议包中,并使用 send
函数发送给服务器。
1.1.6. 接收服务器响应并输出结果
ProtocolPack resp_pack;
recv(sock, &resp_pack, sizeof(ProtocolPack), 0);
printf("结果: %s\n", resp_pack.data);
使用 recv
函数接收服务器的响应协议包,并输出结果。
1.1.7. 关闭套接字
close(sock);
3.服务器功能实现分析
- 功能:服务器程序,监听客户端的连接请求,接收客户端发送的注册或登录请求,处理请求并将结果返回给客户端。使用 SQLite 数据库存储用户信息。
- 输入:客户端发送的协议包,包含操作码、用户名和密码。
- 输出:处理结果(如注册成功、用户名已存在、登录成功等),封装在协议包中返回给客户端。
3.1. 详细步骤
3.1.1. 包含必要的头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sqlite3.h>
这些头文件提供了标准输入输出、内存管理、字符串处理、套接字编程、事件驱动 I/O 和 SQLite 数据库操作等功能。
3.1.2. 定义常量和协议包结构体
#define MAX_EVENTS 100
#define BUF_SIZE 1024typedef struct {uint8_t op; // 操作码,1表示注册,2表示登录uint16_t data_len; // 数据长度(网络字节序)char data[BUF_SIZE];// 存储用户名、密码等数据
} ProtocolPack;
MAX_EVENTS
定义了 epoll_wait
函数返回的最大事件数量,BUF_SIZE
定义了协议包数据部分的最大缓冲区大小。
3.1.3. 初始化数据库
sqlite3* init_db() {sqlite3* db;int rc = sqlite3_open("user.db", &db);if (rc) {fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));exit(1);}const char* sql = "CREATE TABLE IF NOT EXISTS users (""id INTEGER PRIMARY KEY AUTOINCREMENT,""username TEXT UNIQUE NOT NULL,""password TEXT NOT NULL)";char* errmsg;rc = sqlite3_exec(db, sql, 0, 0, &errmsg);if (rc != SQLITE_OK) {fprintf(stderr, "SQL error: %s\n", errmsg);sqlite3_free(errmsg);}return db;
}
打开或创建一个名为 user.db
的 SQLite 数据库,并创建一个名为 users
的表,用于存储用户信息。
3.1.4. 处理用户注册逻辑
int handle_register(sqlite3* db, const char* username, const char* password) {const char* sql = "INSERT INTO users (username, password) VALUES (?, ?)";sqlite3_stmt* stmt;int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);if (rc != SQLITE_OK) return -1;sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);rc = sqlite3_step(stmt);if (rc == SQLITE_DONE) {sqlite3_finalize(stmt);return 0; // 注册成功}if (rc == SQLITE_CONSTRAINT_UNIQUE) {sqlite3_finalize(stmt);return 1; // 用户名已存在}sqlite3_finalize(stmt);return -1;
}
将用户的用户名和密码插入到 users
表中,处理可能的错误情况,如 SQL 执行失败或用户名已存在。
3.1.5. 处理用户登录逻辑
int handle_login(sqlite3* db, const char* username, const char* password) {const char* sql = "SELECT COUNT(*) FROM users WHERE username=? AND password=?";sqlite3_stmt* stmt;int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);if (rc != SQLITE_OK) return -1;sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);rc = sqlite3_step(stmt);if (rc == SQLITE_ROW) {int count = sqlite3_column_int(stmt, 0);sqlite3_finalize(stmt);return count == 1 ? 0 : -1; // 登录成功返回0,否则失败}sqlite3_finalize(stmt);return -1;
}
检查用户的用户名和密码是否匹配数据库中的记录,返回登录结果。
3.1.6. 创建监听套接字并绑定地址
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(8888),.sin_addr.s_addr = INADDR_ANY
};
bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(listen_fd, 5);
使用 socket
函数创建一个 TCP 监听套接字,绑定到指定的地址和端口,然后开始监听客户端连接。
3.1.7. 创建 epoll 实例并监听事件
int epoll_fd = epoll_create1(0);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
使用 epoll_create1
函数创建一个 epoll 实例,将监听套接字加入到 epoll 监控列表中,监听读事件。
3.1.8. 事件循环处理
while (1) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {if (events[i].data.fd == listen_fd) { // 新客户端连接int client_fd = accept(listen_fd, NULL, NULL);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);} else { // 处理客户端数据int client_fd = events[i].data.fd;ProtocolPack pack;if (recv(client_fd, &pack, sizeof(ProtocolPack), 0) <= 0) {epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);close(client_fd);continue;}char username[32] = {0}, password[32] = {0};sscanf(pack.data, "%31s %31s", username, password);ProtocolPack resp_pack = {0};if (pack.op == 1) { // 处理注册请求int result = handle_register(db, username, password);if (result == 0) strcpy(resp_pack.data, "REGISTER_SUCCESS");else if (result == 1) strcpy(resp_pack.data, "USER_EXISTS");else strcpy(resp_pack.data, "REGISTER_FAIL");} else if (pack.op == 2) { // 处理登录请求int result = handle_login(db, username, password);if (result == 0) strcpy(resp_pack.data, "LOGIN_SUCCESS");else strcpy(resp_pack.data, "LOGIN_FAIL");}resp_pack.op = pack.op;resp_pack.data_len = htons(strlen(resp_pack.data));send(client_fd, &resp_pack, sizeof(ProtocolPack), 0);}}
}
使用 epoll_wait
函数等待事件发生,处理新客户端连接和客户端数据。根据客户端发送的操作码调用相应的处理函数,并将结果封装在协议包中返回给客户端。
3.1.9. 关闭监听套接字和数据库连接
close(listen_fd);
sqlite3_close(db);
3.2.编译运行步骤
- 编译服务器:
gcc server.c -o server -lsqlite3
- 编译客户端:
gcc client.c -o client
- 运行服务器:
./server
- 新开终端运行客户端:
./client
3.3. 功能实现结果展示
3.4. 源码
3.4.1. 服务器端代码(server.c
)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sqlite3.h>#define MAX_EVENTS 100
#define BUF_SIZE 1024// 定义通信协议包结构体
typedef struct {uint8_t op; // 操作码,1表示注册,2表示登录uint16_t data_len; // 数据长度(网络字节序)char data[BUF_SIZE];// 存储用户名、密码等数据
} ProtocolPack;// 初始化数据库
sqlite3* init_db() {sqlite3* db;// 打开/创建数据库文件 "user.db"int rc = sqlite3_open("user.db", &db);if (rc) {fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));exit(1);}// 创建用户表(若不存在)const char* sql = "CREATE TABLE IF NOT EXISTS users (""id INTEGER PRIMARY KEY AUTOINCREMENT,""username TEXT UNIQUE NOT NULL,""password TEXT NOT NULL)";char* errmsg;// 执行建表SQLrc = sqlite3_exec(db, sql, 0, 0, &errmsg);if (rc != SQLITE_OK) {fprintf(stderr, "SQL error: %s\n", errmsg);sqlite3_free(errmsg);}return db;
}// 处理注册逻辑
int handle_register(sqlite3* db, const char* username, const char* password) {const char* sql = "INSERT INTO users (username, password) VALUES (?, ?)";sqlite3_stmt* stmt;// 准备SQL语句int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);if (rc != SQLITE_OK) return -1;// 绑定用户名和密码参数sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);// 执行插入操作rc = sqlite3_step(stmt);if (rc == SQLITE_DONE) {sqlite3_finalize(stmt);return 0; // 注册成功}if (rc == SQLITE_CONSTRAINT_UNIQUE) {sqlite3_finalize(stmt);return 1; // 用户名已存在}sqlite3_finalize(stmt);return -1;
}// 处理登录逻辑
int handle_login(sqlite3* db, const char* username, const char* password) {const char* sql = "SELECT COUNT(*) FROM users WHERE username=? AND password=?";sqlite3_stmt* stmt;// 准备SQL语句int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);if (rc != SQLITE_OK) return -1;// 绑定用户名和密码参数sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);// 执行查询操作rc = sqlite3_step(stmt);if (rc == SQLITE_ROW) {int count = sqlite3_column_int(stmt, 0);sqlite3_finalize(stmt);return count == 1 ? 0 : -1; // 登录成功返回0,否则失败}sqlite3_finalize(stmt);return -1;
}int main() {sqlite3* db = init_db(); // 初始化数据库// 创建监听套接字int listen_fd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(8888),.sin_addr.s_addr = INADDR_ANY};// 绑定地址bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));// 开始监听listen(listen_fd, 5);// 创建epoll实例int epoll_fd = epoll_create1(0);struct epoll_event ev, events[MAX_EVENTS];ev.events = EPOLLIN;ev.data.fd = listen_fd;// 将监听套接字加入epoll监控epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);while (1) {// 等待事件发生int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {if (events[i].data.fd == listen_fd) { // 新客户端连接int client_fd = accept(listen_fd, NULL, NULL);ev.events = EPOLLIN;ev.data.fd = client_fd;// 将客户端套接字加入epoll监控epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);} else { // 处理客户端数据int client_fd = events[i].data.fd;ProtocolPack pack;// 接收协议包if (recv(client_fd, &pack, sizeof(ProtocolPack), 0) <= 0) {epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);close(client_fd);continue;}char username[32] = {0}, password[32] = {0};// 解析数据内容sscanf(pack.data, "%31s %31s", username, password);ProtocolPack resp_pack = {0};if (pack.op == 1) { // 处理注册请求int result = handle_register(db, username, password);if (result == 0) strcpy(resp_pack.data, "REGISTER_SUCCESS");else if (result == 1) strcpy(resp_pack.data, "USER_EXISTS");else strcpy(resp_pack.data, "REGISTER_FAIL");} else if (pack.op == 2) { // 处理登录请求int result = handle_login(db, username, password);if (result == 0) strcpy(resp_pack.data, "LOGIN_SUCCESS");else strcpy(resp_pack.data, "LOGIN_FAIL");}resp_pack.op = pack.op;resp_pack.data_len = htons(strlen(resp_pack.data));// 发送响应协议包send(client_fd, &resp_pack, sizeof(ProtocolPack), 0);}}}close(listen_fd);sqlite3_close(db);return 0;
}
3.4.2. 客户端代码(client.c
)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define BUF_SIZE 1024// 定义通信协议包结构体
typedef struct {uint8_t op; // 操作码,1表示注册,2表示登录uint16_t data_len; // 数据长度(网络字节序)char data[BUF_SIZE];// 存储用户名、密码等数据
} ProtocolPack;int main() {// 创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(8888)};// 设置服务器IPinet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);// 连接服务器connect(sock, (struct sockaddr*)&addr, sizeof(addr));ProtocolPack pack = {0};int choice;printf("1. 注册\n2. 登录\n选择: ");scanf("%d", &choice);char username[32], password[32];printf("用户名: ");scanf("%31s", username);printf("密码: ");scanf("%31s", password);pack.op = (uint8_t)choice;// 构建数据内容sprintf(pack.data, "%s %s", username, password);pack.data_len = htons(strlen(pack.data));// 发送协议包send(sock, &pack, sizeof(ProtocolPack), 0);ProtocolPack resp_pack;// 接收响应协议包recv(sock, &resp_pack, sizeof(ProtocolPack), 0);printf("结果: %s\n", resp_pack.data);close(sock);return 0;
}