简单的网络聊天室

来源:互联网 发布:淘宝网旗袍服饰 编辑:程序博客网 时间:2024/06/07 23:31

一个在 Linux 下可以使用的聊天软件,要求至少实现如下功能:


1. 采用 Client/Server 架构
2. Client A 登陆聊天服务器前,需要注册自己的 ID 和密码
3. 注册成功后,Client A 就可以通过自己的 ID 和密码登陆聊天服务器
4. 多个 Client X 可以同时登陆聊天服务器之后,与其他用户进行通讯聊天
5. Client A 成功登陆后可以查看当前聊天室内其他在线用户 Client x
6. Client A 可以选择发消息给某个特定的 Client X,即”悄悄话”功能
7. Client A 可以选择发消息全部的在线用户,即”群发消息”功能
8. Client A 在退出时需要保存聊天记录

9. Server 端维护一个所有登陆用户的聊天会的记录文件,以便备查

这里我们说一下聊天室几个重点,第一数据存储问题,如果简单的用一个数组来存的话,会出现数据覆盖的问题,所以我们想到了一个链表或者数据库,感觉数据库更为方便,第二是几个参量的判断,设置几个参量来表明是在线或者不在线,普通用户或者管理员,以及只有普通用户才可以被禁言,第三是使用数据库的时候,熟悉数据库的增删改查的操作,并且用完后关闭数据库,第四一个服务器可以被多个客服端连接,服务器此时有两个套接字,一个是用于监听其他客服端的连接,一个是用于处理客服端与服务器之间的数据传递,我们可以用多个进程来做也可以用线程分离来完成



1

以下是服务器的代码

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <sqlite3.h> 


#define PORT  9999


sqlite3 * database;


// 协议
struct Msg
{
char msg[1024];  // 消息内容
int  cmd;        // 消息类型
char filename[50];// 保存文件名
char toname[20];// 接收者姓名
char fromname[20];// 发送者姓名
int  sig; // 用户状态(0:管理员、1:普通用户、2:被禁言)
};


// 初始化套接字,返回监听套接字
int init_socket()
{
//1、创建socket
int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1)
{
perror ("socket");
return -1;
}

// 2、命名套接字,绑定本地的ip地址和端口
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family  = AF_INET;     // 设置地址族
addr.sin_port    = htons(PORT); // 设置本地端口
addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 使用本地的任意IP地址

int  ret = bind(listen_socket,  (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("bind");
return -1;
}

// 3、监听本地套接字
ret = listen(listen_socket, 5);
if (ret == -1)
{
perror ("listen");
return -1;
}

printf ("服务器已就绪,等待客户端连接.......\n");
return listen_socket;
}


// 处理客户端连接,返回与连接上的客户端通信的套接字
int MyAccept(int listen_socket)
{
// 4、接收连接
struct sockaddr_in client_addr; // 用来保存客户端的ip和端口信息
int len = sizeof(client_addr);
int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &len);
if (client_socket == -1)
{
perror ("accept");
}
printf ("成功接收一个客户端: %s\n", inet_ntoa(client_addr.sin_addr));

return client_socket;
}


// 查看当前在线人数
void display (int client_socket, struct Msg *msg)
{
printf ("查看当前在线人数\n");

// 确认flag参数
// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 与User表中信息进行比对
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);

//3、char ***resultp: char *resultp[] = {"fromname", "password", "socket", "flag", "mig",  "12","2".....};
//一维字符字符串指针数组,是一个指针的指针的指针,是一个*是指向字段,另一个*指向字段下面的数值
// 4、nrow: 多少行数据
// 5、ncolumn: 多少列数据
// 6表示错误类型的参数
// 先执行sq1的语句,在将其那种行列排好

if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
int count = 0;
int i;
for (i = 3+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
//前面的行列是没有加上字段,此时全部打印加上1

if(strcmp(resultp[i], "1") == 0)
{
count++;
}
}
// 返回在线人数
msg->cmd = count;
printf ("当前在线人数为:%d\n", msg->cmd);
write (client_socket, msg, sizeof(struct Msg));
//向客服端返回在线情况
sqlite3_free_table(resultp);
// 关闭数据库,关闭数据库摆列字段

sqlite3_close(database);

printf ("操作完成,已关闭数据库\n");
}


// 退出聊天室,返回登录界面
void quit_chatroom (int client_socket, struct Msg *msg)
{
printf ("%s 退出聊天室\n", msg->fromname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}
char buf[100];
char *errmsg = NULL;
errmsg = NULL;
sprintf (buf, "update user set flag = 0 where name = '%s'",msg->fromname);
//只要把登陆者改掉就行了

ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
sqlite3_close(database);

printf ("登录状态修改完毕,已关闭数据库\n");
write (client_socket, msg, sizeof(struct Msg));
}


