您的位置:首页 > 理论基础 > 计算机网络

Linux网络编程——SQLite数据库

2017-08-27 22:27 190 查看
学习笔记,小白可以相互学习,大佬看到能告诉咱理解不对的地方就好了。

数据库基本概念:

1.数据:能够输入计算机并能被计算机程序识别和处理的信息集合

2.数据库:数据库是在数据库管理和控制之下,存放在存储介质上的数据集合

文件管理阶段:

优点:1.数据可长期保存 2.能存储大量数据

缺点:1.数据冗余度大,数据一致性和完整性难以维持 2.数据与程序缺乏高度独立性

数据库系统阶段:

1.数据组织结构化  2.数据冗余度比较小,易扩充
3.具有较高的数据与程序之间的独立性   4.统一的数据控制

SQLite有以下特性:

1.零配置----无需安装和管理配置

2.存储在单一磁盘文件中的一个完整的数据库

3.数据库文件可以再不同字节顺序的机器间自由共享

4.支持数据库大小至2TB

5.足够小,全部源码大致3万行c代码,250kb

6.比目前流行的大多数数据库对数据的操作要快

SQLite常用命令介绍

显示所有命令:sqlite->help

  退出sqlite3:sqlite->quit

  显示当前打开的数据库文件:sqlite->database

  显示数据库中所有的表名:sqlite->.table

  查看表的结构:sqlite->.schema <table_name>

以下为SQL命令以;结束

1.创建新表

sqlite->creat table <table_name> (f1 type1,f2 type2,...);

例如:creat table stu(nu Integer,name char,score float);//char 表示字符串,也可用string ,txt

//creat table stu(nu Integer primary key,name char,score float);加了primary key表示nu作为主键,不可重复,不能插入相同主键的数据

2.插入(向表中添加新记录)

insert into <table_name> values(value1,value2,...);

例如:insert into stu values(1,"xiaoli",99);

3.查询

selete * from <table_name>;//查询表中所有记录,例如selete * from stu;

 按照指定条件查询:

selete * from <table_name> where <expression>;   

//例如:selete
number  * from stu where score>60 and number=1;


//selete number,name from stu where score<60;

4.删除

delete from <table_name>  where <expression>;//删除指定记录

drop table <table_name>;//删除表

