一、功能说明与演示
1.本词典的主要功能有
注册:
用户注册的时候,会提示注册信息,成功或失败。如果用户已经存在了,则会提示”注册失败,用户已存在“
登录:
用户输入用户名和密码进行登录,输入错误则会提示用户重新输入。
查询单词:
查询单词,在终端显示单词的解释。
查询历史记录:
当用户选择查看历史记录的时候,会在终端显示当前登录的用户的历史查询信息,在什么时间查询了什么单词。
2.功能演示
1.注册
2.登录
登录成功后,会跳转到查询页面
3.查询
4.历史记录
二、流程图
三、代码实现
1.服务器端
server.h
#ifndef __SERVER_H__
#define __SERVER_H__
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <signal.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define SERVER_IP "192.168.250.100"
#define SERVER_PORT "8889"
// #define USRNAME "./usrname.txt"
// #define PASSWROD "./password.txt"
#define DATABASE "info.db"
#define FILE_ "./dict.txt"
#define ERRLOG(msg) \
do { \
printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
exit(-1); \
} while (0)
typedef struct _MSG {
char code; //操作码:登录,注册,查询,退出
char name[32]; //此处是用户名
char word[32];
char txt[256]; //登录和注册的时候,此处是密码。查询单词的时候,此处是查询到的单词的解释。如果用户不存在,此处可以是错误信息
} _MSG;
#endif
server.c
#include "server.h"
int max_fd = 0;
int acceptfd = 0;
int loop = 0;
char send_buf[128] = { 0 };
//创建要监视的文件描述集合
fd_set readfds; //母本
fd_set readfds_temp; //给select擦除用的
//数据库初始化的函数
sqlite3* init_process()
{
int ret = 0;
sqlite3* save_word_db = NULL;
char* errmsg = NULL;
char sqlbuff[256] = { 0 };
//打开数据库文件
if (SQLITE_OK != (ret = sqlite3_open(DATABASE, &save_word_db))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(save_word_db));
exit(-1);
}
printf("数据库文件 [%s] 打开成功..\n", DATABASE);
//尝试建表
//组装sql语句 代码中写的sql语句可以不加分号
// IF NOT EXISTS 表示如果表不存在则新建 存在就直接使用
sprintf(sqlbuff, "CREATE TABLE IF NOT EXISTS usrinfo(usrname CHAR PRIMARY KEY, password TEXT)");
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_exec(save_word_db, sqlbuff, NULL, NULL, &errmsg))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, errmsg);
exit(-1);
}
sprintf(sqlbuff, "CREATE TABLE IF NOT EXISTS English(word CHAR PRIMARY KEY, explanation TEXT)");
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_exec(save_word_db, sqlbuff, NULL, NULL, &errmsg))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, errmsg);
exit(-1);
}
printf("数据表创建成功..\n");
//释放errmsg指向的内存空间
sqlite3_free(errmsg);
return save_word_db;
}
void word_obtain_and_insert(FILE* dict, sqlite3* save_word_db)
{
// 1.每行内容不超过300个
char tmp_buf[300] = { 0 };
char word[32] = { 0 };
char recv_char;
int i = 0;
int j = 0;
int worded = 0;
int over = 0;
int space = 0;
//使用sqlite3_prepare可以将插入的时间进一步压缩,原本插入2万条记录需要15秒,现在只需1秒即可完成!!!!
sqlite3_exec(save_word_db, "begin;", 0, 0, 0);
sqlite3_stmt* stmt;
const char* sql = "INSERT INTO English values(?,?)";
sqlite3_prepare(save_word_db, sql, strlen(sql), &stmt, 0);
while (!(feof(dict) || ferror(dict))) {
recv_char = getc(dict);
if (recv_char == EOF) {
break;
}
//最后一次由于读到EOF跳出循环了,不会打印,但是存储是正常的
if (recv_char == '\n') {
//将单词和解释插入save.db数据库中的English表中
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, word, strlen(word), NULL);
sqlite3_bind_text(stmt, 2, tmp_buf, strlen(tmp_buf), NULL);
sqlite3_step(stmt);
//清零,便于下一行的单词和解释的获取
memset(word, 0, sizeof(word));
memset(tmp_buf, 0, sizeof(tmp_buf));
//每次到换行符这里,光标其实已经移动到下一行的开头了,因此,把这些变量都归零一下
i = 0;
j = 0;
worded = 0;
over = 0;
continue;
}
//获取单词
if (worded == 0) {
if (' ' == recv_char) {
//如果是一个词组(两个单词中间会以空格隔开),就往后再判断一位,如果还是空格,就不continue了,直接跳出
if (' ' == (recv_char = getc(dict))) {
fseek(dict, -1, SEEK_CUR);
goto SKIP;
}
fseek(dict, -2, SEEK_CUR);
recv_char = getc(dict);
}
if (recv_char == '\'') { //注意,此处也要加!!!单词中也可能有o'clock 这种形式的,被坑了一下.........
recv_char = '.';
}
word[i] = recv_char;
i++;
continue;
}
SKIP:
worded = 1;
//获取空格(个数)
if (' ' == recv_char && over == 0) {
space++;
continue;
}
over = 1;
//获取解释
if ('\n' != recv_char) {
if (recv_char == '\'') {
recv_char = '.';
}
tmp_buf[j] = recv_char;
j++;
}
}
sqlite3_finalize(stmt);
sqlite3_exec(save_word_db, "commit;", 0, 0, 0);
}
int main(int argc, const char* argv[])
{
// 打开文件,读取每行开头的单词,然后遇到空格就停止,后面再遇到第一个不是空格的字符,就开始保存,直到这一行的结尾
FILE* dict;
if (!(dict = fopen(FILE_, "r"))) {
perror("fopen error");
exit(-1);
}
//创建数据库,创建表
sqlite3* usrinfo_db = init_process();
//向表中插入单词
word_obtain_and_insert(dict, usrinfo_db);
printf("单词信息插入成功..\n");
//创建套接字 流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
ERRLOG("socket error");
}
//填充网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serveraddr.sin_port = htons(atoi(SERVER_PORT));
socklen_t serveraddr_len = sizeof(serveraddr);
//设置允许端口复用,注意要加在 填充网络信息结构体 和 bind 函数之间
int value = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
//绑定
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
ERRLOG("bind error");
}
//将套接字设置成被动监听状态
if (-1 == listen(sockfd, 5)) {
ERRLOG("listen error");
}
//清空集合
FD_ZERO(&readfds);
FD_ZERO(&readfds_temp);
//将sockfd添加到集合中
FD_SET(sockfd, &readfds);
//更新最大文件描述符
max_fd = max_fd > sockfd ? max_fd : sockfd;
int ret = 0;
int i = 0;
int nbytes = 0;
// char buff[128] = { 0 };
_MSG msg;
memset(&msg, 0, sizeof(msg));
//创建数据库和用户名&密码表
// sqlite3* usrinfo_db = init_process();
char sqlbuff[500] = { 0 };
char* errmsg = NULL;
char** result = NULL;
int rows = 0;
int columns = 0;
time_t ts;
struct tm* tm;
while (1) {
readfds_temp = readfds;
if (-1 == (ret = select(max_fd + 1, &readfds_temp, NULL, NULL, NULL))) {
ERRLOG("select error");
}
//遍历集合 看哪个文件描述符就绪了
for (i = 3; i < max_fd + 1 && ret != 0; i++) {
if (FD_ISSET(i, &readfds_temp)) {
if (sockfd == i) {
//说明有新客户端连接了
if (-1 == (acceptfd = accept(sockfd, NULL, NULL))) {
ERRLOG("accept error");
}
printf("客户端[%d]连接..\n", acceptfd);
//将新客户端的acceptfd加入到集合
FD_SET(acceptfd, &readfds);
//更新最大文件描述符
max_fd = max_fd > acceptfd ? max_fd : acceptfd;
} else {
if (-1 == (nbytes = recv(i, &msg, sizeof(msg), 0))) {
printf("recv error");
} else if (0 == nbytes) {
printf("客户端[%d]断开连接..\n", i);
FD_CLR(i, &readfds);
close(i);
continue;
}
// printf("~~~%s\n", msg.txt);
if (!strncmp(msg.txt, "quit", 4)) {
printf("客户端[%d]退出了..\n", i);
FD_CLR(i, &readfds);
close(i);
continue;
}
// printf("~~~%s\n", msg.name);
// printf("~~~%s\n", msg.txt);
switch (msg.code) {
case 'L':
//如果读取正常,用户也没退出,那么就开始判断用户传过来的数据,判断用户是否存在
//组装SQL语句
sprintf(sqlbuff, "SELECT * FROM usrinfo WHERE usrname='%s' AND password='%s'", msg.name,msg.txt);
// printf("-----sql = %s", sqlbuff);
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_get_table(usrinfo_db, sqlbuff, &result, &rows, &columns, NULL))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
}
//记得释放结果集
sqlite3_free_table(result); //!!!
if (rows == 0) {
strncpy(msg.txt, "L_F", 3);
} else if (rows == 1) {
//有用户登陆成功,就为用户创建一个历史记录表
sprintf(sqlbuff, "CREATE TABLE IF NOT EXISTS %s_history(who CHAR,time CHAR,history TEXT)", msg.name);
printf("222zjh222 %s\n", sqlbuff); //
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_exec(usrinfo_db, sqlbuff, NULL, NULL, &errmsg))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, errmsg);
// exit(-1);
}
strncpy(msg.txt, "L_S", 3);
}
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
break;
case 'R':
//如果读取正常,用户也没退出,那么就开始判断用户传过来的数据,判断用户是否存在
//组装SQL语句
sprintf(sqlbuff, "INSERT INTO usrinfo VALUES('%s','%s')", msg.name, msg.txt);
// printf("-----sql = %s", sqlbuff);
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_exec(usrinfo_db, sqlbuff, NULL, NULL, NULL))) {
strncpy(msg.txt, "R_F", 3);
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
// exit(-1);
} else {
strncpy(msg.txt, "R_S", 3);
}
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
break;
case 'S':
//组装SQL语句
sprintf(sqlbuff, "SELECT word FROM English WHERE word='%s'", msg.word);
if (SQLITE_OK != (ret = sqlite3_exec(usrinfo_db, sqlbuff, NULL, NULL, NULL))) {
strncpy(msg.txt, "S_F", 3);
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
// exit(-1);
} else {
// //应该先提取用户输入的单词,再把这个位置填为查询成功
sprintf(sqlbuff, "SELECT explanation FROM English WHERE word='%s'", msg.word);
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_get_table(usrinfo_db, sqlbuff, &result, &rows, &columns, NULL))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
} else {
//如果执行到这儿 说明查询成功了 处理查询结果
int m = 0;
int n = 0;
for (m = 0; m < rows + 1; m++) {
for (n = 0; n < columns; n++) {
// printf("%10s", result[i * columns + j]);
strcpy(msg.txt, result[m * columns + n]);
}
printf("\n");
}
//每次查询成功,还要将本次用户查询的信息插入到:用户名_history 表中
//组装SQL语句
//获取系统时间
if ((ts = time(NULL)) == (time_t)-1)
ERRLOG("get time second error");
if ((tm = localtime(&ts)) == NULL)
ERRLOG("change time error");
char time[128] = { 0 };
sprintf(time, " %d-%02d-%02d %02d:%02d:%02d ", tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
sprintf(sqlbuff, "INSERT INTO %s_history VALUES('%s','%s','%s')", msg.name, msg.name, time, msg.word);
// printf("111zjh111sql = %s", sqlbuff); //
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_exec(usrinfo_db, sqlbuff, NULL, NULL, NULL))) {
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
}
strncpy(msg.word, "S_S", 3);
}
//记得释放结果集
sqlite3_free_table(result); //!!!
}
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
break;
case 'H':
//应该先提取用户输入的单词,再把这个位置填为查询成功
sprintf(sqlbuff, "SELECT * FROM %s_history", msg.name);
// printf("+++++%s\n", sqlbuff);
//执行sql语句
if (SQLITE_OK != (ret = sqlite3_get_table(usrinfo_db, sqlbuff, &result, &rows, &columns, NULL))) {
strncpy(msg.word, "H_F", 3);
printf("%s:%d -- errcode[%d] errstr[%s]\n", __FILE__, __LINE__, ret, sqlite3_errmsg(usrinfo_db));
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
} else {
strncpy(msg.word, "H_S", 3);
//如果执行到这儿 说明查询成功了 处理查询结果
int m = 0;
int n = 0;
for (m = 0; m < rows + 1; m++) {
for (n = 0; n < columns; n++) {
printf("%10s", result[m * columns + n]);
memset(msg.txt, 0, sizeof(msg.txt));
strcpy(msg.txt, result[m * columns + n]);
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
}
printf("\n");
}
strncpy(msg.word, "over", 4);
//发送信息到客户端
if (-1 == send(i, &msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
}
//记得释放结果集
sqlite3_free_table(result); //!!!
break;
}
}
}
}
}
return 0;
}
2.客户端
client.h
#ifndef __CLIENT_H__
#define __CLIENT_H__
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define LOG_IN 1
#define REGISTER 2
#define QUIT 3
#define QUERY 1
#define HISTORY 2
// #define SERVER_IP "112.74.89.58"
// #define SERVER_PORT "39045"
#define SERVER_IP "192.168.250.100"
#define SERVER_PORT "8889"
#define ERRLOG(msg) \
do { \
printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
exit(-1); \
} while (0)
typedef struct _MSG {
char code; //操作码:登录,注册,查询,退出
char name[32]; //此处是用户名
char word[32];
char txt[256]; //登录和注册的时候,此处是密码。查询单词的时候,此处是查询到的单词的解释。如果用户不存在,此处可以是错误信息
} _MSG;
#endif
client.c
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define LOG_IN 1
#define REGISTER 2
#define QUIT 3
#define QUERY 1
#define HISTORY 2
// #define SERVER_IP "112.74.89.58"
// #define SERVER_PORT "39045"
#define SERVER_IP "192.168.250.100"
#define SERVER_PORT "8889"
#define ERRLOG(msg) \
do { \
printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
exit(-1); \
} while (0)
typedef struct _MSG {
char code; //操作码:登录,注册,查询,退出
char name[32]; //此处是用户名
char word[32];
char txt[256]; //登录和注册的时候,此处是密码。查询单词的时候,此处是查询到的单词的解释。如果用户不存在,此处可以是错误信息
} _MSG;
int nbytes = 0;
int first_input_choice(_MSG* msg, int sockfd)
{
int choice = 0;
// 1.提示信息
BEGIN:
system("clear");
printf("--------------------------\n");
printf("| 欢迎使用在线词典,请选择 |\n");
printf("| 1.登录 2.注册 3.退出 |\n");
printf("--------------------------\n");
scanf("%d", &choice);
getchar();
if (choice != 1 && choice != 2 && choice != 3) {
goto BEGIN;
}
// 2.读取用户输入的选择:登录,注册,还是退出,然后继续相应的操作
switch (choice) {
//登录,输入用户名和密码
case LOG_IN:
printf("| 登录 |\n");
memset(msg, 0, sizeof(_MSG));
printf("请输入用户名: \n");
scanf("%s", msg->name);
getchar();
printf("请输入密码: \n");
scanf("%s", msg->txt);
getchar();
msg->code = 'L'; //设置操作码为登录
//发送信息到服务器
if (-1 == send(sockfd, msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
if (-1 == (nbytes = recv(sockfd, msg, sizeof(_MSG), 0))) {
ERRLOG("recv ersockfdror");
}
break;
//注册,输入用户名和密码
case REGISTER:
printf("| 注册 |\n");
memset(msg, 0, sizeof(_MSG));
printf("请输入用户名: \n");
scanf("%s", msg->name);
printf("请输入密码: \n");
scanf("%s", msg->txt);
msg->code = 'R'; //设置操作码为注册
//发送信息到服务器
if (-1 == send(sockfd, msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
//接收服务器发来的消息
if (-1 == (nbytes = recv(sockfd, msg, sizeof(_MSG), 0))) {
ERRLOG("recv ersockfdror");
}
break;
case QUIT:
strcpy(msg->txt, "quit");
printf("~~%s\n", msg->txt);
if (-1 == send(sockfd, msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
printf("| 欢迎下次使用本系统. |\n");
exit(-1);
break;
}
return choice;
}
void query_choice(_MSG* msg, int sockfd)
{
int loop = 0;
int once = 0;
int first = 3;
int choice = 0;
// 1.提示信息
BEGIN:
system("clear");
printf("用户:%s>>>\n", msg->name);
printf("----------------------------\n");
printf("| 在线词典,请选择 |\n");
printf("| 1.查询 2.历史记录 3.退出 |\n");
printf("----------------------------\n");
scanf("%d", &choice);
getchar();
if (choice != 1 && choice != 2 && choice != 3) {
goto BEGIN;
}
switch (choice) {
//查询,输入单词
case QUERY:
printf("请输入要查询的单词: \n");
memset(msg->word, 0, sizeof(msg->word));
scanf("%s", msg->word);
msg->code = 'S'; //设置操作码为查询(select)
//发送信息到服务器
if (-1 == send(sockfd, msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
//接收服务器发来的消息
if (-1 == (nbytes = recv(sockfd, msg, sizeof(_MSG), 0))) {
ERRLOG("recv ersockfdror");
}
printf(">>> %s\n", msg->txt);
getchar();
printf("请按任意键继续..\n");
getchar();
break;
//查看历史记录
case HISTORY:
msg->code = 'H'; //设置操作码为查询历史记录(history)
memset(msg->word, 0, sizeof(msg->word));
memset(msg->txt, 0, sizeof(msg->txt));
//发送信息到服务器
if (-1 == send(sockfd, msg, sizeof(_MSG), 0)) {
ERRLOG("send error");
}
//接收服务器发来的消息
while (0 != strncmp(msg->word, "over", 4)) {
printf(" %s ", msg->txt);
//第一行的内容,表头信息,要隔开,不要距离太近
if (first != 0 && once == 1) {
printf("\t ");
first--;
}
if (once == 0) {
once = 1;
printf("\n");
}
if (loop == 3) {
printf("\n");
loop = 0;
}
loop++;
if (-1 == (nbytes = recv(sockfd, msg, sizeof(_MSG), 0))) {
ERRLOG("recv ersockfdror");
}
}
printf("\n");
printf("请按任意键继续..\n");
getchar();
break;
case QUIT:
strcpy(msg->txt, "quit");
break;
}
}
int main(int argc, char const* argv[])
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
ERRLOG("socket error");
}
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(SERVER_PORT));
serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP);
socklen_t serveraddr_len = sizeof(serveraddr);
//与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
ERRLOG("connect error");
}
_MSG msg;
while (1) {
memset(&msg, 0, sizeof(msg));
first_input_choice(&msg, sockfd);
//判断登录是否成功
if (0 == strncmp(msg.txt, "L_S", 3)) {
printf("登陆成功..\n");
while (1) {
query_choice(&msg, sockfd);
if (0 == strncmp(msg.txt, "quit", 4))
break;
}
} else if (0 == strncmp(msg.txt, "L_F", 3)) {
printf("登陆失败,请检查用户名和密码后重新输入..\n");
printf("请按任意键继续..\n");
getchar();
}
//判断注册是否成功
if (0 == strncmp(msg.txt, "R_S", 3)) {
printf("注册成功..\n");
printf("请按任意键继续..\n");
getchar();
getchar();
} else if (0 == strncmp(msg.txt, "R_F", 3)) {
printf("注册失败,用户已存在..\n");
printf("请按任意键继续..\n");
getchar();
getchar();
}
}
return 0;
}
总结
对于一些步骤和需要注意的点,已经在代码中通过注释的形式说明(在这里偷个懒,希望各位看官见谅T_T),如果有不合适或者错误的地方,希望大家批评指正,我会虚心改正。