// 客户端发送群聊消息
void chat1 (int client_socket, struct Msg *msg)
{
printf ("%s 发了一条群消息\n",msg->fromname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的flag参数信息
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
int i;
for (i = 3+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
// 查询所有在线的用户
if(strcmp(resultp[i], "1") == 0)
{
msg->cmd = 9001;
write (atoi(resultp[i-1]), msg, sizeof(struct Msg));
         //套接字就是文件描述符,将字符串转换为int
          //每一个接听客服端都有一个新的套接字  
}
}
printf ("群消息已全部发送完成\n");
}


// 客户端发送私聊消息
void chat2 (int client_socket, struct Msg *msg)
{
printf ("%s 向 %s 发了一条消息\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的flag参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
//遍历所有的登陆者信息
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
//首先由这个
msg->cmd = 9002;
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
//如果这个人没有注册的话
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 处理确认文件传输对象
void convey_confirm (int client_socket, struct Msg *msg)
{
printf ("%s 向 %s 发送文件传输请求\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}

// 获取数据库中的flag参数信息
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
msg->cmd = 9004;
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 用户不接受文件
void refuse (int client_socket, struct Msg *msg)
{
printf ("%s 拒绝了 %s 的文件传输请求\n",msg->fromname,msg->toname);



// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}

// 获取数据库中 toname 的套接字
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 用户接受文件
void accept_ (int client_socket, struct Msg *msg)
{
printf ("%s 通过了 %s 的文件传输请求\n",msg->fromname,msg->toname);
// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}

// 获取数据库中 toname 的套接字
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 处理文件传输
void convey_chose (int client_socket, struct Msg *msg)
{
printf ("%s正在向%s传输文件......\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的 flag 参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
// 获取toname的套接字
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 文件传输完成
void convey_complete (int client_socket, struct Msg *msg)
{
printf ("文件传输结束\n");

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的 flag 参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
// 获取toname的套接字
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));
return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
}


// 更改密码
void change_pass (int client_socket, struct Msg *msg)
{
printf ("%s请求修改密码\n", msg->fromname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 与User表中信息进行比对
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from user";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->fromname)==0 && strcmp(resultp[i+1], msg->msg)==0)
{
//有这个人并且密码是对的
// 返回确认信息
msg->cmd = 9009;
printf ("%s 验证通过\n", msg->fromname);
write (client_socket, msg, sizeof(struct Msg));


// 修改密码
char buf[100];
errmsg = NULL;
sprintf (buf, "update user set password = '%s' where name = '%s'",msg->filename,msg->fromname);
//该为保存filename中的新密码并且
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
sqlite3_free_table(resultp);

// 关闭数据库
sqlite3_close(database);
printf ("密码修改完成,已关闭数据库\n");
return;
}
}
//密码输入与数据库对不上
printf ("%s 验证不通过,密码输入有误\n", msg->fromname);
msg->cmd = 9010;
write (client_socket, msg, sizeof(struct Msg));

sqlite3_free_table(resultp);
// 关闭数据库
sqlite3_close(database);
printf ("操作完成,已关闭数据库\n");

}


// 客户端请求在线注销
void delete_user (int client_socket, struct Msg *msg)
{
printf ("即将处理用户注销\n");

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}

// 删除 user 表中的信息
char buf[100];
char *errmsg = NULL;
sprintf (buf, "delete from user where name = '%s'", msg->fromname);

ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}


// 关闭数据库
sqlite3_close(database);
printf ("删除成功,已关闭数据库\n");
}


// 处理禁言请求
void silent (int client_socket, struct Msg *msg)
{
printf ("正在处理管理员 %s 对成员 %s 的禁言请求\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的 flag 参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
// 获取toname的套接字
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
//有这个人并且在线的话
msg->cmd =  9011;
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));//将字符串转换为int套接字

char buf[100];
errmsg = NULL;
sprintf (buf, "update user set sig = 2 where name = '%s'",msg->toname);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
printf ("禁言状态修改完毕,已关闭数据库\n");


return;
}
}
//没有或者不存在
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));

sqlite3_close(database);
printf ("用户不在线,修改失败,已关闭数据库\n");
}


// 处理解除禁言请求
void silent_del (int client_socket, struct Msg *msg)
{
printf ("正在处理管理员 %s 对成员 %s 的解除禁言请求\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的 flag 参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
// 获取toname的套接字
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
//可以加一下strcmp(resultp[i+4], "2")== 0
{
msg->cmd =  9012;
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));

char buf[100];
errmsg = NULL;
sprintf (buf, "update user set sig = 1 where name = '%s'",msg->toname);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
printf ("禁言状态修改完毕,已关闭数据库\n");


return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));

sqlite3_close(database);
printf ("用户不在线,修改失败,已关闭数据库\n");


}


// 处理踢出成员
void kickout (int client_socket, struct Msg *msg)
{
printf ("正在处理管理员 %s 对成员 %s 的踢出请求\n",msg->fromname,msg->toname);

// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 获取数据库中的 flag 参数信息,判断是否在线
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from User";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
// 获取toname的套接字
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], msg->toname)==0 && strcmp(resultp[i+3], "1")== 0)
{
msg->cmd =  9013;
write (atoi(resultp[i+2]), msg, sizeof(struct Msg));

char buf[100];
errmsg = NULL;
sprintf (buf, "update user set flag = 0 where name = '%s'",msg->toname);
//退出之前要把flag置为0,下线标志
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
printf ("踢出完毕,已关闭数据库\n");


return;
}
}
msg->cmd = 9003;
write (client_socket, msg, sizeof(struct Msg));
sqlite3_close(database);
printf ("用户不在线,修改失败,已关闭数据库\n");
}


// 处理用户操作请求函数
void user_do (int client_socket)
{
struct Msg msg;
int sig = 0; 
//printf("11111\n");
while(1)
{
printf("处理用户操作请求函数\n");
// 从客户端读一个结构体数据
int ret = read(client_socket, &msg, sizeof(msg));
//从文件描述符里读取多少个字节到缓冲区,返回的字节数
if (ret == -1)
{
perror ("read");
break;
}

// 代表客户端退出
if (ret == 0)
{
printf ("客户端返回登录界面\n");//如果一个字节没有读到,说明没有发送,即
//返回登录界面 
break;
}

switch (msg.cmd)
{
case 10: // 退出聊天室,返回登录界面
quit_chatroom(client_socket, &msg);
//sig = 1;
break;
case 1 :    // 查看当前在线人数
display (client_socket, &msg);
break;
case 2 :    // 处理群聊消息 
chat1 (client_socket, &msg);
break;
case 3 :    // 处理私聊消息 
chat2 (client_socket, &msg);
break;
case 5 : // 处理确认文件传输对象
convey_confirm (client_socket, &msg);
break;
case 6 : // 更改密码
change_pass (client_socket, &msg);
break;
case 8 : // 处理在线注销
delete_user (client_socket, &msg);
break;
case 9005 : // 用户不接受文件
refuse (client_socket, &msg);
break;
case 9006 : // 用户接受文件
accept_ (client_socket, &msg);
break;
case 9007 : // 处理文件传输
convey_chose (client_socket, &msg);
break;
case 9008 : // 文件传输完成
convey_complete (client_socket, &msg);
break;
case 9011: // 处理禁言请求
silent (client_socket, &msg);
break;
case 9012: // 处理解除禁言请求
silent_del (client_socket, &msg);
break;
case 9013:  // 处理踢出成员
kickout (client_socket, &msg);
break;
}
if (sig == 1)
{
printf("即将退出普通用户操作请求函数\n");
break;
}
}
}