5.修改(更新表中内容

update <table_name> set <f1=value1> <f2=value2>...where <expression>;

例如:update stu set name="xiaozhang" ,score=100 where number = 3;

6.在表中添加字段(添加一列)

alter table <table_name> add column <filed> <type> default...;//添加

alter table <table_name> drop column<filed>;//删除一列

例如:alter table stu add column address char;//address是添加的名称,char是添加的address的类型

7.在表中删除字段(删除一列)

sqlite中不允许删除字段,通过下面的步骤一样可以达到同样的目的

creat table stu1 as select number,name,score from stu;//新建一张表stu1同原来一样

drop table stu;//删除原表stu

alter table stu1 rename to stu ;//将表stu1重命名为stu

[b]SQLite编程接口(编译时gcc sqlite.c -lsqlite3)[/b]

int sqlite3_open(char *filename,sqlite 3 **db);

功能:打开sqlite数据库

filename:数据库文件名(UTF-8编码格式)

db:指向sqlite句柄的指针

返回值:成功:0
失败:返回错误码(非0值)//ret !=SQLITE_OK判断否成功返回




[b]int sqlite2_close(sqlite3 *db);[/b]

[b]功能:关闭sqlite数据库[/b]

[b]返回值:成功返回0,失败返回错误码[/b]



[b]const char *sqlite3_errmsg(sqlite *db)[/b]

[b]功能:存放错误信息[/b]

[b]返回值:返回错误信息[/b]



[b]int sqlite3_exec(sqlite3 *db,const char *sql,sqlite3_callback callback,void *,char **errmsg);[/b]

[b]功能:执行SQL操作[/b]

[b]db:数据库句柄[/b]

[b]sql:SQL语句[/b]

[b]callback:回调函数,没有则为NULL[/b]

[b]errmsg:错误信息指针的地址[/b]

[b]返回值:成功返回0,石板返回错误码[/b]



[b]typedef int(*sqlite3_callback)(void *para,int f_num,char **f_value,char **f_name);[/b]

[b]功能:找到每一条记录自动执行一次回调函数[/b]

[b]f_num:记录中包含的字段数目[/b]

[b]f_value:包含每个字段值的指针数组[/b]

[b]f_name:包含每个字段名称的指针数组[/b]

[b]返回值:成功0,失败-1[/b]



[b]不使用回调函数执行SQL语句[/b]

[b]int sqlite3_get_table(sqlite3 *db,const char *sql,char ***resultp,int *nrow,int ncolumn,char **errmsg);[/b]

[b]功能:执行SQL操作[/b]

[b]db:数据库句柄
//打开的数据库
[/b]

[b]sql:SQL语句
//要评估的sql
[/b]

[b]resultp:用来指向sql执行结果的指针
//查询结果
[/b]

[b]nrow:满足条件的记录的数目
//写在这里的结果行号
[/b]

[b]ncolumn:每条记录包含的字段数目
//结果列数
[/b]

[b]errmsg:错误信息指针的地址[/b]

[b]返回值:成功返回0,失败返回错误码[/b]

/********sqlite.c*********************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>

/*
* 何时调用callback函数:表中要要查询的数据的时候;
* 查询到有几条,函数调用几次;
*/

int callback(void *para, int f_num, char **f_value, char **f_name)
{
int i;
for (i = 0; i < f_num; i++)
printf("%s : %s\n", f_name[i], f_value[i]);
return 0;
}

int main()
{
int ret;
sqlite3 *db;
char *errmsg;
char sql[128];

/* 打开数据库文件 */
ret = sqlite3_open("my.db", &db);
if (ret != SQLITE_OK) {
fprintf(stderr, "open : %s\n", sqlite3_errmsg(db));
return -1;
}
#if 0
/* 创建表 */

sprintf(sql, "create table stu(nu Integer primary key, name char, score float)");
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "create fail : %s\n", errmsg);
return -1;
}

/* 插入数据 */
int nu = 3;
char name[20] = "xiaoli";
float score = 89;

sprintf(sql, "insert into stu values(%d, '%s', %f)", nu, name, score);
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "create fail : %s\n", errmsg);
return -1;
}

sleep(10);

/* 修改数据 */
nu = 2;
strcpy(name, "xiaowang");
sprintf(sql, "update stu set name='%s' where nu='%d'", name, nu);
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "create fail : %s\n", errmsg);
return -1;
}

/* 查询数据 */
char test[20];
sprintf(sql, "select * from stu");
ret = sqlite3_exec(db, sql, callback, test, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "create fail : %s\n", errmsg);
return -1;
}

#endif
char **result;
int row;
int column;
sprintf(sql, "select * from stu");
ret = sqlite3_get_table(db, sql, &result, &row, &column, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "select fail : %s\n", errmsg);
return -1;
}
printf("row = %d, column = %d\n", row, column);

int i;
int j;
result += column;
#if 1
for (i = 0; i < row; i++) {
for (j = 0; j< column; j++) {
printf("%s ", *result++);
}
printf("\n");
}
#endif

sqlite3_close(db);
}


英英词典代码:

