一、实现目标
一个在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. Server 可以内建一个特殊权限的账号admin,用于管理聊天室
2. Admin 可以将某个Client X“踢出聊天室”
3. Admin 可以将某个Client X“设为只能旁听,不能发言”
4. Client 端发言增加表情符号,可以设置某些自定义的特殊组和来表达感情,如输入:),则会自动发送“XXX向大家做了个笑脸”
5. Client 段增加某些常用话语,可以对其中某些部分进行“姓名替换”,例如,输入/ClientA/welcome,则会自动发送“ClientA大侠,欢迎你来到咱们的聊天室”
Client.c源文件
#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;
int flag1 = 0; // 线程退出的判断条件(不退出)
int flag2 = 0; // 文件传输确认信号(无接收)
int flag3 = 0; // 存在文件接收请求的判断(不存在)
int flag4 = 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 szw *\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 szw *\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 szw *\n");
printf ("\t********************************************************************\n\n");
printf ("\t***** 请输入命令: ");
}
// 用来保存收到的聊天信息
void keep_msg(char * msg1)
{
// 打开数据库
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);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("\t数据库操作失败:%s\n", errmsg);
return;
}
}
// 接收文件
void receive()
{
printf ("\n\t正在接收文件.....\n");
int fd2 = open(msg.filename, O_WRONLY|O_CREAT, 0777);
if (fd2 == -1)
{
perror ("open fd2");
return;
}
write (fd2, msg.msg, 1023);
close (fd2);
}
// 用来监听收到的信息
void *readMsg(void *v)
{
int socketfd = (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\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)
printf ("\n\t已成功拦截 %s 给您发送的文件\n", msg.fromname);
else
receive();
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 = 1;
break;
case 9012: // 收到结除禁言信号
printf ("\n\t您已被管理员解除禁言\n");
printf ("\n\t回车键返回 \n");
flag4 = 0;
break;
case 9013: // 收到被踢出信号
printf ("\n\t您已被管理员踢出,即将退出聊天室....\n");
//sleep (2);
flag1 = 1;
break;
}
usleep(400000);
}
}
// 查看当前在线人数
void display (int socketfd)
{
msg.cmd = 1;
write (socketfd, &msg, sizeof(struct Msg)); //向服务器发送请求
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);
write (socketfd, &msg, sizeof(struct Msg)); //向服务器发送退出信号
}
// 进入群聊界面
void chat1(int socketfd)
{
if (flag4 == 1)
{
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)
{
if(strcmp(resultp[i], "all") ==