// 处理客户端的登录请求
void log_in(int client_socket, struct Msg *msg)
{
printf ("%s 请求登录\n", msg->fromname);

// 将用户信息进行比对
// 打开数据库
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;
write (client_socket, msg, sizeof(struct Msg));

//把数据库打开向客服端发过去
return;
}

// 与User表中信息进行比对
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from user";
////这里的行数是不加字段,就是原来的数据库的数据



ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);




//3、char ***resultp: char *resultp[] = {"fromname", "password", "socket", "flag", "mig",  "12","2".....};
//一维字符字符串指针数组,是一个指针的指针的指针,是一个*是指向字段,另一个*指向字段下面的数值
// 4、nrow: 多少行数据
// 5、ncolumn: 多少列数据
// 6表示错误类型的参数
// 先执行sq1的语句,在将其那种行列排好



if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
int i;
for (i = 0+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn) //i开始就是ncolunm,这里是5,resultp[5]就是第一个
// 注册的名字,后面以5向下翻
{
//所以要对后面全部打印出来,必须对nrow+1,因为字段是一行
if(strcmp(resultp[i], msg->fromname)==0 && strcmp(resultp[i+1], msg->msg)==0)
{
// 字符串比较,比较他们是否相同

if (strcmp(resultp[i+3], "1") == 0)
{
msg->cmd = -4;
printf ("%s 已经在别处登录\n", msg->fromname);
write (client_socket, msg, sizeof(struct Msg));
//同时关掉这个函数
sqlite3_free_table(resultp);
// 关闭数据库
sqlite3_close(database);
printf ("操作完成,已关闭数据库\n");
return;
}
if (strcmp(resultp[i+4], "0") != 0)//普通用户可以被禁言
{
// 普通用户
msg->cmd = 1002;
msg->sig = atoi(resultp[i+4]);//字符串转为整形,前面用sprintf打印成字符串
printf ("普通用户 %s 验证通过\n", msg->fromname);
write (client_socket, msg, sizeof(struct Msg));
}
else 
{
// 管理员,只是0执行这个循环
msg->cmd = 1003;
msg->sig = atoi(resultp[i+4]);
printf ("管理员 %s 验证通过\n", msg->fromname);
write (client_socket, msg, sizeof(struct Msg));
}

// 修改在线状态、更新套接字,这里讲flag置为1
char buf[100];
errmsg = NULL;
sprintf (buf, "update user set socket = '%d',flag = 1 where name = '%s'",client_socket,msg->fromname);

// 将后面两个参数以字符串存在buf里面,并且执行buf语句

// 登陆一个账号过后,flag改为1,以后再登陆此账号将执行第一个if
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}

// 关闭数据库
sqlite3_close(database);
printf ("在线状态已更新,已关闭数据库\n");

user_do (client_socket);//登录的同时执行这个函数

return;
}
}
printf ("%s 验证不通过\n", msg->fromname);
msg->cmd = -3;
write (client_socket, msg, sizeof(struct Msg));

sqlite3_free_table(resultp);//关闭数据库你这样的排列
// 关闭数据库
sqlite3_close(database);
printf ("操作完成,已关闭数据库\n");
}


// 处理客户端的注册请求
void reg(int client_socket, struct Msg *msg)
{
printf ("%s 进行注册\n", msg->fromname);

// 将用户进行保存
// 打开数据库 
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
msg->cmd = -2;//这里将类型置为-2

write (client_socket, msg, sizeof(struct Msg));//操作失败也要回应
return;
}

// 往User表中添加信息
//第2个参数const char *sql 是一条 sql 语句,以/0结尾,第3个参数sqlite3_callback 是回调,当这条语句执行之后
//参数void * 是你所提供的指针,你可以传递任何一个指针参数到这里不用置为NULL
char buf[100];
char *errmsg = NULL;
sprintf (buf, "insert into user values('%s','%s',%d,%d,%d)",msg->fromname,msg->msg,client_socket,0,msg->sig);
//printf("%s,%s,%d,%d,%d",msg->fromname,msg->msg,client_socket,0,msg->sig);
//打印到字符串中,,显然就在第二个参数:格式化字符串上。将名字,密码,消息类型,flag值为0,用户状态
// 存在buf里面,并没有打印出来
//并且执行sprintf的语句,说明这个语句也存在buf里面
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
//写入的名字插入数据库,如果名字一样,主要建插入不进去
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
msg->cmd = -1;
write (client_socket, msg, sizeof(struct Msg));
return;
}
// 返回确认信息
msg->cmd = 1001;
printf ("%s 注册成功\n", msg->fromname);
write (client_socket, msg, sizeof(struct Msg));
// 关闭数据库
sqlite3_close(database);
printf ("操作完成,已关闭数据库\n");
}


// 线程的工作函数,即处理客户端请求的函数
void* hanld_client(void* v)
{
int client_socket = (int)v;
struct Msg msg;
while(1)
{
printf("处理客户端请求的函数函数已就绪\n");
// 从客户端读一个结构体数据
int ret = read(client_socket, &msg, sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}

// 代表客户端退出,什么也没有读到
if (ret == 0)
{
printf ("客户端退出\n");
break;
}

switch (msg.cmd)
{
case 1 :    // 客户端进行注册
reg(client_socket, &msg);
break;
case 2 :    // 客户端进行登录
log_in(client_socket, &msg);
break;
}
}

close (client_socket);
}


int main()
{
// 打开数据库User.db
int ret = sqlite3_open("User.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return -1;
}
// 创建 user 表
char *errmsg = NULL;
char *sql = "create table if not exists user(name TEXT,password TEXT,socket INTEGER,flag INTEGER,sig INTEGER,primary key(name))";
// 如果这个表不存在的话,创建
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return -1;
}
printf ("数据库准备就绪......\n");
// 关闭数据库
sqlite3_close(database);

// 初始化套接字
int listen_socket = init_socket();

while (1)
{
// 获取与客户端连接的套接字
int client_socket = MyAccept(listen_socket);

// 创建一个线程去处理客户端的请求,主线程依然负责监听
pthread_t id;
pthread_create(&id, NULL, hanld_client, (void *)client_socket);

pthread_detach(id); // 线程分离
}
close (listen_socket);

return 0;
}



2

以下是客户端的代码

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <sqlite3.h> 
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>


#define PORT  9999


char myName[20]; // 保存用户名
char msg1[1024]; // 保存聊天信息


sqlite3 * database;    //这个数据库类型就是database


int flag1 = 0; // 线程退出的判断条件(不退出)
int flag2 = 0; // 文件传输确认信号(无接收)
int flag3 = 0; // 存在文件接收请求的判断(不存在)
int flag4 = 0; // 本地存储是否被禁言(未被禁言)
int flag5 = 0; // 返回登录界面(不返回)
int flag7 = 0;          //确认传输文件


// 协议
struct Msg
{
char msg[1024];  // 消息内容
int  cmd;        // 消息类型
char filename[50];// 保存文件名
char toname[20];// 接收者姓名
char fromname[20];// 发送者姓名
int  sig; // 用户状态(0:管理员、1:普通用户、2:被禁言)
};


struct Msg msg; // 全局变量两个线程共享


// 注册/登录界面
void interface1()
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                    1、 注册                                      *\n");                               
printf ("\t*                    2、 登录                                      *\n");
printf ("\t*                    q、 退出                                      *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                 BY 漫天飞舞      *\n");
printf ("\t******************************************************************\n\n");
printf ("\t***** 请输入命令: ");

}


// 普通用户界面
void interface2()
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                    1、 查看当前在线人数                          *\n");                               
printf ("\t*                    2、 进入群聊界面                              *\n");
printf ("\t*                    3、 进入私聊界面                              *\n");
printf ("\t*                    4、 查看聊天记录                              *\n");
printf ("\t*                    5、 文件传输                                  *\n");
printf ("\t*                    6、 更改密码                                  *\n");
printf ("\t*                    7、 在线注销                                  *\n");
printf ("\t*                    Q、 退出聊天室 返回登录界面                   *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                    BY  漫天飞舞  *\n");
printf ("\t********************************************************************\n\n");
printf ("\t***** 请输入命令: ");
}


// 管理员界面,管理员有个特殊的功能可以禁言,可以将人提出群等等
void interface3()
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                    1、 查看当前在线人数                          *\n");                               
printf ("\t*                    2、 进入群聊界面                              *\n");
printf ("\t*                    3、 进入私聊界面                              *\n");
printf ("\t*                    4、 查看聊天记录                              *\n");
printf ("\t*                    5、 文件传输                                  *\n");
printf ("\t*                    6、 更改密码                                  *\n");
printf ("\t*                    7、 在线注销                                  *\n");
printf ("\t*                    8、 管理员界面                                *\n");
printf ("\t*                    Q、 退出聊天室 返回登录界面                   *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                                  *\n");
printf ("\t*                                                    BY   漫天飞舞 *\n");
printf ("\t********************************************************************\n\n");
printf ("\t***** 请输入命令: ");
}


// 用来保存收到的聊天信息
void keep_msg(char * msg1)
//群聊和私聊在聊的时候自动保存在数据库,而群聊的接收者就是all


{
// 打开数据库
int ret = sqlite3_open("Histroy.db", &database);
if (ret != SQLITE_OK)
{
printf ("\t打开数据库失败\n");
return;
}


// 往histroy表中添加信息
char buf[100];
char *errmsg = NULL;
sprintf (buf, "insert into histroy values('%s','%s','%s')",msg.fromname,msg.toname,msg1);
//b把msg1里面的内容存入数据里
//执行sq1语句,就是buf里面的
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("\t数据库操作失败:%s\n", errmsg);
return;
}
}


// 接收文件
void receive(int socketfd)
{
printf ("\n\t正在接收文件.....\n");
int fd2 = open(msg.filename, O_WRONLY|O_CREAT, 0777);
//打开一个新的文件,描述符为fd2 ,名字一样
if (fd2 == -1)
{
perror ("open fd2");
return;
}

read (socketfd, &msg, sizeof(struct Msg)); 
write (fd2, msg.msg, 1023);//并将传来的内容写入文件描述符中
close (fd2);
}


// 用来监听收到的信息
void *readMsg(void *v)
{


int socketfd = (int)v;
//强制转换将v强制装换为int,并且v赋给套接字
while(1)
{
if (flag1 == 1)// 判断线程的退出条件
{
flag1 = 0; // 重置线程退出条件
pthread_exit(NULL);

read (socketfd, &msg, sizeof(msg)); //接收服务器发来的信息
//接收所有的在线的套接字信息
switch(msg.cmd)
{
case 9001:   // 群聊
sprintf (msg1,"%s发送了一条群信息:\n\t%s",msg.fromname, msg.msg);
//打印成字符串
printf ("\n\n\t%s\n", msg1);
printf ("\n\t回车键返回  \n");
keep_msg (msg1);
break;
case 9002: // 私聊
printf ("\n\t%s 发来一条消息:\n\t%s\n", msg.fromname, msg.msg);
sprintf (msg1,"%s 向 %s 即是本人发送一条信息:\n\t%s",msg.fromname, msg.toname, msg.msg);

printf ("\n\n\t%s\n", msg1);
printf ("\n\t回车键返回  \n");
keep_msg (msg1);
break;
case 9003: // 处理发送失败
sleep(3);
printf ("\n\t用户不在线或不存在,发送失败\n");
printf ("\n\t回车键返回  \n");
break;
case 9004: // 是否存在文件接收确认信号
printf ("\n\n\t收到一条信息,输入任一字符进行回复:");

//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
fflush(stdout);

flag3 = 1;//回到下面的循环中

break;
case 9005: // 文件传输请求被拒绝
printf ("\n\t您发送的文件传输请求已被拒绝\n");
printf ("\n\t回车键返回  \n");
break;
case 9006: // 文件传输请求已通过
printf ("\n\t您发送的文件传输请求已通过,请打开文件传输界面进行文件传输\n");
printf ("\n\t回车键返回  \n");
break;
case 9007: // 接收文件
// if (flag2 != 1)
if (flag7 != 1)
printf ("\n\t已成功拦截 %s 给您发送的文件\n", msg.fromname);
else
receive( socketfd);
break;
case 9008: // 文件传输完成
printf ("\n\t%s 给您发送的文件已全部接收完毕,请及时查看\n", msg.fromname);
printf ("\n\t回车键返回  \n");
flag2 = 0; // 重置文件传输确认信号
break;
case 9009: // 密码修改成功
printf ("\n\t密码修改成功!\n");
sleep(1);
break;
case 9010: // 密码输入有误
printf ("\n\t密码输入有误,修改失败\n");
usleep(1500000);
break;
case 9011: // 收到禁言信号
printf ("\n\t您已被管理员禁言,将无法发送群聊信息\n");
printf ("\n\t回车键返回  \n");
flag4 = 2;
break;
case 9012: // 收到结除禁言信号
printf ("\n\t您已被管理员解除禁言\n");
printf ("\n\t回车键返回  \n");
flag4 = 1;
break;
case 9013: // 收到被踢出信号
printf ("\n\t您已被管理员踢出,回车键退出聊天室....\n");
flag5 = 1; 
break;
}
usleep(400000);
}
}


// 查看当前在线人数
void display (int socketfd)
{
msg.cmd = 1;//前面的内存覆盖为1

write (socketfd, &msg, sizeof(struct Msg)); 
//向服务器发送请求
//read(socketfd, &msg, sizeof(struct Msg)); 

//相应客服端返回过来的信息,不能用read来访问不然将阻塞在那里不动
//printf("1111\n");


usleep(100000);//睡觉一秒
printf ("\n\t当前在线人数为:%d\n", msg.cmd);
printf ("\n\t回车键返回  \n");
getchar();
}


// 退出聊天室,返回登录界面
void quit_chatroom (int socketfd)
{
msg.cmd = 10;
strcpy (msg.fromname, myName);
//退出之后把flag置为0
write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送退出信号
}


// 进入群聊界面
void chat1(int socketfd)
{
if (flag4 == 2)
{
printf ("\n\t您已被管理员禁言,无法发送群聊信息...\n");
return;
//然后就结束了
}
msg.cmd = 2;
strcpy (msg.fromname, myName);
//就是你登陆时的名字
strcpy(msg.toname, "all");

printf ("\n\t请输入您要发送的内容:\n\t");
scanf ("%s",msg.msg);

getchar();//目的就是加入回车键

write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求

printf ("\n\t发送完成,等待处理结果.....\n");
// usleep (500000);
}


// 进入私聊界面
void chat2(int socketfd)
{
msg.cmd = 3;
strcpy (msg.fromname, myName);

printf ("\n\t请输入您要发送的对象:\n\t");
scanf ("%s",msg.toname);
getchar();

printf ("\t请输入您要发送的内容:\n\t");
scanf ("%s",msg.msg);
getchar();

write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求

printf ("\n\t发送完成,请稍候.....\n");
usleep (500000);
}


// 打印群聊历史记录
void chat1_hst()
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n\n\n");
printf ("\t群聊历史记录:                                                      \n");


// 打开数据库
int ret = sqlite3_open("Histroy.db", &database);
if (ret != SQLITE_OK)
{
printf ("\t打开数据库失败\n");
return;
}


// 获取histroy表中的信息
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from histroy";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 1+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
//遍历数据库中所有的接收者,如果是all就打印信息
{
if(strcmp(resultp[i], "all") == 0)
{
printf ("\n\t%s\n", resultp[i+1]);
}
}
sqlite3_free_table(resultp);//关闭字段


// 关闭数据库
sqlite3_close(database);
}


// 打印私聊历史记录
void chat2_hst()
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n\n\n");
printf ("\t私聊历史记录:\n");


// 打开数据库
int ret = sqlite3_open("Histroy.db", &database);
if (ret != SQLITE_OK)
{
printf ("\t打开数据库失败\n");
return;
}


// 获取histroy表中的信息
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from histroy";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
int i;
for (i = 1+ncolumn; i < (nrow+1)*ncolumn; i+=ncolumn)
{
if(strcmp(resultp[i], "all") != 0)
//接收者不是all就是私聊了

{
printf ("\n\t%s\n", resultp[i+1]);
}
}
sqlite3_free_table(resultp);

// 关闭数据库
sqlite3_close(database);
}


// 查看聊天记录
void dis_histroy(int socketfd)
{
printf ("\n\t a:查看群聊记录\n\t b:查看个人聊天记录\n\t");
printf ("\n\t***** 请选择: ");
switch (getchar())
{
case 'a':
chat1_hst();
break;
case 'b':
chat2_hst();
break;
}
printf ("\n\t回车键返回  ");
getchar();
}


// 确认传输对象
void convey_confirm(int socketfd)
{
msg.cmd = 5;
strcpy (msg.fromname, myName);

printf ("\n\t请输入文件的传输对象:\n\t");
scanf ("%s",msg.toname);
getchar();

printf ("\n\t传输请求发送完成,请稍等.....\n");
write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求
}


// 文件传输过程
void convey_chose(int socketfd)
{
msg.cmd = 9007;
strcpy (msg.toname, msg.fromname);
//将lpd4改为接受者
strcpy (msg.fromname, myName);

printf ("\n\t当前目录下的文件有: \n\n\t");



fflush(stdout);
//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
    //printf("。。。。。。。。。。。");后面加fflush(stdout);可提高打印效率


system ("ls");
printf ("\n\t请输入您要传送的文件名:  ");
printf ("\n\t");

scanf ("%s",msg.filename);
//保存文件名 

getchar();


// 打开要读的文件
int fd1 = open(msg.filename, O_RDONLY);
if (fd1 == -1)
{
perror ("open fd1");
return;
}
int ret = 0;
flag1 = 1; // 关闭线程
while (ret = read (fd1, msg.msg, 1023))//以1023 读取fd1里内容存取msg缓冲区中
//第二个参数是const void *buf, 只读指针,但是这个字符串名字就是代表首地址
{
if (ret == -1)
{
perror("read");
break;
}
printf ("\n\t正在传输文件,请稍候......\n");


write (socketfd, &msg, ret);
//边读边写,将内容以返回的 字节数写入套接字字符中

write (socketfd, &msg, sizeof(struct Msg));


sleep(1);
}
msg.cmd = 9008;
write (socketfd, &msg, sizeof(struct Msg));//发送请求服务器中

printf ("\n\t文件传输完成\n");
close (fd1);

// 重新开启一个线程
pthread_t id;
pthread_create(&id, NULL, readMsg, (void*)socketfd);
pthread_detach(id); 

// getchar();
}


// 文件传输界面
void convey(int socketfd)
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n\n\n");
printf ("\t                     1、传输对象确认\n");
printf ("\t                     2、选择文件\n");
printf ("\n\t***** 请选择: ");
char ch[2];
fgets(ch, 2, stdin);
while (getchar()!= '\n');
switch (ch[0])
{
case '1': // 确认传输对象
convey_confirm(socketfd);
break;
case '2': // 文件选择
convey_chose(socketfd);
break;


}
printf ("\n\t回车键返回  ");
getchar();
}


// 更改密码
void change_pass(int socketfd)
{
char ch[2];
msg.cmd = 6;
printf ("\n\t您是否确定需要修改密码?(y/n): ");
fgets(ch, 2, stdin);
while (getchar()!= '\n');
if (ch[0] != 'y')
{
printf ("\n\t请稍候.....\n");
usleep(700000);
return;
}
printf ("\t请输入旧密码: ");
scanf ("%s", msg.msg);
getchar();

printf ("\t请输入新密码: ");
scanf ("%s", msg.filename);//新密码暂时存在filename里
getchar();

strcpy (msg.fromname, myName);
//登陆者就是发送者

write (socketfd, &msg, sizeof(struct Msg)); // 向服务器发送注册信息

printf ("\n\t正在校验数据,请稍候......\n");
sleep(1);
}


// 在线注销
void delete_user(int socketfd)
{
msg.cmd = 8;

printf ("\n\t正在处理注销操作......\n");
write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求


sleep(1);
printf ("\t注销完成!\n");//删除之后自动返回登录界面
}


// 普通用户操作
void user_do (int socketfd)
{
int flag6 = 0;// 再执行一次 quitroom 的操作
char ch[2];
//程序往下走的时候,肯定是flag带下去,并且这个
while(1)
{
interface2();

if (flag3 == 1)
{
printf ("\n\n\t%s 请求传输文件,是否接收?(y/n):", msg.fromname);
//当程序将flag3为1 带来时,你你输入时数字时,程序跳过这个循环,执行下面的那个输入
fflush(stdout);
fgets(ch, 2, stdin);
while (getchar()!= '\n');
if (ch[0] != 'y')
{
printf ("\n\t您已拒绝接受文件传输\n");
printf ("\n\t回车键返回  \n");
}
//拒绝接受文件时,此时flag2=0
else 
{
printf ("\n\t您已接受文件传输请求\n");
printf ("\n\t回车键返回  \n");
flag2 = 1;
}
//接受文件时,此时flag2为1

if (flag2 == 0)
{
msg.cmd = 9005; // 不接受文件
strcpy (msg.toname,msg.fromname); // 修改信号发送对象


                //传过来的是发送者是ldp1.现在调换
strcpy (msg.fromname, myName);
//此时这个终端的登陆就是ldp4
write (socketfd, &msg, sizeof(struct Msg)); 
}
else if (flag2 == 1)
{
msg.cmd = 9006;// 接受文件
strcpy (msg.toname,msg.fromname);// 修改信号发送对象
strcpy (msg.fromname, myName);
write (socketfd, &msg, sizeof(struct Msg));

flag7 = 1;
flag2 = 0;//因为接受时将flag置为1,下次再传的时候拒绝时还为1
}
flag3 = 0; // 重置是否存在文件接收请求的判断
// flag2 = 0;
}
if (flag5 == 1) // 判断退出条件
{
flag6 = 1;
flag5 = 0;
ch[0] = 'q';
}
else
{
fgets(ch, 2, stdin);//都是读取文件当中的n-1个字符到s中
while (getchar()!= '\n');  //去掉回车键这个字符
}
switch(ch[0])
{
case '1':     // 查看当前在线人数
display(socketfd);
break;
case '2':     // 进入群聊界面 
chat1(socketfd);
printf ("\n\t回车键返回  \n");
getchar();//吸收回车键后就救赎返回上一层
break;
case '3':     // 进入私聊界面 
chat2(socketfd);
printf ("\n\t回车键返回  \n");
getchar();
break;
case '4':  // 查看聊天记录
dis_histroy(socketfd);
getchar();
break;
case '5':  // 文件传输
convey(socketfd);
break;
case '6':  // 更改密码
change_pass(socketfd);
break;
case '7':  // 在线注销
printf ("\n\t是否确认注销?(y/n):   ");
fgets(ch, 2, stdin);
while (getchar()!= '\n');
if (ch[0] != 'y')
{
printf ("\n\t请稍候.....\n");
usleep(700000);
break;
}
delete_user(socketfd);

case 'q':  // 退出聊天室 返回登录界面
flag5 = 1;
quit_chatroom(socketfd);
printf ("\n\t正在退出,请稍候......\n");
break;
}
if (flag5 == 1) // 判断退出条件
{
flag1 = 1; // 设置线程退出条件(退出)
}
if (flag6 == 1)
{
flag5 = 0;
break;
}
system("clear");

}
}


// 将成员禁言
void silent (int socketfd)
{
msg.cmd = 9011;
strcpy (msg.fromname, myName);

printf ("\n\t请输入您要禁言的用户:\n\t");
scanf ("%s",msg.toname);
getchar();

write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求

printf ("\n\t操作完成,请稍候.....\n");
usleep (500000);
}


// 将成员解除禁言
void silent_del (int socketfd)
{
msg.cmd = 9012;
strcpy (msg.fromname, myName);

printf ("\n\t请输入您要解除禁言的用户:\n\t");
scanf ("%s",msg.toname);
getchar();

write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求

printf ("\n\t操作完成,请稍候.....\n");
usleep (500000);
}


// 将成员踢出聊天室
void kickout (int socketfd)
{
msg.cmd = 9013;
strcpy (msg.fromname, myName);

printf ("\n\t请输入您要踢出的用户:\n\t");
scanf ("%s",msg.toname);
getchar();

write (socketfd, &msg, sizeof(struct Msg));  //向服务器发送请求

printf ("\n\t操作完成,请稍候.....\n");
usleep (500000);
}


// 管理员权限
void supuser (int socketfd)
{
system("clear");
printf ("\t*************************** 网络聊天室 *****************************\n\n\n");
printf ("\t                     1、将成员禁言\n");
printf ("\t                     2、将成员解除禁言\n");
printf ("\t                     3、将成员踢出聊天室\n");
printf ("\n\t***** 请选择: ");
char ch[2];
fgets(ch, 2, stdin);
while (getchar()!= '\n');
switch (ch[0])
{
case '1': // 将成员禁言
silent(socketfd);
break;
case '2': // 将成员解除禁言
silent_del(socketfd);
break;
case '3': // 将成员踢出聊天室
kickout(socketfd);
break;
}
printf ("\n\t回车键返回  ");
getchar();
}


// 管理员操作
void supuser_do (int socketfd)
{
int flag6 = 0;// 再执行一次 quitroom 的操作
char ch[2];
while(1)
{
interface3();
if (flag3 == 1)

{
printf ("\n\n\t%s 请求传输文件,是否接收?(y/n):", msg.fromname);
fflush(stdout);
fgets(ch, 2, stdin);
while (getchar()!= '\n');
if (ch[0] != 'y')
{
printf ("\n\t您已拒绝接受文件传输\n");
printf ("\n\t回车键返回  \n");
}
else 
{
printf ("\n\t您已接受文件传输请求\n");
printf ("\n\t回车键返回  \n");
flag2 = 1;
}
if (flag2 == 0)
{
msg.cmd = 9005;// 不接受文件
strcpy (msg.toname,msg.fromname);// 修改信号发送对象
strcpy (msg.fromname, myName);
write (socketfd, &msg, sizeof(struct Msg)); 
}
else if (flag2 == 1)
{
msg.cmd = 9006;// 接受文件
strcpy (msg.toname,msg.fromname);// 修改信号发送对象
strcpy (msg.fromname, myName);
write (socketfd, &msg, sizeof(struct Msg)); 
}
flag3 = 0; // 重置是否存在文件接收请求的判断
// flag2 = 0;
}



if (flag5 == 1)
// 判断退出条件
 {
flag6 = 1;
flag5 = 0; // 重置线程退出条件
ch[0] = 'q';
 }
//将这个flag5置为0否则下次登录进来直接退出
else
 {
fgets(ch, 2, stdin);
while (getchar()!= '\n');  
 }
switch(ch[0])
{
case '1':     // 查看当前在线人数
display(socketfd);
break;
case '2':     // 进入群聊界面 
chat1(socketfd);
printf ("\n\t回车键返回  \n");
getchar();
break;
case '3':     // 进入私聊界面 
chat2(socketfd);
printf ("\n\t回车键返回  \n");
getchar();
break;
case '4':  // 查看聊天记录
dis_histroy(socketfd);
getchar();
break;
case '5':  // 文件传输
convey(socketfd);
break;
case '6':  // 更改密码
change_pass(socketfd);
break;
case '8':  // 管理员权限操作
supuser (socketfd);
break;
case '7':  // 在线注销
printf ("\n\t是否确认注销?(y/n):   ");
fgets(ch, 2, stdin);
while (getchar()!= '\n');
if (ch[0] != 'y')
{
printf ("\n\t请稍候.....\n");
usleep(700000);
break;
}
delete_user(socketfd);
case 'q':  // 退出聊天室 返回登录界面
flag5 = 1;
quit_chatroom(socketfd);
printf ("\n\t正在退出,请稍候......\n");
break;
}
if (flag5 == 1) // 判断退出条件
{
flag1 = 1; // 设置线程退出条件
}
if (flag6 == 1)
{
flag5 = 0;
break;
}
system("clear");

}
}