/*******server.c*********************************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<pthread.h>
#include<sqlite3.h>
#include<time.h>
#include<sys/stat.h>
#include<fcntl.h>
int server_start(int listenfd); //服务器初始化函数
int server_accept(int listenfd); //服务器连接客户端函数
void *thread_func(void *connfd); //线程的处理函数
int server_register(int connfd); //服务器注册函数
int server_login(int connfd); //服务器登陆函数
int server_search(int connfd,int l); //服务器搜索单词函数
int server_history(int connfd,int l); //服务器历史纪录传给客户端函数
int server_history_write(char *word); //服务器历史记录写入文件函数

int main()
{
int ret;
int listenfd;
listenfd = server_start(listenfd);
if(-1 == ret )
{
printf("server_start failed\n");
return -1;
}

while(1)
{
//建立连接
int connfd;
connfd = server_accept(listenfd);
if(-1 == connfd)
{
printf("server_accept failed\n");
return -1;
}

//采用多线程的方式
pthread_t thread;
ret = pthread_create(&thread,NULL,thread_func,(void *)&connfd);
if (0 != ret)
{
perror("pthread_creat");
close(connfd);
continue;
}
printf("pthread_creat success! connfd\n");

//给线程收尸体,用pthread_join会阻塞
if(0 != pthread_detach(thread))
{
perror("pthread_detach");
close(connfd);
continue;
}
}
return 0;
}

/********************************************************************/
/*******************服务器初始化**************************************/
int server_start(int listenfd)
{
/*1.socket*/
int ret;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == listenfd)
{
perror("server->socket");
return -1;
}
printf("created listenfd = %d success\n",listenfd);

