# myCloud
**Repository Path**: liu-siyuan888/my-cloud
## Basic Information
- **Project Name**: myCloud
- **Description**: 私有协议文件管理服务器
- **Primary Language**: C
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-08-26
- **Last Updated**: 2024-03-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 私有协议文件服务器
## 初代版本
**服务端:**
+ 与多个客户端建立 **TCP 长连接**
+ 使用**线程池**来完成客户端的请求,主线程只负责监听,子线程负责 I/O
+ 使用自定义的**小火车协议**传输文件(使用文件映射)、具有差错检验和断点续传功能
+ 无注册功能,使用 linux **系统用户和密码**登录,验证用户名和密码
+ 在 **系统日志** 中记录客户端的连接信息、操作记录、操作时间
**客户端:**
+ 目录命令:cd、ls、pwd、mkdir、rmdir
+ 文件命令:puts 将本地文件上传至服务器、 gets 文件名 下载服务器文件到本地、remove 删除服务器上的文件
+ 非法命令会提示
### 1 基础框架
#### 1.1 线程池
```c
/* 所有共享资源 */
typedef struct threadPool_s {
int workerNum; // 线程数量
pthread_t *workerArr; // 线程信息
taskQueue_t taskQueue; // 任务队列
int epollFd; // epollFd
connetionBuffer_t fdBuf[128]; // 服务端缓存
pthread_mutex_t mutex; // 锁
pthread_cond_t cond; // 条件变量
} threadPool_t;
```
**注意!在主线程唤醒子线程前要先关闭 netfd 的监听,并在子线程工作结束后重新监听**
```c
pthread_mutex_lock(&threadPool.mutex);
enQueue(&threadPool.taskQueue, eventArr[i].data.fd);
/* 注意! 必须先移除监听再唤醒线程, 然后在线程做完任务后添加监听 */
/* 否则多个线程会接到同一个任务 */
epollDelete(epollFd, eventArr[i].data.fd);
pthread_cond_signal(&threadPool.cond);
pthread_mutex_unlock(&threadPool.mutex);
```
#### 1.2 小火车协议
+ 先发文件名,再发文件长度,最后接收文件
+ 解决粘包问题
```c
int recvFileFromClient(int fd, threadPool_t *pThreadPool) {
/* 文件名 */
train_t train;
recvn(fd,&train.length,sizeof(train.length));
recvn(fd,train.data,train.length);
char filename[4096] = {0};
changePath(&pThreadPool->fdBuf[0].netPath, pThreadPool->fdBuf[0].usr, train.data, filename);
/* 文件长度 */
int filefd = open(filename,O_RDWR|O_CREAT,0666);
off_t filesize;
recvn(fd,&train.length,sizeof(train.length));
recvn(fd,train.data,train.length);
memcpy(&filesize,train.data,sizeof(filesize));
ftruncate(filefd,filesize);
/* 文件 */
char *p = (char *)mmap(NULL,filesize,PROT_READ|PROT_WRITE,MAP_SHARED,filefd,0);
recvn(fd,p,filesize);
munmap(p,filesize);
close(filefd);
return 0;
}
```
### 2 登录及密码验证
+ 注意,从系统中获取用户密码信息需要更高的权限,客户端和服务端都要加 sudo


+ 盐值提取
```c
void get_salt(char *salt,char *passwd) {
int i, j;
for (i = 0, j = 0; passwd[i] && j != 3; i++) {
if (passwd[i] == '$') {
++j;
}
}
strncpy(salt, passwd, i - 1);
}
```
+ 从系统中获取用户信息并验证密码是否正确
```c
char userName[128] = {0};
ssize_t recvRet = recvMessageFromClient(netFd, userName, sizeof(userName));
if (!recvRet) {
close(netFd);
break;
}
printf("userName = %s\n", userName);
struct spwd* sp = getspnam(userName);
printf("systemPasswd = %s\n", sp->sp_pwdp);
char salt[512] = {0};
if (!sp) {
sendMessageToClient(netFd, "0", 1);
printf("用户不存在\n");
continue;
} else {
get_salt(salt, sp->sp_pwdp);
sendMessageToClient(netFd, salt, strlen(salt));
char saltPasswd[1024] = {0};
size_t recvRet =
recvMessageFromClient(netFd, saltPasswd, sizeof(saltPasswd));
printf("clientPasswd = %s\n", saltPasswd);
if (!recvRet) {
close(netFd);
break;
}
if (strcmp(sp->sp_pwdp, saltPasswd) == 0) {
send(netFd, "1", 1, 0);
printf("密码正确\n\n");
epollAdd(epollFd, netFd);
break;
} else {
send(netFd, "0", 1, 0);
printf("密码错误\n\n");
}
}
```
### 3 cd 命令

+ 使用栈结构保存当前路径

+ 思路:先取出当前路径,然后移动指针修改当前路径
```c
/* 当前路径栈 */
typedef struct pathStack_s{
char path[128][128];
int top;
}pathStack_t;
/* 一次连接的缓存 */
typedef struct connetionBuffer_s{
pathStack_t netPath; // 当前路径栈
char usr[128]; // 用户根目录路径
}connetionBuffer_t;
```
### 4 ls 命令

+ 方法:遍历目录流
```c
int listSearch(const char* realPath,char* lsReturn) {
DIR* pdir = opendir(realPath);
ERROR_CHECK(pdir,NULL,"opendir");
struct dirent* pdirent;
while((pdirent=readdir(pdir))){
if (strcmp(pdirent->d_name, ".") && strcmp(pdirent->d_name, "..")) {
strcat(lsReturn,pdirent->d_name);
if(pdirent->d_type == DT_DIR) {
strcat(lsReturn,"/");
}
strcat(lsReturn," ");
}
}
strcat(lsReturn," ");
return 0;
}
```
### 5 pwd 命令
+ 每次 cd 会修改缓存中的当前路径,直接取出即可

```c
if (strcmp(cmd, "pwd") == 0) {
char buffer[128] = {0};
netdeskpwd(&pThreadPool->fdBuf[0].netPath, buffer);
sendMessageToClient(netFd, buffer, strlen(buffer));
}
```
### 6 mkdir 命令 和 rmdir 命令

+ mkdir:直接 mkdir 系统调用
+ rmdir:直接 rmdir 系统调用
```c
if (strcmp(cmd, "mkdir") == 0) {
memset(buf, 0, sizeof(buf));
changePath(&pThreadPool->fdBuf[0].netPath, pThreadPool->fdBuf[0].usr, path1,
buf);
int ret = mkdir(buf, 0777);
if (ret == -1) {
char buffer[1024] = {0};
sprintf(buffer, "mkdir: cannot create directory '%s'", path1);
sendMessageToClient(netFd, buffer, strlen(buffer));
} else {
sendMessageToClient(netFd, "mkdir finished", 14);
}
} else if (strcmp(cmd, "rmdir") == 0) {
memset(buf, 0, sizeof(buf));
changePath(&pThreadPool->fdBuf[0].netPath, pThreadPool->fdBuf[0].usr, path1,
buf);
int ret = rmdir(buf);
ERROR_CHECK(ret, -1, "remove");
if (ret == -1) {
char buffer[1024] = {0};
sprintf(buffer, "rmdir: cannot remove directory '%s'", path1);
sendMessageToClient(netFd, buffer, strlen(buffer));
} else {
sendMessageToClient(netFd, "rmdir finished", 14);
}
}
```
### 7 remove 命令

+ 系统调用 unlink
```c
if (strcmp(cmd, "remove") == 0) {
memset(buf, 0, sizeof(buf));
changePath(&pThreadPool->fdBuf[0].netPath, pThreadPool->fdBuf[0].usr, path1,
buf);
int ret = unlink(buf);
if (ret == -1) {
char buffer[1024] = {0};
sprintf(buffer, "remove: cannot remove '%s': No such file or directory",
path1);
sendMessageToClient(netFd, buffer, strlen(buffer));
} else {
sendMessageToClient(netFd, "remove filished", 15);
}
}
```
### 8 puts 命令
+ 基于小火车协议
+ 需要传递 md5 值
+ 先发文件名,再发 md5 值,然后发文件长度,最后发文件
```c
int sendFileToServer(int fd, char *localPath, char *virtualPath) {
/* 发文件名 */
train_t train;
train.length = strlen(virtualPath);
memcpy(train.data,virtualPath,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发 md5 值 */
char md5[128];
changeToMd5(localPath,md5);
train.length=strlen(md5);
memcpy(train.data,md5,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发文件长度 */
int filefd = open(localPath,O_RDWR);
ERROR_CHECK(filefd,-1,"filefd");
struct stat statbuf;
fstat(filefd,&statbuf);
printf("%d\n",(int)statbuf.st_size);
train.length = sizeof(statbuf.st_size);
memcpy(train.data,&statbuf.st_size,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发文件 */
sendfile(fd,filefd,NULL,statbuf.st_size);
close(filefd);
return 0;
}
```
### 9 gets 命令 与 断点重传功能
+ 服务端先给客户端发文件名,客户端收到后返回一个偏移量
+ 服务端收到偏移量后,就从偏移量开始传输
+ 传输使用 mmap 提高效率(sendfile 系统调用)
```c
int sendFileToClient(int fd, char *localPath, char *clientPath) {
/* 发文件名 */
train_t train;
train.length = strlen(clientPath);
memcpy(train.data,clientPath,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 收偏移量 */
off_t offset;
recv(fd,&offset,sizeof(offset),0);
/* 发文件长度 */
int filefd = open(localPath,O_RDWR);
ERROR_CHECK(filefd,-1,"filefd");
struct stat statbuf;
fstat(filefd,&statbuf);
train.length = sizeof(statbuf.st_size);
memcpy(train.data,&statbuf.st_size,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 从偏移量开始发文件 */
sendfile(fd,filefd,&offset,statbuf.st_size-offset);
close(filefd);
return 0;
}
```
### 10 日志记录
+ 本组直接将登录记录和命令记录放入系统日志文件中,系统调用 syslog
+ 注意:必须加 sudo 才能写入系统日志
+ 路径 /var/log/syslog
```c
#define LOG_WRITE(msg) { \
int fd = open("/var/log/syslog", O_WRONLY | O_APPEND | O_CREAT, 0666); \
time_t now = time(NULL); \
time(&now); \
struct tm* localTime = localtime(&now); \
char message[4096] = {0}; \
sprintf(message, "[%d-%d-%d][%d:%d:%d]: %s\n", localTime->tm_year + 1900, localTime->tm_mon + 1, localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec, msg); \
write(fd, message, strlen(message)); \
close(fd); \
}
```
## 最终版本
**服务端:**
+ 初代版本的 **线程池、私有协议、断点续传、秒传** 功能
+ **长短命令分离**:使用线程池来完成客户端的请求,主线程做非 puts 和 gets 命令,子线程做 puts 和 gets 命令
+ 注册和登录:用户信息存入 mysql 中,以密文形式存储
+ 每次登陆后生成一个 **token** 发给客户端用于验证身份
+ **虚拟目录系统**:虚拟的目录树全部存在 mysql 数据库中
+ 文件存储:所有用户的所有文件放在一个真实的系统目录中,以 md5 值作为名字,同一份文件只存储一份
+ **超时踢人**:客户端如果 30 秒没有操作,断开 TCP 连接,使用环形队列实现
**客户端:**
+ 检查并发送 cd、ls、pwd、mkdir、rmdir、puts、gets、remove 命令给服务端
+ 长短命令分离:puts 和 gets 命令交给子线程,子线程与服务器建立新连接,验证 token 后才可以 I/O 文件
### 1 数据库表结构
+ 用户登录信息表
```sql
create table user (id int primary key auto_increment, username varchar(128) unique, salt varchar(128), passwd varchar(512), tomb int);
```


+ 目录和文件系统表
```c
create table tree (id int primary key auto_increment, name varchar(128), username varchar(128), path varchar(256), pre_id int, md5 varchar(64), size bigint, createtime varchar(128),tomb int, unique key idx_username_path (username, path));
```


+ 存储位置:以 MD5 值作为 真实文件名

### 2 注册与登录
+ 在 tcp 连接后就可以开始注册/登录,只实现了注册或者登录
+ 使用 getpass 函数实现输入密码不显示的功能
+ 用户在客户端输入账号和密码,用明文传给服务端
+ 需要随机生成盐值加密后再存储到数据库中
+ 如果是注册,服务端先查表,没有就插入一行数据
```sql
sprintf(sql,"select username from user where username = '%s';",tmpuser.userName);
sprintf(sql,"insert into user (username,salt,passwd,tomb) values ('%s','%s','%s',0);",tmpuser.userName,saltnum,saltresult);
```
+ 如果是登录,服务端直接找用户名,分为两种情况,用户名不存在和密码不正确
```c
sprintf(sql,"select salt, passwd, tomb from user where username = '%s';",tmpuser.userName);
```
+ 盐值生成
```c
int salt(char *saltnum){
srand(time(NULL));
printf("salt begin\n");
saltnum[0] = '$';
saltnum[1] = '6';
saltnum[2] = '$';
//随机生成中间16位
const char char16[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (int i = 0; i < 16; ++i) {
saltnum[3+i] = char16[rand()%(sizeof(char16)-1)];
}
saltnum[19] = '$';
return 0;
}
```
### 3 cd 命令
+ 服务端在缓存中存有当前路径,客户端发来一个相对路径/绝对路径(以 / 开头为区分)
+ 相对路径解决起来比较麻烦,要一个一个查表
```sql
sprintf(sql,"select path from tree where pre_id = %d and username = '%s' and tomb = 0;",row[0],username);
```

```c
if (strcmp(cmd, "cd") == 0) {
/* strcpy(pThreadPool->userBuffer[netFd], "liu"); */
int cdRet = changeDir(mysql, pThreadPool->userBuffer[netFd],
pThreadPool->pathBuffer[netFd], path1);
if (cdRet == -1) {
sendMessageToClient(netFd, "cd: directory does not exist", 28);
} else {
sendMessageToClient(netFd, pThreadPool->pathBuffer[netFd],
strlen(pThreadPool->pathBuffer[netFd]));
}
}
```
### 4 ls 命令

+ 先取出当前目录的 id, 然后再 pre_id 里找当前 id
```sql
sprintf(sql,"select id from tree where username = '%s' and path = '%s';\n",username,curr_path);
sprintf(sql,"select name from tree where pre_id = %d and username = '%s' and tomb = 0;",row[0],username);
```
```c
int listSearch(MYSQL* mysql,const char* username,const char* curr_path,char* list){
//sq保存query语句
char sq[1024]={0};
sprintf(sq,"select id from tree where username = '%s' and path = '%s';\n",username,curr_path);
int qret=mysql_query(mysql,sq);
QUERY_CHECK(qret,mysql);
MYSQL_RES *result=mysql_store_result(mysql);
MYSQL_ROW row=mysql_fetch_row(result);
//row[0]就是当前目录的id
fprintf(stdout,"select name,md5,size,createtime from tree where pre_id = %s and tomb = 0;\n",row[0]);
sprintf(sq,"select name,md5,size,createtime from tree where pre_id = %s and tomb = 0;",row[0]);
qret=mysql_query(mysql,sq);
QUERY_CHECK(qret,mysql);
result=mysql_store_result(mysql);
while((row=mysql_fetch_row(result))!=NULL){
strcat(list, row[3]);
strcat(list, " ");
strcat(list, row[2]);
strcat(list, "\t");
strcat(list, row[0]);
if (strcmp(row[1], "0") == 0) {
strcat(list, "/");
}
strcat(list,"\n");
}
return 0;
}
```
### 5 gets 命令与断点重传
+ 服务端先给客户端发文件名,客户端收到后返回一个偏移量
+ 服务端收到偏移量后,就从偏移量开始传输
+ 传输使用 mmap 提高效率(sendfile 系统调用)
```c
int sendFileToClient(int fd, char *localPath, char *clientPath) {
/* 发文件名 */
train_t train;
train.length = strlen(clientPath);
memcpy(train.data,clientPath,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 收偏移量 */
off_t offset;
recv(fd,&offset,sizeof(offset),0);
/* 发文件长度 */
int filefd = open(localPath,O_RDWR);
ERROR_CHECK(filefd,-1,"filefd");
struct stat statbuf;
fstat(filefd,&statbuf);
train.length = sizeof(statbuf.st_size);
memcpy(train.data,&statbuf.st_size,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 从偏移量开始发文件 */
sendfile(fd,filefd,&offset,statbuf.st_size-offset);
close(filefd);
return 0;
}
```
### 6 mkdir 命令

+ 小组实现的 mkdir 命令可以在相对路径下创建目录,但相对路径中不能有不存在的目录
+ 相对路径创建文件的功能是基于 cd 实现的
+ 先找是否有 tomb = 1 的目录
```sql
sprintf(sql,"select * from tree where path = '%s' and username = '%s' and tomb = 1;",path, username);
```
+ 然后找目录是否存在:复制一份缓存中的当前路径,把路径参数传入修改的 cd 命令中(可以转换路径到任意目录或文件,不存在就报错)
+ 接着判断要创建的目录前的一串目录是否有不存在

```c
int cutTail(char *path, char *left, char *right) {
char tmp[1024] = {0};
memcpy(tmp, path, strlen(path));
memcpy(left, path, strlen(path));
char *p = strtok(tmp, "/");
while (1) {
char *tmp = strtok(NULL, "/");
if (tmp == NULL) {
memcpy(right, p, strlen(p) + 1);
if (strlen(path) == strlen(right)) {
strcpy(left, ".");
break;
}
left[strlen(path) - strlen(right) - 1] = '\0';
break;
}
p = tmp;
}
return 0;
}
```
+ 插入表中
```sql
sprintf(sql,"insert into tree (name,username,path,pre_id,md5,tomb,size,createtime) values('%s' , '%s' , '%s' ,%d, '0' , 0, 0, '%s') ;",right,username,path,id,t);
```
### 7 puts 命令与秒传功能
+ 基于小火车协议
+ 需要传递 md5 值,如果 md5 值相同即可妙传
+ 先发文件名,再发 md5 值,然后发文件长度,最后发文件
+ 同样支持相对路径
```sql
sprintf(sql,"insert into tree (name,username,path, pre_id,md5,size,createtime,tomb) values('%s','%s','%s',%d ,'%s', %d,'%s', %d);",filename,user,filepath,parent,filemd5,filesize,t, 0);
```

```c
int sendFileToServer(int fd, char *localPath, char *virtualPath) {
/* 发文件名 */
train_t train;
train.length = strlen(virtualPath);
memcpy(train.data,virtualPath,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发 md5 值 */
char md5[128];
changeToMd5(localPath,md5);
train.length=strlen(md5);
memcpy(train.data,md5,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发文件长度 */
int filefd = open(localPath,O_RDWR);
ERROR_CHECK(filefd,-1,"filefd");
struct stat statbuf;
fstat(filefd,&statbuf);
printf("%d\n",(int)statbuf.st_size);
train.length = sizeof(statbuf.st_size);
memcpy(train.data,&statbuf.st_size,train.length);
send(fd,&train,sizeof(train.length)+train.length,MSG_NOSIGNAL);
/* 发文件 */
sendfile(fd,filefd,NULL,statbuf.st_size);
close(filefd);
return 0;
}
```
### 8 rmdir 命令
+ 同样支持相对路径,方法类似 mkdir
```sql
sprintf(sql,"update tree set tomb=1 where path = '%s' and username = '%s';",path, username);
```
### 9 remove 命令
+ 同样支持相对路径,方法类似 mkdir
```c
sprintf(sql,"update tree set tomb=1 where path = '%s' and username = '%s';",path, username);
```
### 10 pwd 命令
+ 一直存在缓存中,直接取出
```c
if (strcmp(cmd, "pwd") == 0) {
printf("%s\n", pThreadPool->pathBuffer[netFd]);
sendMessageToClient(netFd, pThreadPool->pathBuffer[netFd],
strlen(pThreadPool->pathBuffer[netFd]));
}
```
### 11 长短命令分离
#### 11.1 服务端
```c
while (1) {
int readyCount = epoll_wait(epollFd, eventArr, 10, -1);
for (int i = 0; i < readyCount; ++i) {
/* 事件一: 有新连接 */
if (eventArr[i].data.fd == socketFd) {
struct sockaddr_in clientAddr;
int netFd = acceptConnection(socketFd, &clientAddr);
epollAdd(epollFd, netFd); // 添加监听
/* puts 和 gets 的新连接 */
if (子线程的连接) {
pthread_mutex_lock(&threadPool.mutex);
enQueue(&threadPool.taskQueue, netFd); // 加入任务队列
pthread_cond_signal(&threadPool.cond); // 唤醒子线程处理长命令
pthread_mutex_unlock(&threadPool.mutex);
}
/* 事件二: 处理短命令 */
} else {
/* 处理短命令 */
}
}
}
```
#### 11.2 客户端
```c
while (1) {
int readyCount = epoll_wait(epollFd, eventArr, 2, -1);
ERROR_CHECK(readyCount, -1, "epoll_wait");
for (int i = 0; i < readyCount; ++i) {
/* 事件一: 标准输入就绪 */
if (eventArr[i].data.fd == STDIN_FILENO) {
memset(buf, 0, sizeof(buf));
ssize_t stdinLength = recvStdin(buf, sizeof(buf));
/* 解析命令 */
int ret = cutCmd(buf, cmd, path1, path2);
if (!checkArguments(cmd, path1, path2, ret)) {
write(STDOUT_FILENO, s, strlen(s));
continue;
}
/* puts 命令 和 gets 命令交给子线程处理 */
if (strcmp(cmd, "puts") == 0) {
source_t source;
initSource(&source, atoi(fd), buf, name, cmd, path1, path2);
pthread_t thread;
pthread_create(&thread, NULL, threadFunc, &source);
} else if (strcmp(cmd, "gets") == 0) {
source_t source;
initSource(&source, atoi(fd), buf, name, cmd, path1, path2);
pthread_t thread;
pthread_create(&thread, NULL, threadFunc, &source);
} else {
/* 其他命令直接发给服务器主线程处理 */
sendMessageToServer(socketFd, buf, stdinLength);
}
/* 事件二: tcp 读端就绪 */
} else {
ssize_t recvRet = recvMessageFromServer(socketFd, buf, sizeof(buf));
printf("%s\n", buf);
write(STDOUT_FILENO, s, strlen(s));
}
}
}
```
+ 由于小组设计了缓存系统(用来实现所有命令支持相对路径),所以子线程也要知道当前用户的缓存(知道短命令的 netfd)
+ 因此设计了新的命令 getfd,客户端可以获取短命令的 netfd,在创建子线程时要传入这个 netfd
### 12 token
+ 服务端生成 token:以客户端传来的用户名作为 key 值,生成 token,参考了 github 中的文档
```c
void createToken(char *username,char *token) {
char* jwt;
size_t jwt_length;
struct l8w8jwt_encoding_params params;
l8w8jwt_encoding_params_init(¶ms);
params.alg = L8W8JWT_ALG_HS512;
params.sub = "myCloud";
params.iss = "team07";
params.aud = "classmate";
params.iat = 0;
params.exp = 0x7fffffff; /* Set to expire after 10 minutes (600 seconds). */
params.secret_key = (unsigned char*)username;
params.secret_key_length = strlen(params.secret_key);
params.out = &jwt;
params.out_length = &jwt_length;
int r = l8w8jwt_encode(¶ms);
if(r==L8W8JWT_SUCCESS){
strcpy(token,jwt);
}
printf("\n l8w8jwt example HS512 token: %s \n", r == L8W8JWT_SUCCESS ? jwt : " (encoding failure) ");
/* Always free the output jwt string! */
l8w8jwt_free(jwt);
}
```
+ token 的携带:服务端以小火车的形式发送给客户端,先发用户名,再发 token
```c
int sendTokenToClient(int fd, char *username, char *token) {
train_t train;
memset(&train, 0, sizeof(train));
/* username */
train.length = strlen(username);
memcpy(train.data,username, train.length);
ssize_t sendRet = send(fd, &train, sizeof(train.length) +train.length, 0);
ERROR_CHECK(sendRet, -1, "send username");
/* token */
train.length=strlen(token);
memcpy(train.data,token,train.length);
sendRet=send(fd, &train, sizeof(train.length) +train.length,0);
ERROR_CHECK(sendRet, -1, "send token");
return 0;
}
```
+ token 的验证:客户端子线程执行 puts 和 gets 命令时,携带 token 相关信息,传给服务端认证
+ 认证成功返回 1, 认证失败返回 0,参考了 github 文档
```c
int verify(char *username,char *token){
struct l8w8jwt_decoding_params params;
l8w8jwt_decoding_params_init(¶ms);
params.alg = L8W8JWT_ALG_HS512;
params.jwt = (char*)token;
params.jwt_length = strlen(token);
params.verification_key = (unsigned char*)username;
params.verification_key_length = strlen(username);
params.validate_iss = "team07";
params.validate_sub = "myCloud";
params.validate_exp = 0;
params.exp_tolerance_seconds = 60;
params.validate_iat = 0;
params.iat_tolerance_seconds = 60;
enum l8w8jwt_validation_result validation_result;
int decode_result = l8w8jwt_decode(¶ms, &validation_result, NULL, NULL);
if (decode_result == L8W8JWT_SUCCESS && validation_result == L8W8JWT_VALID) {
printf("\n Example HS512 token validation successful! \n");
return 1;
} else {
printf("\n Example HS512 token validation failed! \n");
return 0;
}
}
```
### 13 定时器设计
+ 使用 epoll 的超时就绪实现定时器,就绪时间设为 1000ms
+ 每次 epoll_wait 返回 ,就让 timer = time(NULL),就会跟随时间自增
### 14 环形队列数据结构
+ 循环队列:定长数组实现,一共 30 个槽,每次 timer 前进就前进一个槽,有一个 currentSlot 指向当前的槽
```c
#define TIME_WHEEL_SIZE 30
//设置一个长度为30的循环队列
typedef struct time_wheel_s{
//30个槽位,每个槽位一个集合
HashSet* set[TIME_WHEEL_SIZE];
//当前的槽位
int curr_slot;
//一个哈希表,用于记录fd在哪个槽位的集合里
HashTable* table;
}time_wheel_t;
```
+ map 设计:一个哈希表,键是文件描述符,值是槽位
```c
typedef struct{
Entry* entries[TABLE_SIZE];
}HashTable;
```
+ set 设计:每个槽都有一个集合,用哈希表实现,存放槽内的文件描述符
```c
typedef struct Entry {
int key;
int value;
struct Entry* next;
} Entry;
typedef struct {
Entry* entries[TABLE_SIZE];
} HashSet;
```