// 登录
void log_in(int socketfd)
{
char password[20];
msg.cmd = 2;
printf ("\n\t用户登录:\n");
printf ("\t请输入你用户名: ");
scanf ("%s", myName);
getchar();
//目的就是加上回车键
printf ("\t请输入密码: ");
scanf ("%s", password);//密码是局部变量后来存在全局变量中
getchar();

strcpy (msg.fromname, myName);
strcpy (msg.msg, password);


write (socketfd, &msg, sizeof(struct Msg)); // 向服务器发送登录请求

read (socketfd, &msg, sizeof(struct Msg));  // 读取服务器的登录回应

printf ("\n\t正在校验数据......\n");
sleep(1);
if (msg.cmd == 1002)
{
printf ("\n\t验证通过,正在登录......\n");

usleep(1500000); //其他的除了这个0其他这是这个循环
flag4 = msg.sig;// 更新禁言状态

// 线程分离,用来监听服务器返回信息
pthread_t id;
pthread_create(&id, NULL, readMsg, (void*)socketfd);
// 线程的入口函数是readMsg
pthread_detach(id);  


user_do (socketfd);//普通用户所做的事情

}
else if (msg.cmd == 1003)
{
printf ("\n\t验证通过,正在登录......\n");

usleep(1500000);

flag4 = msg.sig;// 更新禁言状态,这个肯定是0
// 线程分离,用来监听服务器返回信息,阻塞在那里
pthread_t id;
pthread_create(&id, NULL, readMsg, (void*)socketfd);
pthread_detach(id);  


supuser_do (socketfd);
}
else if (msg.cmd == -4)
{
printf ("\n\t此账号已在别处登录\n");
}
else if (msg.cmd == -3)
{
printf ("\n\t验证失败,此账号为空\n");
}
else if (msg.cmd == -2)
{
printf ("\t验证失败,数据库打开失败\n");
}
else if (msg.cmd == -1)
{
printf ("\t数据库操作失败\n");
}
usleep(1500000);
}


// 注册(可注册管理员)
void reg(int socketfd)
{
msg.cmd = 1;
printf ("\t用户注册:\n");
printf ("\t请输入你要注册用户名: ");
scanf ("%s", myName);
getchar();//就是加入回车键表示输入结束


printf ("\t请输入密码: ");
scanf ("%s", msg.msg);
getchar();

printf ("\t请选择你要注册的类型或者禁言: ");
scanf ("%d", &msg.sig);//为int
getchar();

strcpy (msg.fromname, myName);


write (socketfd, &msg, sizeof(struct Msg)); // 向服务器发送注册信息

read (socketfd, &msg, sizeof(struct Msg));  // 读取服务器的注册回应


printf ("\n\t正在校验数据......\n");
sleep(1);
if (msg.cmd == 1001)
{
printf ("\n\t注册成功!\n\t请稍候......\n");
}
else if (msg.cmd == -1)
{
printf ("\t注册失败,该用户名已被注册\n");
}
else if (msg.cmd == -2)
{
printf ("\t注册失败,数据库打开失败\n");
}
usleep(1500000);//停顿0.5秒
}


// 向服务器发送请求
void ask_server(int socketfd)
{
char ch[2];
while (1)
{
interface1();
fgets(ch, 2, stdin);//标准输入回车键也算上,所以应该去掉
while (getchar()!= '\n');//
switch(ch[0])
{
case '1':     // 注册
reg(socketfd);
break;
case '2':     // 登录
log_in(socketfd);
break;
case 'q':  // 退出
exit(0);//void exit(int status)
//status -- 返回给父进程的状态值。通常里面为0是正常退出,为1是异常退出
//数是返回给操作系统的,供给其他程序使用EXIT_SUCCESS参数成功退出
}


system("clear");
}
}


int main(int argc, char **argv)
//char** value  代表一条记录的值 char *argv[] = {"1", "zhang1", "M", "12"};字符串指针数组
{
// 打开数据库Histroy.db
int ret = sqlite3_open("Histroy.db", &database);//这种类型的数据结构
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return -1;
}
// 创建 histroy 表
char *errmsg = NULL;
char *sql = "create table   if not exists Histroy(fromname TEXT,toname TEXT,msg TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);

    //第2个参数const char *sql 是一条 sql 语句,以/0结尾,第3个参数sqlite3_callback 是回调,当这条语句执行之后
//参数void * 是你所提供的指针,你可以传递任何一个指针参数到这里不用置为NULL
//第五个参数是错误信息,
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return -1;
}
// 关闭数据库
sqlite3_close(database);

// 创建与服务器通信的套接字
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror ("socket");
return -1;
}

// 连接服务器
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family  = AF_INET;     // 设置地址族
addr.sin_port    = htons(PORT); // 设置本地端口
inet_aton(argv[1],&(addr.sin_addr));


// 连接服务器,如果成功,返回0,如果失败,返回-1
// 成功的情况下,可以通过socketfd与服务器进行通信
ret = connect(socketfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("connect");
return -1;
}
printf ("成功连上服务器\n");

ask_server(socketfd);

// 关闭套接字
close(socketfd);

return 0;
}


原创粉丝点击