/*2.bind*/
struct sockaddr_in addr;
memset(&addr,0,sizeof(struct sockaddr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listenfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(-1 == ret)
{
perror("server->bind");
return -1;
}
printf("port: %d \n",htons(addr.sin_port));

/*3.listen*/
ret = listen(listenfd,0);
if(-1 == ret)
{
perror("server->listen");
return -1;
}
printf("listen success\n");

return listenfd;
}

/********************************************************************/
/******************服务器连接************************************/
int server_accept(int listenfd)
{
/*4.accept*/
socklen_t addrlen;
struct sockaddr_in cltaddr;
addrlen = sizeof(socklen_t);
int connfd;
connfd = accept(listenfd,(struct sockaddr *)&cltaddr,&addrlen);
if(-1 == connfd)
{
perror("server->accept");
return -1;
}
printf("connfd = %d\n",connfd);
return connfd;
}

/********************************************************************/
/************线程的处理函数*****************************************/
void *thread_func(void *arg)
{
int connfd = *(int *)arg;
int ret;
int l;
int chause;
char buf[256];
char word[16];
while(1)
{
ret = read(connfd,&chause,sizeof(chause));//接收chause
//printf("chause = %d\n",chause);
if(-1 == ret)
{
perror("server->read");
close(connfd);
pthread_exit(0);
}

switch(chause)
{
case 1:
server_register(connfd);
break;
case 2:
l = server_login(connfd);
break;
case 3:
server_search(connfd,l);
break;
case 4:
server_history(connfd,l);
break;
case 5:
printf("this client quit\n");
close(connfd);
pthread_exit(0);
default:
continue;
}
}
}

/********************************************************************/
/********注册***********************************/
int server_register(int connfd)
{
char buf[256];
char buf2[256];
int ret;
char name[16];
char mima[16];
char sql[256];
sqlite3 *db;
char *errmsg;
int k = 1;//用来保存注册是否成功的状态
memset(sql,0,sizeof(sql));
memset(buf,0,sizeof(buf));
memset(buf2,0,sizeof(buf2));

ret = read(connfd,buf,sizeof(buf));//name
strncpy(name,buf,16);
if(-1 == ret)
{
perror("server_register->read.name");
return -1;
}

ret = read(connfd,buf2,sizeof(buf2));//mima
strncpy(mima,buf2,16);
if(-1 == ret)
{
perror("server_register->read.mima");
return -1;
}

/*打开数据库文件*/
ret = sqlite3_open("my.db",&db);
if(SQLITE_OK != ret)
{
fprintf(stderr,"open:%s\n",sqlite3_errmsg(db));
return -1;
}
#if 0
/*创建表之前应该判断下有没有user这个表,如果有就不创建了*/
sprintf(sql,"create table user(name char primary key,mima Interger)");
ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
if(SQLITE_OK != ret)
{
fprintf(stderr,"create:%s\n",errmsg);
return -1;
}
#endif
memset(sql,0,sizeof(sql));

/*插入数据*/
sprintf(sql,"insert into user values('%s','%s')",name,mima);
ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
if(SQLITE_OK != ret)
{
k = 0;
fprintf(stderr,"insert:%s\n",errmsg);
ret = write(connfd,&k,sizeof(k));
if(-1 == ret)
{
perror("server_login->write k");
return -1;
}
return -1;
}
ret = write(connfd,&k,sizeof(k));
if(-1 == ret)
{
perror("server_login->write k");
return -1;
}

printf("注册成功\n");
return 0;
}

/********************************************************************/
/*********登陆********************************/
int server_login(int connfd)
{
int ret;
char buf[16];
char buf2[16];
char sql[256];
char sql1[256];
char name[16];
char mima[16];
char *errmsg;
sqlite3 *db;
memset(buf,0,sizeof(buf));
memset(buf2,0,sizeof(buf2));
memset(sql,0,sizeof(sql));
memset(sql1,0,sizeof(sql1));
ret = read(connfd,buf,sizeof(buf));//name
strncpy(name,buf,16);
if(-1 == ret)
{
perror("server_register->read.name");
return -1;
}

ret = read(connfd,buf2,sizeof(buf2));//mima
strncpy(mima,buf2,16);
if(-1 == ret)
{
perror("server_register->read.mima");
return -1;
}

/*打开数据库文件*/
ret = sqlite3_open("my.db",&db);
if(SQLITE_OK != ret)
{
fprintf(stderr,"open:%s\n",sqlite3_errmsg(db));
return -1;
}
/*查询数据*/
char **result;
int row;
int column;
int b = 1;
sprintf(sql,"select * from user where name='%s' and mima='%s'",name,mima);
printf("name = %s,mima=%s\n",name,mima);
ret = sqlite3_get_table(db,sql,&result,&row,&column,&errmsg);
if(SQLITE_OK != ret)
{
fprintf(stderr,"select:%s\n",errmsg);
return -1;
}
if((1 == row))
{
printf("login success\n");
ret = write(connfd,&b,sizeof(b));
if(-1 == ret)
{
perror("server_login->write");
return -1;
}
return 0;
}
else
{
b = 0;
ret = write(connfd,&b,sizeof(b));
if(-1 == ret)
{
perror("server_login->write");
return -1;
}
printf("login fail\n");
return -1;
}
}

/********************************************************************/
/**********搜索*********************************/
int server_search(int connfd,int l)
{
int ret;
char r = '0';
char buf[1024];
char b[256];
char word[64];
memset(buf,0,sizeof(buf));
memset(b,0,sizeof(b));
memset(word,0,sizeof(word));
if(0 != l)
{
printf("please login\n");
return 0;
}

ret = read(connfd,word,sizeof(word));
if(-1 == ret)
{
perror("server_search->read");
return -1;
}

ret = server_history_write(word);///////////////写入历史纪录文档
if(-1 == ret)
{
printf("history write fail\n");
}

FILE *fp;
fp = fopen("dict.txt","r");
while(1)
{
int i = 0;
int k = 1;
memset(buf,0,sizeof(buf));
if (NULL == fgets(buf,sizeof(buf),fp))
{
break;
}
while(word[i])
{
if(word[i] != buf[i])
{
k = 0;
break;
}
i++;
}
if((k == 1) && (buf[i++] ==' '))
{
ret = write(connfd,buf,strlen(buf));
r = '1';//表示找到这个单词
if(-1 == ret)
{
perror("server_search->write");
return -1;
}
break;
}
}
//r = 0;//表示没找到这个单词
if(r == '0')
{
ret = write(connfd,&r,sizeof(r));
if(-1 == ret)
{
perror("server_serach->noword");
return -1;
}
}
return 0;
}

/********************************************************************/
/***********历史记录写入文件********************************/
int server_history_write(char *word)
{
FILE *fp;
size_t ret;
char buf[64];
memset(buf,0,sizeof(buf));
time_t t;
time(&t);
struct tm *tp = localtime(&t);
int i = 0;
fp = fopen("history.txt","a+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
fprintf(fp,"%2d-%2d %2d:%2d:%2d : ",tp->tm_mon +1,tp->tm_mday,tp->tm_hour,tp->tm_min,tp->tm_sec);

while('\0' != word[i])//word len
{
i++;
}

ret = fwrite(word,i,1,fp);
if(-1 == ret)
{
perror("server_history->write time");
return -1;
}

ret = fwrite("\n",1,1,fp);
if(-1 == ret)
{
perror("server_history->write huanhang");
return -1;
}
fclose(fp);
return 0;
}

/********************************************************************/
/****************历史纪录读出来,传给服务器 *******************/
int server_history(int connfd,int l)
{
int fd;
char buf[128];
memset(buf,0,sizeof(buf));
int ret;
FILE *fp;
int i = 0;
if(0 != l)
{
return -1;
}
fp = fopen("history.txt","r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
while(NULL != fgets(buf,sizeof(buf),fp))// 获得行号
{
i++;
}
fclose(fp);

ret = write(connfd,&i,sizeof(i));//发送行号
if(-1 == ret)
{
perror("server_history->line");
return -1;
}

fp = fopen("history.txt","r");
while(NULL != fgets(buf,sizeof(buf),fp))// 发送具体内容
{
ret = write(connfd,buf,sizeof(buf));
if(-1 == ret)
{
perror("server_history->write");
return -1;
}
}
fclose(fp);
return 0;
}

/********client.c******************************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
int client_start(int sockfd); //客户端初始化函数
void client_meau(void); //客户端菜单函数
int client_register(int sockfd); //客户端注册函数
int client_login(int sockfd); //客户端登陆函数
int client_search(int sockfd,int l); //客户端搜索单词函数
int client_history(int sockfd,int l); //客户端查询历史记录函数
int client_name(int sockfd); //客户端登陆和注册输入名字函数
int client_mima(int sockfd); //客户端登陆和注册输入密码函数

int main()
{
int ret;
int sockfd;
int chause;
int l = 2;
char buf[256];
sockfd = client_start(sockfd);
if(-1 == sockfd)
{
printf("client_start failed\n");
return -1;
}

while(1)
{
client_meau();
scanf("%d",&chause);
memset(buf,0,sizeof(buf));
ret = write(sockfd,&chause,sizeof(chause));//传chause给服务器
if(-1 == ret)
{
perror("client->write");
continue;
}

switch(chause)
{
case 1:
client_register(sockfd);
break;
case 2:
l = client_login(sockfd);//用l保存登陆的状态
break;
case 3:
client_search(sockfd,l);
break;
case 4:
client_history(sockfd,l);
break;
case 5:
return 0;
default:
printf("请选择正确的选项\n");
continue;
}
}
close(sockfd);
return 0;
}

/*************************************************************/
/***********客户端********************************************/
int client_start(int sockfd)
{
int ret;
/*1.socket*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("server->socket");
return -1;
}
printf("sockfd = %d\n",sockfd);

/*2.connect*/
struct sockaddr_in addr;
memset(&addr,0,sizeof(struct sockaddr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(-1 == ret)
{
perror("server->bind");
return -1;
}
printf("connect success\n");
return sockfd;
}

/*************************************************************/
/**********主菜单******************************************/
void client_meau(void)
{
printf("**********请您选择要进行的操作****************\n");
printf("----------------------------------------------\n");
printf("| -.-*英英词典*-.- |\n");
printf("| * __ * |\n");
printf("| |\n");
printf("| 1.用户注册 |\n");
printf("| 2.用户登陆 |\n");
printf("| 3.单词在线翻译 |\n");
printf("| 4.历史记录查询 |\n");
printf("| 5.退出 |\n");
printf("| |\n");
printf("----------------------------------------------\n");
printf("\n");
printf("input:");
}

/*************************************************************/
/***********注册*****************************************/
int client_register(int sockfd)
{
int ret;
int k = 0;// 保存注册的状态
ret = client_name(sockfd);
if(-1 == ret)
{
printf("name failed\n");
return -1;
}

ret = client_mima(sockfd);
if(-1 == ret)
{
printf("mima failed\n");
return -1;
}

ret = read(sockfd,&k,sizeof(k));
if(-1 == ret)
{
printf("client_register->read k\n");
return -1;
}
if(k == 1)
{
printf("注册成功\n");
}
else
{
printf("注册失败\n");
}
return 0;
}

int client_name(int sockfd)//name
{
char name;
int ret;
char buf[16];
memset(buf,0,sizeof(buf));
fprintf(stdout,"请输入帐号:\n");
scanf("%s",buf);
//buf[strlen(buf)-1] = '\0';
if(0 == strlen(buf))
{
printf("name is null\n");
return -1;
}
ret = write(sockfd,buf,sizeof(buf));
if(-1 == ret)
{
perror("client_register->write.name");
printf("failed\n");
return -1;
}
return 0;
}

int client_mima(int sockfd)//mima
{
char mima;
int ret;
char buf[16];
fprintf(stdout,"请输入密码\n");
scanf("%s",buf);
//buf[strlen(buf)-1] = '\0';
if(0 == strlen(buf))
{
printf("mima is null\n");
return -1;
}
ret = write(sockfd,buf,sizeof(buf));
if(-1 == ret)
{
perror("client_register->write.mima");
printf("failed\n");
return -1;
}
return 0;
}

/*************************************************************/
/************登陆****************************************/
int client_login(int sockfd)
{
int ret;
char buf[16];
int b = 0;
memset(buf,0,sizeof(buf));
client_name(sockfd);
if(-1 == ret)
{
printf("name failed\n");
return -1;
}
client_mima(sockfd);
if(-1 == ret)
{
printf("mima failed\n");
return -1;
}

ret = read(sockfd,&b,sizeof(b));
if(-1 == ret)
{
perror("client_register->write.mima");
printf("failed\n");
return -1;
}

if(1 == b)
{
printf("login success\n");
return 0;
}
else
{
printf("login fail\n");
return -1;
}
}

/*************************************************************/
/***********搜索************************************/
int client_search(int sockfd,int l)
{
char word[64];
int ret;
char r = '1';
char buf[1024];
memset(word,0,sizeof(word));
memset(buf,0,sizeof(buf));
if(0 != l)
{
printf("please login\n");
return -1;
}

printf("请输入要搜索的单词:\n");
scanf("%s",word);
ret = write(sockfd,word,sizeof(word));
if(-1 == ret)
{
perror("client_search->write");
return -1;
}

ret = read(sockfd,buf,sizeof(buf));
if(-1 == ret)
{
perror("client_search->read");
return -1;
}

if('0' == buf[0])
{
printf("no the word\n");
}
else
{
printf("查询内容:%s\n",buf);
}
return 0;
}

/*************************************************************/
/*************历史查询*************************************/
int client_history(int sockfd,int l)
{
char buf[128];
int ret;
int i = 0;

if(0 != l)
{
printf("please login\n");
return -1;
}

ret = read(sockfd,&i,sizeof(i));
if(-1 == ret)
{
perror("client_history->line");
return -1;
}

printf("一共有%3d个历史数据\n",i);
while(i--)
{
memset(buf,0,sizeof(buf));
ret = read(sockfd,buf,sizeof(buf));
if(-1 == ret)
{
perror("client_history->read");
return -1;
}
printf("%s",buf);
}
printf("\n");
return 0;
}

/**Makefile******************************************************************/
all:
gcc server.c -o server -lpthread -lsqlite3
gcc client.c -o client -lsqlite3
.PHONY:clean
clean:
rm server client
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: